Die Engineering-Familie in Excel liest sich wie die einfachste Ecke der Funktionsreferenz. DEC2BIN macht aus einer Zahl einen Binärstring. HEX2DEC macht den Weg zurück. IMSUM addiert zwei komplexe Zahlen. Jede davon sieht nach einer Formatierungsübung aus. Das ist sie nicht. Hinter diesen Namen steckt eine zehnstellige Zweierkomplement-Kodierung, die die meisten Entwickler seit einer Vorlesung zur Rechnerarchitektur nicht mehr angefasst haben, ein Format für komplexe Zahlen, das vollständig in Strings lebt, und Bitoperatoren, die einen 64-Bit-Integer stillschweigend überlaufen lassen, wenn man verschiebt, bevor man prüft. Eine Tabellenkalkulations-Engine, die Excel exakt nachbildet, darf nichts davon abrunden
Die Funktionen zerfallen in drei Gruppen, und jede Gruppe verbirgt eine andere Falle. Bei der Basiskonvertierung geht es um negative Zahlen und basisabhängige Schwellenwerte. Bei der komplexen Arithmetik geht es um das Parsen und Formatieren eines Strings. Bei den Bitoperationen geht es darum, innerhalb der Grenzen von Int64 zu bleiben. Dieser Artikel geht jede Gruppe so durch, wie HotXLS sie implementiert, mit den Arbeitsblattaufrufen, die man tatsächlich schreiben würde
Basiskonvertierung und das zehnstellige Zweierkomplement
Die Hinrichtung ist der Teil, den jeder erwartet. DEC2BIN(9) liefert "1001", und ein optionales zweites Argument füllt das Ergebnis links auf eine feste Breite auf. Die Falle ist negative Eingabe. Excel schreibt kein Minuszeichen. Es kodiert den Wert als zehnstelligen Zweierkomplement-String in der Zielbasis, weshalb DEC2BIN(-5,10) "1111111011" zurückgibt und nichts mit Vorzeichen. Das Stellenargument wird ignoriert, sobald der Wert negativ ist, weil die Kodierung bereits auf zehn Stellen festgelegt ist
Zehn Stellen sind ein festes Budget, und dieses Budget bestimmt den darstellbaren Bereich je Basis. Im Binärsystem liegt die Größe, bei der in die negative Hälfte gekippt wird, bei 512, und der Umbruchmodul ist 1024, sodass ein Binärstring nur dann vorzeichenbehaftet ist, wenn er genau zehn Zeichen lang ist und sein Wert mindestens 512 beträgt. Dieselbe Idee skaliert mit der Basis. Oktal verwendet eine Halbschwelle von 2^29 und einen vollen Modul von 2^30. Hexadezimal verwendet 2^39 und 2^40. Der HotXLS-Leser wendet genau diese Regel an: Er akkumuliert die Ziffern, und nur wenn der String zehn Zeichen breit ist und der akkumulierte Wert auf oder über der Halbschwelle liegt, zieht er den vollen Modul ab, um den vorzeichenbehafteten Wert wiederherzustellen. Ein neunstelliger String ist immer nicht negativ, egal wie groß
Der Kodierer ist das Spiegelbild. Ein nicht negativer Wert wird Ziffer für Ziffer konvertiert und optional mit Nullen auf die gewünschte Breite aufgefüllt, und er wird zurückgewiesen, wenn er die positive Obergrenze der Basis überschreitet oder wenn die gewünschte Breite zu schmal ist, um ihn aufzunehmen. Ein negativer Wert wird zuerst durch Addition des vollen Moduls in den Bereich gebracht, was ihn in einen Wert verwandelt, dessen Darstellung in der Basis immer zehn Stellen hat, und dann werden die Ziffern mit führenden Nullen ausgegeben, um die Breite zu füllen. Die eine gemeinsame Bereichsprüfung, die symmetrischen unteren und oberen Grenzen je Basis, ist es, was DEC2BIN, DEC2OCT und DEC2HEX an ihren Rändern untereinander konsistent hält
Bleiben die basisübergreifenden Konvertierungen, also solche wie HEX2BIN und OCT2HEX, die die Basis wechseln, ohne im Funktionsnamen über Dezimal zu gehen. Die Implementierung führt keine eigene Routine für jedes geordnete Paar. Sie parst den Eingabestring mit der Quellbasis in einen vorzeichenbehafteten Dezimalwert und formatiert diesen Dezimalwert dann in die Zielbasis. Dezimal ist der Drehpunkt. Eine Parse-Routine und eine Format-Routine, hintereinandergeschaltet, decken jede Kombination ab, und weil beide Hälften dieselbe zehnstellige Vorzeichenkonvention teilen, übersteht ein negativer Wert die Reise mit intaktem Vorzeichen
Komplexe Zahlen sind Strings, also besteht die Arbeit im Parsen
Excel hat keinen Datentyp für komplexe Zahlen. Ein komplexer Wert ist der String "a+bi", und jede Funktion der IM-Familie nimmt diese Strings entgegen und gibt einen zurück. COMPLEX baut den String aus einem Real- und einem Imaginärteil. IMSUM, IMSUB, IMPRODUCT und IMDIV parsen ihre Argumente, rechnen auf den numerischen Teilen und formatieren das Ergebnis zurück in einen String. Die numerische Arbeit ist Grundstudiumsalgebra. Die Schwierigkeit liegt ganz darin, den Text zuverlässig in zwei Gleitkommazahlen zu verwandeln, und genau dort verdient der interne Parser sein Geld
Zwei Details in diesem Parser gehen leicht schief. Das erste ist die nackte imaginäre Einheit. Der String "i" bedeutet eins mal i, nicht null und kein Fehler, sodass der Parser, wenn der Koeffizient vor dem Suffix leer oder ein einzelnes Pluszeichen ist, ihn als Wert 1 lesen muss, und ein einzelnes Minus als -1. Lässt man das weg, ist IMSUM("i","i") nicht mehr 2i. Das zweite ist die Kollision der wissenschaftlichen Notation mit dem Vorzeichen, das Real- und Imaginärteil trennt. Der Parser findet diesen Trenner, indem er nach einem Plus oder Minus sucht, aber eine Zahl wie "1.5E-3" enthält ein Minus, das zum Exponenten gehört. Die Suche weigert sich deshalb, ein Plus oder Minus als Trenner zu behandeln, wenn das Zeichen unmittelbar davor e oder E ist. Ohne diese Absicherung würde der Realteil am Exponentenvorzeichen zerrissen und das Parsen an vollkommen gültiger Eingabe scheitern
Das Suffix selbst wird beibehalten statt normalisiert. Excel akzeptiert sowohl i als auch j, und HotXLS merkt sich, welches die Eingabe verwendet hat, damit das formatierte Ergebnis denselben Buchstaben trägt. Die Formatierung wendet dann die üblichen Kurzformen an: Ein Imaginärteil von eins wird nur als Suffix ausgegeben, minus eins als -i, ein Imaginärteil von null fällt auf einen reinen Realwert zusammen, und ein Realteil von null lässt das führende 0+ weg
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Engineering');
// Negative Eingabe: zehnstelliges Zweierkomplement, Stellenargument wird ignoriert.
Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
// Komplexe Multiplikation zweier "a+bi"-Strings.
Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
finally
Book.Free;
end;
end;
Die transzendenten komplexen Funktionen, darunter IMSQRT, IMEXP, IMLN und IMPOWER, arbeiten nicht in kartesischen Koordinaten. Sie wandeln den geparsten Wert in Polarform um, wenden die Operation auf Betrag und Argument an und wandeln zurück. Eine Quadratwurzel halbiert das Argument und zieht die Wurzel aus dem Betrag. Eine Potenz multipliziert das Argument und potenziert den Betrag. Jeder andere Weg hieße, jede Identität in kartesischer Form neu herzuleiten, was sowohl mehr Code als auch weniger numerische Stabilität nahe der Verzweigungsschnitte bedeutet
Bitoperatoren und der Überlauf, der zuerst geprüft werden muss
Excel 2013 fügte BITAND, BITOR, BITXOR, BITLSHIFT und BITRSHIFT hinzu. Die Operanden sind eingeschränkt: Jeder muss ein nicht negativer Integer nicht größer als 2^48 minus 1 sein, und jedes gebrochene oder negative Argument ist ein numerischer Fehler. Diese Obergrenze ist großzügig genug, um jede realistische Flag-Menge abzudecken, und bleibt dabei deutlich innerhalb des exakt darstellbaren Bereichs eines Double, was wichtig ist, weil Excel jedes numerische Argument als Gleitkommawert übergibt
Die Verschiebefunktionen tragen die eine Reihenfolgeregel, die wirklich beißt. Eine Linksverschiebung kann einen Wert erzeugen, der weit größer als ihre Eingabe ist, und wer zuerst das shl ausführt und danach das Ergebnis untersucht, hat Int64 bereits überlaufen lassen, und der Test ist bedeutungslos. Die Prüfung muss vor der Verschiebung kommen. HotXLS vergleicht den Operanden mit der um den Verschiebebetrag nach rechts verschobenen Obergrenze, und nur wenn der Operand hineinpasst, führt es die eigentliche Linksverschiebung aus. Eine Verschiebung um mehr als 53 Bits wird rundweg zurückgewiesen, und eine negative Verschiebung kehrt einfach die Richtung um, sodass sich BITLSHIFT mit negativem Zähler wie eine Rechtsverschiebung verhält. Das Prinzip reicht weit über diese eine Funktion hinaus: Wo eine Absicherung existiert, um Überlauf zu verhindern, muss sie auf den Eingaben laufen, niemals auf dem Ergebnis, das sie schützen sollte
// Bitweise Aufrufe werden über Calculate auf dieselbe Weise ausgewertet.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)'); // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)'); // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)'); // 5
Zukunftsfunktionen und das Namenspräfix _xlfn
Die Bitoperatoren und eine lange Liste weiterer Ergänzungen nach 2007 hängen mit einem Benennungsschema zusammen, das nichts damit zu tun hat, was sie berechnen, und alles damit, wie Excel sie speichert. Das ursprüngliche binäre Arbeitsblattformat wies jeder eingebauten Funktion einen numerischen Platz in einer festen Tabelle zu. Funktionen, die nach dem Einfrieren dieser Tabelle erfunden wurden, haben keinen Platz. Um eine solche Funktion in eine Datei zu speichern und von einem modernen Excel erkennen zu lassen, wird der Name mit dem Präfix _xlfn. geschrieben, sodass BITAND auf der Festplatte als _xlfn.BITAND gespeichert wird, obwohl der Nutzer immer nur BITAND tippt
Der Haken ist, dass die Regel nicht einheitlich ist. Einige neuere Funktionen bekamen Tabellenplätze und werden ohne Präfix geschrieben, während ein paar alte verborgene Funktionen trotz ihres Alters ebenfalls ohne Präfix geschrieben werden. HotXLS führt eine explizite Whitelist, welche Namen das Präfix brauchen, fügt es beim Schreiben hinzu und entfernt es beim Lesen, sodass der Formeltext, den Sie setzen und zurücklesen, immer der saubere, Excel-seitige Name ist. Sie setzen =BITLSHIFT(5,2), die Datei enthält _xlfn.BITLSHIFT, und der Wert kommt in jedem Fall als 20 zurück. Das Präfix ist ein Speicherdetail, das nie in die Formeln durchsickern sollte, mit denen Sie im Code arbeiten
Alles zusammen in einem Arbeitsblatt
Die öffentliche Oberfläche für all das ist klein. Man erzeugt ein TXLSXWorkbook, fügt ein Arbeitsblatt hinzu und schreibt entweder über Cells[Row, Col].Formula eine Formel in eine Zelle und berechnet neu, oder man wertet einen Ausdruck direkt mit der Methode Calculate des Arbeitsblatts aus, die die Formel gegen dieses Blatt kompiliert und ein Variant zurückgibt. Die Beispiele oben verwenden Calculate, weil es das Ergebnis eines einzelnen Engineering-Aufrufs ohne den umgebenden Blattzustand zeigt, aber dieselben Funktionen werten in echten Zellformeln identisch aus, wenn die Arbeitsmappe neu berechnet
Die Kodierungen sind der Teil, den man im Kopf behalten sollte, nicht die Aufrufstellen. Ein Binärstring ist nur bei zehn Stellen und nur jenseits der Halbschwelle seiner Basis vorzeichenbehaftet. Eine komplexe Zahl ist Text, ein leerer Imaginärkoeffizient ist eins, und der Parser steigt über das e eines Exponenten hinweg. Eine Linksverschiebung wird geprüft, bevor sie verschiebt. Wer diese vier Fakten richtig hat, für den ist die Engineering-Familie keine Quelle von Vorzeichenüberraschungen mehr
Wenn Sie Ihre eigene Fachmathematik in dieselbe Engine einbinden, wird die Mechanik des Registrierens eines Handlers und der Rückgabe von Werten in unserem Artikel zur Erweiterung der Formel-Engine um benutzerdefinierte Funktionen behandelt, und wenn diese Formeln über Blätter hinweg per Name statt per Zelladresse zugreifen müssen, zeigt die Anleitung zu definierten Namen und blattübergreifenden Formeln, wie die Referenzen aufgelöst werden. Die hier beschriebenen Engineering-Funktionen werden als Teil der HotXLS-Delphi-Tabellenkalkulationskomponente für Delphi und C++Builder ausgeliefert, neben den Lese-, Schreib- und Berechnungs-APIs, die an anderer Stelle in diesem Blog behandelt werden