The Power of 2

Der Begriff "Gleitkommaunterlauf" ist sicherlich nicht jedem geläufig und die Fallstricke, die die POWER()-Funktion bereithält überraschen sicherlich auch den ein oder anderen (unangenehm).

Wie arbeitet POWER()?

Zu beachten ist sicherlich in der Online-Doku der Hinweis darauf, dass der erste Operator implizit nach float konvertierbar sein muss. Die Berechnung erfolgt also mit float-Datentypen und am Ende wird das Ergebnis wieder zurück konvertiert, in den Datentyp, den der erste Operator tatsächlich hat.

Alles kein Thema, solange sich die Ergebnisse auch mit dem Datentypen darstellen lassen, den man als Input verwendet hat.

SELECT POWER(2,5)

bringt als Ergebnis die Zahl 32. Das ist dann wieder ein Integer-Wert, wie auch der erste Operand (2). Verändert man jetzt die Abfrage so, dass der erste Operand ein Dezimal-Wert wird, verändert sich auch das Ergebnis.

SELECT POWER(2.0,5)

Wir erhalten jetzt den Wert 32.0 als Ergebnis! Ist ja eigentlich das selbe, oder?

Verändern wir die Abfrage auf nicht ganzzahlige Potenzen, so ist das Ausgangsformat für das Ergebnis entscheidend.

SELECT POWER(2, 5.2), POWER(2.0, 5.2), POWER(2.00, 5.2), POWER(2.000, 5.2);

Als Ergebnisse erhalten wir die Werte 36, 36.8, 36.76, 36.758! Das ist ja auch noch kein allzu großer Unterschied, oder? Es fällt auf, dass der erste Wert nicht durch Rundung sondern durch Konvertierung entstanden ist. Damit ist das Ergebnis also nicht ganz so wie erwartet. Als Umgehung dieser Einschränkung kann man folgenden Weg wählen:

DECLARE @f FLOAT = 2;
SELECT  POWER(@f, 5.2);
36,7583473599051

Gleitkommaunterlauf

Damit wir auch in diese Falle tappen, können wir mit negativen Potenzen arbeiten. Aus der Schule wissen wir noch (bzw. ich darf aktuell mal wieder Mathe-Stoff der Unter- und Mittelstufe wiederholen), dass eine negative Potenz dem reziproken Wert der positiven Potenz entspricht. Also 2 hoch -5 entspricht dem Bruch 1/(2 hoch 5). Das Ergebnis ist also der Wert 1/32.

Versucht man eine relativ hohe Potenz zu bilden, so erhalten wir eine Fehlermeldung:

SELECT POWER(2, 100);
Arithmetic overflow error for type int, value = 1267650600228229400000000000000.000000.

Logisch! Der große Wert für das Ergebnis kann nicht in einen Integer-Wert konvertiert werden. Versuchen wir es dann mal mit dem reziproken Wert und einem Dezimaldatentyp als Input:

SELECT POWER(2.0, -100);

... so erhalten wir als Ergebnis den Wert 0.0!

Das ist jetzt also der Gleitkommaunterlauf, weil die Nachkommastellen des float-Datentyps begrenzt sind!?!

Die Konvertierung der float-Werte, die eine wissenschaftliche Schreibweise für decimal oder numeric verwenden, ist auf Werte mit einer Genauigkeit von 17 Stellen beschränkt. Alle Werte mit einer Genauigkeit von mehr als 17 Stellen werden auf Null gerundet.

Wirklich ein Gleitkommaunterlauf?

Nähern wir uns dem Thema mit kleineren Potenzen. Das sollte jetzt aber schon gehen, oder?

DECLARE @f FLOAT = 2;
SELECT  POWER(2, -5) AS simple, POWER(2.0, -5) AS wrong, POWER(2.00000, -5) AS trick_1, 1.0/POWER(2, 5) AS trick_2, POWER(@f, -5) AS trick_3 ;

Als Ergebnis erhalten wir diese Tabelle:

simple wrong trick_1 trick_2 trick_3
0 0.0 0.03125 0.031250000000 0.03125

Hier sind ein paar Tricks notwendig. Mindestens sollte man den Datentyp so wählen, dass er Nachkommastellen beinhalten kann. Decimal(2,1) ist hier sicherlich zu wenig. Wenn man weiß, wie groß, bzw. wie klein das Ergebnis wird, kann man entsprechende Nullen nach dem Komma anhängen (2.00000 also Decimal(6,5)). Der Trick mit dem Reziprok, bzw. dem float-Datentyp sollte eigentlich immer funktionieren.

Dann gehen auch plötzlich negative Potenzen von -100, was die Schlussfolgerung zulässt, dass der Hinweis in der Online-Doku auf den Gleitkommaunterlauf eigentlich falsch ist, sondern das Ergebnis eher dem falschen Datentyp als Ziel der Konvertierung geschuldet ist.

DECLARE @f FLOAT = 2;
SELECT  1.0/POWER(2.0, 100), POWER(@f, -100);
0.000000000000000000000000000000788860     7,88860905221012E-31

Darf es etwas weniger sein?

Wie weit runter können wir denn mit unserer Float-Berechnung in diesem Falle gehen? Welche negative Potenz ist wohl das Maximum oder besser Minimum? Nun, das lässt sich leicht ausprobieren und bringt schon erstaunlich kleine Werte, die die Behauptung von einem Gleitkommaunterlauf ebenfalls widerlegen.

DECLARE @f FLOAT = 2
SELECT POWER(@f, -1075);
4,94065645841247E-324

Interessanterweise gibt es in den Systemmeldungen zwar welche für den arithmetischen Überlauf, aber keine für den arithmetischen Unterlauf, obwohl die Norm IEEE 754 diese eigentlich klar definiert.