Articles

Die Gefahren der Verwendung von Float- oder Real-Datentypen

Es gibt einen alten Witz über Gleitkomma-Arithmetik:

„Wenn ich einen Kuchen in drei Teile schneide, ist jedes Stück 0,33 des Kuchens. Wenn ich alle drei Stücke wieder zusammenhalte, gibt mir das 0,99 des Kuchens. Wo ist der Rest des Kuchens geblieben?“
– „Einfach. Das ist das kleine bisschen, das auf dem Messer steckt“

Bei der Gleitkomma-Arithmetik geht es darum, Näherungen zu tolerieren und zu verwalten, um Überlauffehler in Berechnungen zu vermeiden. In der realen Welt legen wir normalerweise Wert auf Präzision in Zahlen und opfern stattdessen Platz und Ressourcen, um Überläufe zu vermeiden.

Während die Wissenschaft glücklich innerhalb einer Fehlergrenze arbeitet, ist Präzision in der Unternehmensbuchhaltung wichtig. Als ich ein Cub-Programmierer war, schrieb ich einmal, was ich für eine perfekt geeignete Methode hielt, um den Gewinn von Börsenmaklergeschäften zu berechnen. In einer Million Pfund war es höchstens ein oder zwei Penny. Ich war sehr zufrieden. Es verwendete die Berechnungen, die dem PL / 1-Compiler innewohnten, den wir damals zur Entwicklung von Finanzpaketen verwendeten. Ich zeigte ihnen die fein gearbeitete Anwendung, und sie waren entsetzt. Ein Penny in einer Million Pfund schien den hartgesottenen Stadthändlern rücksichtslos zu sein. Sie würden es nicht haben. Ich war gezwungen, ein binärcodiertes Dezimalpaket (BCD) in Assembler-Code zu schreiben, das genau war.

SQL Prompt verfügt über eine Codeanalyseregel (BP023), die Sie auf die Verwendung von FLOAT oder REAL Datentypen aufmerksam macht, aufgrund der erheblichen Ungenauigkeiten, die sie in die Art von Berechnungen einbringen können, die viele Organisationen routinemäßig für ihre SQL Server-Daten durchführen.

Datentypen für ungefähre Zahlen

Die Gleitkomma-Arithmetik wurde zu einer Zeit entwickelt, als es vorrangig darum ging, Speicher zu sparen und gleichzeitig Berechnungen mit großen Zahlen vielseitig durchzuführen. Obwohl es immer noch für viele Arten von wissenschaftlichen Berechnungen nützlich ist, insbesondere für solche, die dem IEEE 754-Standard für Gleitkommaarithmetik mit doppelter Genauigkeit entsprechen, ist es notwendigerweise ein Kompromiss. Der Hinweis liegt im Namen dieser Art von Daten und Arithmetik: ‚ungefähr‘. Gleitkommazahlen können nicht alle reellen Zahlen genau darstellen: Außerdem können Gleitkommaoperationen nicht alle arithmetischen Operationen genau darstellen. Der Größenbereich der Zahl, die sie halten können, ist jedoch weitaus größer als bei anderen numerischen Typen, auch wenn sie nicht immer genau gehalten wird.

Die Probleme, die sich aus der Verwendung von Gleitkommaberechnungen ergeben, sind auf das Abrunden während komplexer Berechnungen zurückzuführen und werden am häufigsten gesehen, wenn die Daten ’schlecht konditioniert‘ sind, so dass kleine Änderungen in der Eingabe in der Ausgabe vergrößert werden. Die Ungenauigkeiten sind bei erhöhter Genauigkeit der Darstellung der Zahlen weit weniger offensichtlich, aber sie sind dennoch vorhanden. Es gibt auch einige esoterische Einschränkungen bei der Verwendung von Zahlen, die gültig sind, aber nicht in Gleitkommazahlen dargestellt werden können, wie tan (π / 2), aber diese werden wahrscheinlich nur Mathematiker begeistern.

SQL Server-Gleitkommadatentypen

Der SQL-Standard hat drei Gleitkommadaten, ungefähre Datentypen, REALDOUBLEPRECISION und FLOAT(n). SQL Server entspricht dem, außer dass es keinen DOUBLEPRECISION Datentyp hat, stattdessen FLOAT(53) . Die Datentypen FLOAT(24) und FLOAT(53) entsprechen Binary32 (Single) und Binary64 (double) im IEEE 754-Standard und werden in 4 und 8 Bytes gespeichert und 7 und 16 Ziffern entsprechend gehalten. Sie sind nützlich, wenn es wichtig ist, dass Berechnungen das gleiche Ergebnis liefern wie eine Anwendung, die das .NET Framework verwendet, das auch IEEE 754 verwendet. Der Double Precision-Typ ist auch erforderlich, wenn Zahlen in ihrer Größe das vom Datentyp DECIMAL zulässige Maximum (38 Ziffern) überschreiten, jedoch mit Genauigkeitsverlust. Ungefähre Zahlen können natürlich in keinem Gleichheitstest zuverlässig verwendet werden, z. B. in einer WHERE -Klausel.

Berechnungen mit dem REALEN Datentyp (einfache Genauigkeit)

Ich werde den REAL Datentyp ausprobieren. Der Datentyp FLOAT(24) oder kleiner reagiert auf dieselbe Weise. Das erste, was Sie beim Experimentieren mit Gleitkommazahlen in SQL Server beachten sollten, ist, dass SSMS eine Gleitkommazahl so rendert, dass kleine Unterschiede verschleiert werden. Beispiel:

1
WÄHLEN SIE Konvertieren(REAL,0.100000001490116119384765625)

…ergibt 0.1

Um genauer zu sehen, welcher Wert in einer Gleitkommazahl gespeichert ist, müssen Sie die Funktion STR() verwenden und die gewünschte Genauigkeit angeben.

1
2
3
4
5

/*Ich bin hier ein wenig unfair, weil die Dezimalzahl 0.1 nicht darstellbar ist
fließkomma; die genaue binäre Darstellung hätte eine „1100“ -Sequenz
, die endlos fortgesetzt wird:*/
DECLARE @FirstApproximate REAL = 0.1
SELECT Str(@FirstApproximate,20,16) –sollte 0 sein.100000001490116119384765625

Das ist schon alarmierend. Schließlich haben wir es mit Daten mit Millionen von Zeilen zu tun, so dass sich kleine Fehler stapeln, es sei denn, sie werden, wie beim Runden, gemittelt. Dieser Fehler liegt bereits nahe an dem ‚Penny in einer Million Pfund‘ (1 / 240000000), den ich in der Einleitung erwähnt habe!

Vermeiden wir 0.1 und setzen es auf eine Art Gleitkomma. Wie wäre es, 1 durch 3 zu teilen. Das könnte doch kein Problem sein?

1
2
3
4
5

ANGABE @payoffs REAL = 1
ANGABE @divisor REAL =3
Str(@payoffs /@divisor,20,16) als Quotient
–erzeugt 0,33333333432674408
–sollte 0,333333333333333333 sein

Ups. Es hat es falsch verstanden. OK, es ist ein kleiner Fehler, aber erinnere dich an meine Geschichte über die Banker. Eine Antwort ist entweder richtig oder falsch, es gibt keine Grautöne für die Männer in grauen Anzügen. In der Business School gibt es nur ein Häkchen und ein Kreuz. Kein Zeichen, das ’nahe genug‘ bedeutet.

Ein einfacher Test besteht darin, eins durch Zahlen von eins bis zwanzig zu teilen. Was könnte schief gehen?

Wir können die Ergebnisse der Gleitkomma- und numerischen Berechnung speichern, die beide in Strings konvertiert wurden, und wir vergleichen dann die Strings (seien Sie gewarnt, dass STR() kann in einen führenden Raum setzen, der für eine Komplikation sorgt).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DECLARE @divisor REAL, @dividend REAL = 1
DECLARE @FloatingPointCalculations TABLE (Divisor INT, QuotientFloatingPoint VARCHAR(20), QuotientNumeric VARCHAR(20));
DECLARE @ii INT = 1;
DECLARE @iiMax INT = 20;
WHILE (@ii <= @iiMax)
BEGIN
SELECT @divisor = @ii;
INSERT INTO @FloatingPointCalculations (Divisor, QuotientFloatingPoint,
QuotientNumeric)
SELECT @ii AS divisor, Str(@Dividend / @divisor, 20, 16) AS QuotientFloatingPoint,
Convert(VARCHAR(20), 1.0000000 / @ii) AS QuotientNumeric;
SELECT @ii += 1;
END;
SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations AS The;

Now, what if we list the rows where the numbers don’t match?

1
2
3

SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations The
WHERE Left(LTrim(The.QuotientFloatingPoint),16)<> Left(LTrim(The.QuotientNumeric),16)

Pfui! Nur wenn der Divisor 1, 2, 4, 8 oder 16 war, gab es ein korrektes Ergebnis.

Falls Sie hoffen, dass der Float irgendwie korrekt war und die numerische Version nicht, finden Sie hier den in Excel berechneten numerischen Quotienten:

Berechnungen mit FLOAT(25) oder höher (double precision)

Wenn Sie Gleitkommazahlen mit doppelter Genauigkeit verwenden, FLOAT(25) oder höher, werden die Tests alle bestanden, da die Funktion STR() maximal sechzehn Stellen rechts von der der Dezimalpunkt. Wenn es mehr als 16 gibt, wird das Ergebnis abgeschnitten. Der Datentyp mit doppelter Genauigkeit hat sechzehn Ziffern, während der Datentyp mit einfacher Genauigkeit sieben Ziffern hat. Sie haben auch gesehen, dass der Datentyp mit einfacher Genauigkeit die ersten sieben Ziffern richtig macht. Ebenso erhält die doppelte Genauigkeit die ersten sechzehn Ziffern richtig. Wir können die Zahl einfach erweitern, um die Annäherung zu sehen.

1
2

DEKLARIEREN SIE @FirstApproximate FLOAT(53) = 100000000000000000.1
WÄHLEN SIE Str(@FirstApproximate,40, 16) ALS BigNumberWithaDecimal

Dieser Bruchteil ist verschwunden, nicht wahr? Es ist wahrscheinlich nur ein winziger Unterschied, aber in einigen Berechnungen kann es Probleme verursachen.

Fazit

Gleitkommaarithmetik ist schnell und speichersparsam, liefert aber ein ungefähres Ergebnis. Es eignet sich für gut konditionierte wissenschaftliche Anwendungen, aber nicht für finanzielle Berechnungen, die verlangen, dass eine Zahl entweder ‚richtig‘ oder ‚falsch‘ ist. Es hat auch den zusätzlichen Nachteil in einer Datenbank, da Sie zwei ungefähre Zahlen nicht zuverlässig und konsistent auf Gleichheit testen können.

Es ist nicht richtig zu sagen, dass Sie niemals Gleitkommazahlen in SQL-Datentypen oder in der Arithmetik verwenden sollten. Ungefähre Typen gibt es im SQL-Standard für einen bestimmten Zweck. Ich würde mich heutzutage immer an den Gleitkommadatentyp mit doppelter Genauigkeit in SQL Server halten, wo es eine geeignete Anforderung gibt. Sie eignen sich hervorragend für Zwecke wie das Modellieren von Wettersystemen oder das Zeichnen von Trajektorien, jedoch nicht für die Arten von Berechnungen, für die die durchschnittliche Organisation wahrscheinlich eine Datenbank verwendet.

Wenn Sie eine fehlerhafte Verwendung dieser Typen feststellen, sollten Sie stattdessen zu einem geeigneten DECIMALNUMERIC -Typ wechseln. Wenn Sie wissen, dass Sie Gleitkommaarithmetik benötigen und erklären können, warum, dann wissen Sie wahrscheinlich genug, um die Fallstricke des Gleitkommas zu vermeiden, wie die, die bei dem berühmten Patriot-Raketenfehler auftrat, der direkt zu 28 Todesfällen führte.