Unicode, UTF-8, UTF-16, ISO-8859-1: Warum ist das so schwierig?

English

Seit etwa 20 Jahren schlagen wir uns mit der Umstellung auf Unicode herum.

Warum ist das so schwierig?

Das größte Problem ist, dass man Dateien nur sehr begrenzt ansieht, wie ihr Inhalt zu interpretieren ist. Wir haben letztlich ein paar Tricks, mit denen man es oft erkennen kann:
Die Endungen funktionieren für häufige und gut definierte Dateitypen, z.B. .jpg oder .png recht gut. In anderen Fällen wird der Inhalt der Datei untersucht und zum Beispiel am Anfang der Datei so etwas wie

#!/usr/bin/ruby

gefunden, woraus geschlossen werden kann, dass das mit ruby ausgeführt werden soll und zwar mit dem Ruby, das unter /usr/bin/ruby steht. Wenn man sich lieber nicht festlegen will und das Ruby haben will, das zuerst im Pfad ( $PATH ) kommt, dann kann man stattdessen

#!/usr/bin/env ruby

verwenden. Das geht leider unter MS-Windows nur unter cygwin, wenn man das cygwin-Ruby verwendet, nicht aber mit dem nativen Win32-Ruby (oder Win64-Ruby).

Nun kommt aber der nächste Schritt und der ist einfach ärerlich. Welches „encoding“ wird für diese Datei verwendet? Man kann sich auf UTF-8 oder ISO-8859-1 einigen, aber sobald einer im Team vergisst, seinen Editor entsprechend zu konfigurieren, ist Durcheinander abzusehen, weil dann Dateien entstehen, die UTF-8 und ISO-8859-1 (oder noch andere Encodings) miteinander mischen, was dann irgendwann zu obskuren Fehlern führt.

Es war ein großer Fehler, dass bei der Entwicklung von C, Unix und vor allem der libc ein Verständnis von Dateien definiert wurde, das keine Typ-Information für den Dateiinhalt erlaubt. Im Internet haben wir Mime-Header für EMail und WWW-Seiten und alles mögliche andere. Dadurch weiß der Empfänger der Kommunikation stets, wie die empfangen Daten zu interpretieren sind. Ich denke, dass Dateien solche Metainformationen haben sollte, die etwa dem Mime-Header entsprechen. Dann könnte man Dateien beliebig umbenennen, sogar die Endung, ohne dass die Datei dadurch unlesbar würde. Aber als Unix und C entwickelt wurde, wurde auch die libc und die Filesystemkonzepte definiert. Daran haben sich alle Unixe seither gehalten und auch Linux folgt diesen Vorgaben. Aber auch in der MS-Windows-Welt hat man die Betriebssysteme wahrscheinlich in C entwickelt und dabei diese Eigenschaften oder deren Fehlen geerbt. Ich weiß nicht, bis wann man versucht hat, MS-Windows-NT/2000/XP/Vista/7/8… noch auf FAT-Dateisystemen lauffähig zu halten, dabei hätte NTFS mit den multiplen Streams pro Datei eine Werkzeug geschaffen, mit dem man so einen Mime-Typ im zweiten Stream und den eigentlichen Inhalt im ersten Stream speichern könnte. Was aber fehlt ist ein allgemein anerkanntes Regelwerk, das die Nutzung des zweiten Streams für Typinformationen festlegt Aber man verwendet weiterhin Endungen, hofft auf gutes Glück bei Textdateien und analysiert magic-bytes innerhalb der Dateien, um den Typ der Datei und das Encoding zu raten.

Natürlich haben XML und HTML die Möglichkeit, das Encoding innerhalb der Datei zu definieren. Dummerweise muss man aber das Encoding der Datei schon kennen, um die Zeilen zu lesen, wo drinsteht, welches Encoding die Datei hat. Das ist nicht so schlimm, denn letztlich steht diese Information jeweils am Anfang der Datei und man kann ein paar Encodings durchprobieren, und jeweils unter dieser Annahme anfangen, die Datei zu lesen, bis man es richtig weiß.

Am gefährlichsten ist es, UTF-8 und ISO-8859-1 (oder ähnliche Encodings) zu verwechseln. Da die unteren 128 Zeichen bei beiden gleich sind und zumindest im deutschen Sprachraum doch die überwältigende Mehrheit von Text-Inhalten darstellen, sticht das Problem nicht gleich ins Auge, sondern schleicht sich eher ein, wenn man nicht sauber arbeitet und Dateien mit verschiedenen Encodings zusammenkopiert oder die Dateien den falschen Konversionen aussetzt. Nun werden aber die Umlaute in UTF-8 durch zwei Zeichen codiert, in ISO-8859-1 durch eines. Beim Lesen der Datei unter der falschen Annahme bekommt man also irgendwann mal Zeichenfolgen, die in dem Encoding eigentlich gar nicht vorkommen dürften. Bei UTF-16 ist das einfacher, weil die Dateien dort jeweils mit FFFE oder FEFF anfangen, so dass man einigermaßen sicher UTF-16 an sich und die Bytefolge (niedriges zuerst oder hohes zuerst) erkennen kann.

In der MS-Windows-Welt kommt noch als weiteres Ärgernis hinzu, dass zwar das ganze System mit modernen Encoding arbeiten kann, aber diese schwarzen CMD-Fenster kommen immer noch mit CP-850 oder CP-437 hoch, enthalten also in etwa dieselben Zeichen wie ISO-8859-1, aber an anderen Positionen. Da bekommt man dann schon einmal ein Sigma-Zeichen statt einem „ä“ zu sehen. Diese Incompatibilität innerhalb desselben Systems bringt natürlich Nachteile mit sich.

Share Button

Die kleinen Hürden der Interoperabilität

Heute hat sich in der IT-Landschaft vieles vereinheitlicht, so daß Interorabilität besser geworden ist als vor 20 Jahren.

Ein paar Beispiele:

  • Netzwerktechnologie: Heute hat sich TCP/IP als Netzwerktechnologie durchgesetzt. Sogar die Verkabelung mit RJ45/Ethernet und die Funknetze (WLAN) sind standardisiert und passen zwischen verschiedensten Geräten zusammen. Vor ein paar Jahren gab es beliebig viele proprietäre Netzwerktechnologiene, die nicht miteinander kompatible waren, z.B. BitNet (IBM), NetBios (MS), DecNet (DEC), IPX (Novell),….
  • Zeichensätze: Heute haben wir Unicode und ein paar standardisierte Zeichensätze und Codierungen und zumindest für Web und EMail Wege, diese Metainformation zur Verfügung zu stellen. Dieser Bereich ist noch nicht problemfrei, aber im Vergleich zu früheren Jahren, wo verschiedene EBCDIC-Codierungen regierten oder wo Zeichensätze üblich waren, die keine Umlaute enthielten, haben wir hier auch große Fortschritte in der Standardisierung erlebt.
  • Zahlen: Es hat sich für Fließkommazahlen und für Ganzzahlen eine gewisse, relativ kleine Menge von numerischen Typen etabliert, die immer wieder benutzt werden und die sich überall (fast) gleich verhalten. Problematisch bleibt der Integer-Überlauf.
  • Software: Früher hat man Software für eine spezifische Maschine entwickelt, also eine CPU-Architektur mit einem Betriebssystem. Heute hat man die Möglichkeit, einheitliche „Plattformen“ auf verschiedenester Hardware zu haben: Linux läuft auf fast jeder physikalischen und virtuellen Hardware vom Mobiltelefon bis zum Supercomputer und es ist praktisch derselbe Kernel, läßt sich also gleich nutzen. Java, Ruby, Perl, Scala und andere Programmiersprachen sind auf verschiedensten Plattformen vorhanden und bieten sozusagen ihre eigene abstrakte Plattform. Und das Web ist eine einfache und sinnvolle Möglichkeit, Applikationen für verschiedenste Geräte nur einmal zu entwickeln.
  • Dateisysteme: Es hat sich ein einigermaßen einheitliches Verständnis dafür, wie ein Dateisystem aussehen soll, entwickelt, mit einigen betriebssystemspezifischen Besonderheiten. Für Datenhaltung lassen sich Dateisysteme aber gemeinsam für verschiedene Betriebssyteme nutzen, zum Beispiel mit Samba.
  • GNU-Tools: Die GNU-Tools (bash, ls, cp, mv,……..) sind unter Linux zum Standard geworden und ihren traditionellen Unix-Pendents, wie man sie noch heute z.B. unter Solaris findet, haushoch überlegen. Man kann sie aber auf praktisch jedem Unix installieren und es gibt mit cygwin sogar eine Portierung für MS-Windows.

Interoperabilität ist heute für viele Interoperabilität zwischen Linux (oder anderen Posix-Systemen) und Win32/Win64 (MS-Windows).

Erfahrene Linux-Anwender sind es gewohnt, als Trennzeichen für Pfade diesen Schrägstrich „/“ (forward slash) zu verwenden. Der umgekehrte Schrägstrich „\“ wird benötigt, um Sonderzeichen zu „escapen“. In der MS-Windows-Welt sieht man häufig, daß der umgekehrte Schrägstrich „\“ (backslash) als Trennzeichen verwendet wird. Das ist nötig im CMD-Fenster, weil dieses den normalen Schrägstrich „/“ nicht durchläßt. Meine Erfahrung ist aber, daß die low-level-Win32-Bibliotheken beide Varianten verstehen. Sowieso werden die normalen Schrägstriche „/“ von cygwin, ruby, perl, java u.s.w. verstanden. Man kann sich also fast immer die Mühe sparen, hierfür Fallunterscheidungen zu machen, außer man schreibt cmd-Skripte. Und wer will sich das schon für mehr als fünf oder sechs Zeilen antun. Ich empfehle also für Java-, Perl- und Ruby-Entwickler auch unter MS-Windows ausdrücklich immer den normalen Schrägstrich „/“ als Trenungszeichen in Pfaden zu verwenden. Das ist lesbarer, schon weil man den umgekehrten Schrägstrich „\“ oft verdoppeln muß, und es erleichtert die Portablität auf Linux oder Posix.

Tückischer ist die Sache mit dem Zeilenwechsel. In der Linux- und Unix-Welt ist in Textdateien ein „Linefeed“ („\n“=Ctrl-J) als Zeilenwechsel üblich. In der MS-DOS und MS-Windows-Welt hat sich dagegen „Carrige-Return+Linefeed“ („\r\n“=Ctrl-M Ctrl-J) etabliert. Die meisten heutigen Programme stören sich nicht daran und kommen unter beiden Plattformen mit beidem klar. Wer unter MS-Windows Notepad verwendet, wird mit Linux-Zeilenenden keine Freude haben, aber Notepad muß man wirklich unter MS-Windows nicht benutzen, da es dort bessere Editoren (gvim, emacs, ultraedit, scite, …) gibt. Umgekehrt führt der MS-Windows-Zielenwechsel bei ausführbaren Skripten unter Linux zu Probleme. Skripte enthalten normalerweise in der ersten Zeile so etwas wie „#/usr/bin/ruby“. Das nimmt das Betriebssystem als Hinweis, daß man das Programm /usr/bin/ruby verwenden soll, um dieses Skript auszuführen. Wenn aber die Zeile mit Ctrl-M Ctrl-J endet, dann wird nach einem Programm „/usr/bin/ruby^M“ gesucht (^M = Ctrl-M = „\r“) gesucht, das es natürlich nicht gibt und man erhält eine unverständliche Fehlermeldung.

Ad hoc kann man die Umwandlung schnell so machen:

$ perl -i~ -p -e ’s/\r//g;‘ script

Oder für die Umgekehrte Richtung:

$ perl -i~ -p -e ’s/\n/\r\n/g;‘ textfile

Wer noch Subversion verwendet, sollte Skripte dort so einstellen, daß sie immer nur mit „LF“ als Zeichenwechsel gespeichert werden und Textdateien vielleicht jeweils mit der Konvention des Betriebsystems, unter dem der Client läuft.

Share Button

neo4j

Da ich in dieser Woche einen Vortrag darüber gehört habe, schreibe ich mal einen kurzen Beitrag dazu.

Sicher haben viele schon von „NoSQL“-Datenbanken gehört.

In den guten alten Zeiten kam so etwa alle 10 Jahre ein neues Datenbank-Paradigma auf, bis die relationalen Datenbanken kamen. So etwa Mitte der 90er Jahre wäre nach diesem 10-Jahres-Rhytmus wieder etwas neues fällig gewesen und die objektorientierten Datenbanken waren ein recht offensichtlicher Kandidat. Letztlich blieben sie aber Nischenprodukte, ebenso wie einige andere Ideen, wie XML-Datenbanken.

Die relationalen Datenbanken und vor allem SQL waren zu gut oder zu gut etabliert und zu gut verstanden und statt objektorientierte Datenbanken einzusetzen verliebte man sich in verschiedene Technologien, um objektorientierte Software mit relationalen Datenbanken zu verbinden, zum Beispiel OR-Mapping wie Hibernate, JDO oder Eclipselink in der Java-Welt oder ActiveRecord in der Ruby-Welt. Diese Technologien, ihre Vor- und Nachteile und auch die grundsätzlichen konzeptionellen Fragen dazu sind sicher noch Stoff für viele Blog-Artikel in der Zukunft…

Letztlich scheint jetzt das Thema „NoSQL“-Datenbanken neben den weiterhin starken relationalen Datenbanken seinen Platz einzunehmen. Dabei steht „NoSQL“ angeblich für „not only SQL“. Letztlich sind es aber zwei Aspekte, an denen man schraubt. Die gängigen SQL-Datenbanken sind relational (oder zumindest unterstützen sie das relationale Modell) und transaktional. Das Thema Transaktionen ist sicher auch interessant genug für viele Blog-Beiträge und man kann problemlos allein darüber ein Buch von mehreren 100 Seiten schreiben, das nicht langweilig wird, wenn man sich mit verteilten Transaktionen und der Implementierung dieser Konzepte und der theoretischen und praktischen Zuverlässigkeit solcher Implementierungen gemessen an den Ansprüchen beschäftigt. Es gibt gegen Einwurf vieler großer Münzen mehrere gute Monographien dazu im Buchhandel.

Eine wichtige Motivation für die Entwicklung und Verbreitung der noSQL-Datenbanken war „Big Data“, also die Verarbeitung riesiger Datenmengen, die den Rahmen traditioneller relationaler transaktionaler Datenbanken wie Oracle, DB2, PostgreQL u.s.w. sprengen. Solche Fragestellungen findet man unter anderem bei Webapplikationen wie sie Google oder Facebook betreiben. Es gibt aber auch Fragestellungen mit Datenmengen, die noch gut für relationale Datenbanken handhabbar sind, die sich aber von ihrer Struktur nicht so gut für das relationale Modell eignen.

Nun muß eine SQL-Datenbank nicht transaktional sein. Zum Beispiel war es mySQL lange Zeit nicht und heute ist die für Data-Warhouses spezialisierte Datenbank Teradata unterstützt Transaktionen nur eingeschränkt.

NoSQL-Datenbanken weichen aber das relationale Prinzip auf und je nach Einzelfall eventuell außerdem die Transaktionalität. Es gibt verschiedene Arten von NoSQL-Datenbanken, zum Beispiel Key-Value-Stores wie Riak oder dokumentenorientierte Datenbanken wie MongoDB oder CouchDB, die sich eignen, wenn man eine gewisse Struktur der Daten kennt, aber die einzelnen Datensätze doch von Zeile zu Zeile (oder hier von Dokument zu Dokument) zu stark varieren oder zu stark strukturiert sind, um gut in eine normalisierte relationale Datenbank zu passen.

Graphendatenbanken speichern Daten in der Struktur eines Graphen. Man hat also Knoten mit gewissen Eigenschaften (Daten) und Verbindungen zwischen diesen Knoten mit gewissen Eigenschaften. Ein Beispiel ist eine IT-Landschaft, in der man Hardware, virtuelle Server, Basis-Software, Applikationen, Businessprozesse u.s.w. hat, zwischen denen verschiedene Arten von Abhängigkeiten bestehen können. Das war das Beispiel, das in dem Vortrag gebracht wurde. Das läßt sich eigentlich gut im relationalen Modell abbilden, ist aber in der Praxis sehr schwerfällig zu gebrauchen, weil die Queries um einen Teilgraphen zu laden, sehr schwerfällig sind und weil man letztlich durch fortgesetztes Verfolgen von Abhängigkeiten sehr schnell einen großen Teil des Systems im Speicher hat. Mit einer Graphendatenbank kann man diese Struktur allerdings ganz natürlich und direkt modellieren. neo4j ist zum Beispiel eine solche Graphendatenbank, die als Opensource-Software verfügbar ist. Sie enthält auch praktischerweise gleich noch Implementierungen einiger gängiger Graphenalgorithmen, die man direkt auf dem gespeicherten Graphen operieren lassen kann. So lassen sich gewisse Aufgabenstellungen sehr elegant lösen, die mit einer relationalen Datenbank zwar theoretisch korrekt, aber nicht praxistauglich umsetzbar sind, sobald der zu speichernde Graph eine gewisse Größe und Komplexität erreicht. Zur Aufweichung des Transaktionsprinzip ist noch zu sagen, daß neo4j transaktional ist.

Share Button

Carry-Bit: Wie funktioniert das?

English

Alle, die in der Grundschule noch das handschriftliche Addieren gelernt haben, kennen das Verfahren eigentlich. Es ist nichts anderes als das, nur nicht im Zehnersystem, auch nicht im Zweiersystem, sondern im 256er-System (8 Bit), 65536er-System (16 Bit), 4294967296er-System (32 Bit), 18446744073709551616er-System (64 Bit) oder was auch immer die Wortbreite der CPU ist. Dass man immer mit Zweierpotenzen arbeitet, ist heute üblich, aber es kann durchaus sein, dass man von unseren zweiwertigen Bits einmal auf dreiwertige „Trits“ wechselt, wenn sich die Hardware-Technologie weiterentwickelt. Wir werde es sehen.

Ich finde es zwar nicht sinnvoll, dass man sich bei der Applikationsentwicklung mit solchen Details auf Bit-Ebene herumschlagen muss, aber da man ja mit Java, C, C++, C# und ähnlichen Sprachen heute einen großen Teil der Applikationsentwicklung durchführt, kommt man daran nicht vorbei. Diese Sprachen zwingen den Entwickler, sich mit diesen Fragen zu einem gewissen Maße auseinanderzusetzen, um deren Darstellungen ganzer Zahlen zu verstehen. Aber das „Carry“-Bit sieht man leider in diesen Sprachen nicht, obwohl es für das Verständnis wichtig ist.

Ich habe mich seit etwa Anfang der 80er Jahre damit beschäftigt, Software zu erstellen. Die damals für mich zugänglichen Rechner waren 8-Bit-Rechner mit 6502 oder 6510 als CPU und 1 MHz Taktfrequenz. Man konnte sie in einem Basic-Dialekt programmieren, aber das war für viele Zwecke unbrauchbar, weil es zu langsam war. So kam Assemblersprache zum Einsatz. Ich habe dann später noch 680×0-Assembler und 80×86-Assembler verwendet, aber ab Mitte der 90er Jahre kam das eigentlich nicht mehr vor. Eine 8-Bit-CPU kann zwei 8-Bit-Zahlen miteinander addieren und dabei ein 8-Bit-Ergebnis liefern. Dabei gibt es zwei Varianten zu unterschieden, nämlich signierte Ganzzahlen, die meistens im Zweierkomplement dargestellt werden. Das bedeutet, dass das erste Bit das Vorzeichen codiert. Somit sind die Werte von 0 bis 127 postive Ganzzahlen, wie man es erwartet. Die 127 hat das Bit-Muster 01111111. Nun könnte man meinen, dass das Bitmuster 10000000 die direkt darauffolgende Zahl, also 128 ist, aber in Wirklichkeit ist das die -128, weil ja das erste Bit das Vorzeichen codiert und die „1“ für negatives Vorzeichen steht. Erhöht man die -128 weiter, erhöht sich der Zahlwert, weiter, wird also weniger negativ. Man hat dann am Schluss 11111111, um die -1 auszudrücken. Dieses etwas obskure Verhalten ist plausibel, wenn man sich vorstellt, nicht mit ganzen Zahlen zu rechnen, sondern mit Restklassen modulo 256. Dabei werden zwei Zahlen also kongruent, also zusammengehörig, angesehen, wenn sie sich nur um ein Vielfaches von 256 unterscheiden. Um alle 256 möglichen Restklassen abzudecken, kann man als Vertreter die Zahlen von 0 bis 255 (unsigned byte) oder von -128 bis 127 (signed byte, Zweierkomplement) verwenden. Beides kommt in unseren heutigen Programmiersprachen vor.

Für das Carry-Bit nehme ich zunächst der Einfachheit einmal an, dass wir nur mit nicht-negativen Zahlen rechnen. Die möglichen Werte eines Speicherworts sind also von 0 bis 2^n-1, wobei n die Wortbreite ist, also in unserem Beispiel 8. Heutige CPUs haben normalerweise 64bit Wortbreite, was nichts an der prinzipiellen Funktionisweise ändert, aber mit 8Bit ist das Beispiel übersichtlicher. Jeder kann sich vorstellen, wie sich das Prinzip auf 32 oder 64 oder 36 oder auch 96 Bit übertragen ließe.

Es steht also die Bitfolge 11111111 für 255. Nun kann man mit einem Assemblerbefehl, der oft ADD oder so ähnlich heißt, zwei solche Zahlen addieren. Das heißt, dass die Addition innerhalb der CPU als eine einzige Operation innerhalb von einem oder einigen wenigen Taktzyklen durchgeführt werden kann. Nun ergibt die Addition von zwei unsignierten 8-Bit-Zahlen eine Zahl zwischen 0 und 510 (111111110 binär), leider zu viel für ein Byte. Mit einem Bit mehr ließe sich das aber ausdrücken. So behilft man sich, indem man die niedrigen 8 Bit des Ergebnisses als Resultat akzeptiert, aber das neunte, oberste Bit, das nun 0 oder 1 sein kann, in einem sogenannten Carry-Bit oder Carry-Flag oder Übertragsbit in der CPU speichert. Dieses kann man nun abfragen und davon abhängig einen anderen Pfad einschlagen, zum Beispiel eine Fehlerbehandlung wegen eines Überlaufs auslösen, wenn die weiteren Verarbeitungsschritte nicht in der Lage sind, mehr als 8-Bit zu verwenden. Aber es gibt auch eine sehr elegante Lösung, die zum Zuge kommt, wenn man Zahlen addiert, die mehrere Bytes (oder CPU-Worte) breit sind. Ab der zweiten Addition verwendet man so etwas wie ADC („Add with Carry“). Dabei wird das Carrybit, das 0 oder 1 ist, als dritter Summand einbezogen. Damit ist das Ergebnis diesmal sogar zwischen 0 und 511 (111111111 binär). Wieder erhält man ein Carry-Bit. Man kann diese Addition nun fortfahren, bis man alle Bytes der Summanden verarbeitet hat. Wenn sie verschieden lang sind, kann man die oberen Bytes des kürzeren Summanden durch 0 ersetzen, solange das Carry-Bit noch 1 ist, oder ansonsten die Bytes des längeren Summanden einfach übernehmen. Um das Gesamtergebnis auszudrücken braucht man in diesem Fall eventuell ein Byte (oder CPU-Wort) mehr als der längere der beiden Summanden hat.

So lässt sich relativ einfach eine Langzahladdition in Assemblersprache schreiben. Es ist einer der größten Design-Fehler vieler heutiger Programmiersprachen, insbesondere von C, dass sie einerseits mit Low-Level-Ganzzahltypen ausgestattet sind, aber andererseits uns das Carry-Bit vorenthalten, so dass man dieses mit viel Gebastel ermitteln muss.

Die Subtraktion von Langzahlen funktioniert sehr ähnlich, dafür gibt es meist ein SBC („subtract with carry“) oder SBB („subtract with borrow“), je nachdem, wie das Carry-Bit bei der Subtraktion interpretiert wird.

Für die vorzeichenbehafteten Ganzzahlen muss man beim jeweils höchsten Byte der beiden Summanden aufpassen und hier das Vorzeichenbit berücksichtigen. Häufig gibt es ein sogenanntes Overflow-Bit, das in diesem Fall zumindest erkennen lässt, wann man ein weiteres Speicherwort benötigt.

Die 64-Bit-Addition heutiger CPUs könnte prinzipiell so funktionieren, dass sie bitweise nacheinander oder byteweise nacheinander mit Carry addiert. Mir sind die Implementierungsdetails von ARM, Intel und AMD zwar nicht bekannt, aber ich gehe davon aus, dass man dort eine größere Parallelisierung der Operation verwendet. Es gibt Algorithmen, die es so ermöglichen, eine Langzahladdition unter Verwendung von Parallelisierung wesentlich schneller auszuführen als mit dem hier beschriebenen Verfahren. Vielleicht schreibe ich dazu auch irgendwann einmal etwas, wenn es jemanden interessiert.

Interessant ist es auch, Multiplikation, Division, Quadratwurzel, Kubikwurzel und ähnliches zu berechnen. Auch damit habe ich Erfahrung, kann das also bei Interesse gerne beschreiben. Kurz gesagt sind diese Operationen auf heutigen CPUs sehr einfach in Assemblersprache zu implementieren, weil dort Multiplikation und Division bereits vorhanden sind, aber es geht auch mit 8-Bit-CPUs ohne Multiplikationsbefehl, falls das jemanden aus Nostalgiegründen interessieren sollte. Gerade für die Multiplikation gibt es aber wesentlich bessere Algorithmen, wenn die Faktoren sehr lang sind.

Ich möchte einen Artikel über die mögliche Ermittlung des Carrybits in C verfassen. Dieser wird auf Englisch erscheinen und unter der englischen Übersetzung dieses Artikels als Ping-Back verlinkt sein.

Share Button