Warum sind Gleitkommazahlen ungenau?

198

Warum verlieren einige Zahlen an Genauigkeit, wenn sie als Gleitkommazahlen gespeichert werden?

Zum Beispiel kann die Dezimalzahl 9.2genau als Verhältnis von zwei Dezimalzahlen ( 92/10) ausgedrückt werden, die beide genau in binär ( 0b1011100/0b1010) ausgedrückt werden können . Das gleiche Verhältnis, das als Gleitkommazahl gespeichert ist, ist jedoch niemals genau gleich 9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

Wie kann eine so scheinbar einfache Zahl "zu groß" sein, um sie in 64 Bit Speicher auszudrücken ?

mhlester
quelle

Antworten:

240

In den meisten Programmiersprachen werden Gleitkommazahlen ähnlich wie in der wissenschaftlichen Notation dargestellt : mit einem Exponenten und einer Mantisse (auch als Signifikand bezeichnet). Eine sehr einfache Zahl 9.2ist beispielsweise dieser Bruchteil:

5179139571476070 * 2 -49

Wo der Exponent ist -49und die Mantisse ist 5179139571476070. Der Grund, warum es unmöglich ist, einige Dezimalzahlen auf diese Weise darzustellen, ist, dass sowohl der Exponent als auch die Mantisse ganze Zahlen sein müssen. Mit anderen Worten, alle Floats müssen eine ganze Zahl multipliziert mit einer ganzzahligen Potenz von 2 sein .

9.2kann einfach sein 92/10, aber 10 kann nicht als 2 n ausgedrückt werden, wenn n auf ganzzahlige Werte beschränkt ist.


Daten anzeigen

Zunächst einige Funktionen, um die Komponenten zu sehen , aus denen ein 32- und 64-Bit besteht float. Beschönigen Sie diese, wenn Sie sich nur für die Ausgabe interessieren (Beispiel in Python):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

Hinter dieser Funktion steckt eine Menge Komplexität, und es wäre ziemlich tangential zu erklären, aber wenn Sie interessiert sind, ist die Struktur die wichtige Ressource für unsere Zwecke .

Python floatist eine 64-Bit-Zahl mit doppelter Genauigkeit. In anderen Sprachen wie C, C ++, Java und C # hat die doppelte Genauigkeit einen separaten Typ double, der häufig als 64-Bit implementiert wird.

Wenn wir diese Funktion mit unserem Beispiel aufrufen, erhalten 9.2wir Folgendes:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

Daten interpretieren

Sie werden sehen, dass ich den Rückgabewert in drei Komponenten aufgeteilt habe. Diese Komponenten sind:

  • Zeichen
  • Exponent
  • Mantisse (auch Signifikand oder Bruch genannt)

Zeichen

Das Vorzeichen wird in der ersten Komponente als einzelnes Bit gespeichert. Es ist leicht zu erklären: 0bedeutet, dass der Float eine positive Zahl ist; 1bedeutet, dass es negativ ist. Weil 9.2positiv ist, ist unser Vorzeichenwert0 .

Exponent

Der Exponent wird in der mittleren Komponente als 11 Bit gespeichert. In unserem Fall 0b10000000010. In Dezimalzahlen entspricht dies dem Wert 1026. Eine Besonderheit dieser Komponente ist, dass Sie eine Zahl von 2 ( Anzahl der Bits) - 1 - 1 subtrahieren müssen , um den wahren Exponenten zu erhalten. In unserem Fall bedeutet dies Subtrahieren 0b1111111111(Dezimalzahl 1023), um den wahren Exponenten 0b00000000011(Dezimalzahl 3) zu erhalten.

Mantisse

Die Mantisse ist in der dritten Komponente als 52 Bit gespeichert. Diese Komponente hat jedoch auch eine Besonderheit. Um diese Eigenart zu verstehen, betrachten Sie eine Zahl in wissenschaftlicher Notation wie folgt:

6.0221413x10 23

Die Mantisse wäre die 6.0221413. Denken Sie daran, dass die Mantisse in wissenschaftlicher Notation immer mit einer einzelnen Ziffer ungleich Null beginnt. Das gleiche gilt für Binär, außer dass Binär nur zwei Ziffern hat: 0und 1. Die binäre Mantisse beginnt also immer mit 1! Wenn ein Float gespeichert ist, wird die 1Vorderseite der binären Mantisse weggelassen, um Platz zu sparen. Wir müssen es wieder vor unser drittes Element setzen, um die wahre Mantisse zu erhalten:

1.0010011001100110011001100110011001100110011001100110

Dies beinhaltet mehr als nur eine einfache Addition, da die in unserer dritten Komponente gespeicherten Bits tatsächlich den Bruchteil der Mantisse rechts vom Radixpunkt darstellen .

Beim Umgang mit Dezimalzahlen "verschieben wir den Dezimalpunkt" durch Multiplizieren oder Dividieren mit Potenzen von 10. In Binärzahlen können wir dasselbe tun, indem wir mit Potenzen von 2 multiplizieren oder dividieren. Da unser drittes Element 52 Bits hat, teilen wir es um 2 52 , um es 52 Stellen nach rechts zu bewegen:

0,0010011001100110011001100110011001100110011001100110

In Dezimalschreibweise entspricht dies dem Teilen 675539944105574durch 4503599627370496, um zu erhalten 0.1499999999999999. (Dies ist ein Beispiel für ein Verhältnis, das genau in Binärform, aber nur ungefähr in Dezimalzahl ausgedrückt werden kann . Weitere Informationen finden Sie unter: 675539944105574/4503599627370496 .)

Nachdem wir die dritte Komponente in eine Bruchzahl umgewandelt haben, 1ergibt das Hinzufügen die wahre Mantisse.

Komponenten neu zusammenfassen

  • Vorzeichen (erste Komponente): 0für positiv, 1für negativ
  • Exponent (mittlere Komponente): Subtrahieren Sie 2 (Anzahl der Bits) - 1 - 1 , um den wahren Exponenten zu erhalten
  • Mantisse (letzte Komponente): Teilen Sie durch 2 (Anzahl der Bits) und addieren Sie 1, um die wahre Mantisse zu erhalten

Berechnung der Anzahl

Wenn wir alle drei Teile zusammenfügen, erhalten wir diese Binärzahl:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Was wir dann von binär nach dezimal konvertieren können:

1.1499999999999999 x 2 3 (ungenau!)

Und multiplizieren Sie, um die endgültige Darstellung der Zahl anzuzeigen, mit der wir begonnen haben ( 9.2), nachdem sie als Gleitkommawert gespeichert wurde:

9.1999999999999993


Darstellen als Bruch

9.2

Nachdem wir die Zahl erstellt haben, ist es möglich, sie in einen einfachen Bruch zu rekonstruieren:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Verschieben Sie die Mantisse auf eine ganze Zahl:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

In Dezimalzahl konvertieren:

5179139571476070 x 2 3-52

Subtrahieren Sie den Exponenten:

5179139571476070 x 2 -49

Negativen Exponenten in Division umwandeln:

5179139571476070/2 49

Exponent multiplizieren:

5179139571476070/562949953421312

Was gleich ist:

9.1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

Sie können bereits sehen, dass die Mantisse nur 4-stellig ist, gefolgt von einer ganzen Reihe von Nullen. Aber lassen Sie uns durch die Schritte gehen.

Stellen Sie die binäre wissenschaftliche Notation zusammen:

1,0011 x 10 11

Verschieben Sie den Dezimalpunkt:

10011 x 10 11-100

Subtrahieren Sie den Exponenten:

10011 x 10 & supmin; ¹

Binär bis dezimal:

19 x 2 -1

Negativer Exponent zur Teilung:

19/2 1

Exponent multiplizieren:

19/2

Gleich:

9.5



Weiterführende Literatur

mhlester
quelle
1
Es gibt auch ein nettes Tutorial , das zeigt, wie man in die andere Richtung geht - wie konstruiert man bei einer dezimalen Darstellung einer Zahl das Gleitkommaäquivalent? Der Ansatz der "langen Teilung" zeigt sehr deutlich, wie Sie nach dem Versuch, die Zahl darzustellen, einen "Rest" erhalten. Sollte hinzugefügt werden, wenn Sie mit Ihrer Antwort wirklich "kanonisch" sein möchten.
Floris
1
Wenn Sie über Python und Gleitkomma sprechen, würde ich vorschlagen, zumindest das Python-Tutorial in Ihre Links aufzunehmen: docs.python.org/3.4/tutorial/floatingpoint.html Dies sollte die zentrale Anlaufstelle sein Ressource für Gleitkomma-Probleme für Python-Programmierer. Wenn es in irgendeiner Weise fehlt (und es ist fast sicher), öffnen Sie bitte ein Problem im Python-Bug-Tracker, um Updates oder Änderungen zu erhalten.
Mark Dickinson
@mhlester Wenn dies zu einem Community-Wiki wird, kannst du meine Antwort gerne in deine integrieren.
Nicu Stiurca
5
Diese Antwort sollte definitiv auch auf float-point-gui.de verlinken , da dies wahrscheinlich die beste Einführung für Anfänger ist. IMO, es sollte sogar über "Was jeder Informatiker wissen sollte ..." hinausgehen - heutzutage sind sich Leute, die Goldbergs Artikel vernünftigerweise verstehen können, dessen bereits bewusst.
Daniel Pryden
1
"Dies ist ein Beispiel für ein Verhältnis, das genau binär, aber nur ungefähr dezimal ausgedrückt werden kann." Das ist nicht wahr. Alle diese "Zahl über eine Zweierpotenz" -Verhältnisse sind dezimal. Jede Annäherung dient nur dazu, die Dezimalzahl zu verkürzen - der Einfachheit halber.
Rick Regan
29

Dies ist keine vollständige Antwort ( Mhlester hat bereits viele gute Gründe abgedeckt, die ich nicht duplizieren werde), aber ich möchte betonen, wie sehr die Darstellung einer Zahl von der Basis abhängt, in der Sie arbeiten.

Betrachten Sie den Bruch 2/3

In der guten alten Basis 10 schreiben wir es normalerweise so etwas wie

  • 0,666 ...
  • 0,666
  • 0,667

Wenn wir uns diese Darstellungen ansehen, neigen wir dazu, jede von ihnen mit dem Bruch 2/3 zu verknüpfen, obwohl nur die erste Darstellung mathematisch gleich dem Bruch ist. Die zweite und dritte Darstellung / Annäherung weisen einen Fehler in der Größenordnung von 0,001 auf, der tatsächlich viel schlimmer ist als der Fehler zwischen 9,2 und 9,1999999999999993. Tatsächlich ist die zweite Darstellung nicht einmal richtig gerundet! Trotzdem haben wir kein Problem mit 0,666 als Annäherung an die Zahl 2/3, so dass wir eigentlich kein Problem damit haben sollten, wie 9,2 in den meisten Programmen angenähert wird . (Ja, in einigen Programmen ist es wichtig.)

Zahlenbasen

Hier sind also die Zahlenbasen entscheidend. Wenn wir versuchen würden, 2/3 in Basis 3 darzustellen, dann

(2/3) 10 = 0,2 3

Mit anderen Worten, wir haben eine exakte, endliche Darstellung für dieselbe Zahl, indem wir die Basis wechseln! Das Mitnehmen ist, dass, obwohl Sie jede Zahl in jede Basis umwandeln können, alle rationalen Zahlen in einigen Basen exakte endliche Darstellungen haben, in anderen jedoch nicht .

Um diesen Punkt nach Hause zu fahren, schauen wir uns 1/2 an. Es könnte Sie überraschen, dass diese vollkommen einfache Zahl zwar eine genaue Darstellung in Basis 10 und 2 hat, jedoch eine wiederholte Darstellung in Basis 3 erfordert.

(1/2) 10 = 0,5 10 = 0,1 2 = 0,1111 ... 3

Warum sind Gleitkommazahlen ungenau?

Weil sie oft Rationalen approximieren, die in Basis 2 nicht endlich dargestellt werden können (die Ziffern wiederholen sich), und im Allgemeinen approximieren sie reelle (möglicherweise irrationale) Zahlen, die in keiner Basis in endlich vielen Ziffern darstellbar sind.

Nicu Stiurca
quelle
3
Mit anderen Worten, Basis 3 wäre perfekt für 1/3genau wie Basis 10 perfekt für 1/10. Keine der Fraktionen funktioniert in Base-2
Mhlester
2
@mhlester Ja. Und im Allgemeinen ist Base-N perfekt für jeden Bruch, dessen Nenner Noder ein Vielfaches davon ist.
Nicu Stiurca
2
Und dies ist ein Grund, warum einige numerische Werkzeugkästen verfolgen, "was durch was geteilt wurde", und dabei "unendliche Genauigkeit" für alle rationalen Zahlen beibehalten können. Genau wie Physiker ihre Gleichungen gerne bis zum letztmöglichen Moment symbolisch halten, falls sich Faktoren von πusw. aufheben.
Floris
3
@Floris Ich habe auch Fälle gesehen, in denen ein Algorithmus, der nur grundlegende Arithmetik ausführt (dh die Rationalität der Eingabe beibehält), feststellt, ob die Eingabe (wahrscheinlich) rational war, die Mathematik mit normaler Gleitkomma-Arithmetik durchführt und dann eine rationale neu schätzt Annäherung am Ende, um Rundungsfehler zu beheben. Dies geschieht insbesondere durch den Algorithmus der reduzierten Reihenebenenform von Matlab, der die numerische Stabilität enorm verbessert.
Nicu Stiurca
@SchighSchagh - interessant, das wusste ich nicht. Ich weiß, dass die numerische Stabilität in diesen Tagen der doppelten doppelten Präzision nicht ausreichend gelehrt wird. Was bedeutet, dass viele es vermissen, etwas über die Eleganz vieler schöner Algorithmen zu lernen. Ich mag Algorithmen, die ihre eigenen Fehler berechnen und korrigieren.
Floris
13

Obwohl alle anderen Antworten gut sind, fehlt noch eines:

Es ist unmöglich , irrationale Zahlen darzustellen (zB π, sqrt(2), log(3), etc.) genau!

Und deshalb werden sie eigentlich irrational genannt. Keine Menge an Bitspeicher auf der Welt würde ausreichen, um auch nur einen von ihnen aufzunehmen. Nur symbolische Arithmetik kann ihre Präzision bewahren.

Wenn Sie jedoch Ihre mathematischen Anforderungen auf rationale Zahlen beschränken würden, wäre nur das Problem der Präzision beherrschbar. Sie müssten ein Paar (möglicherweise sehr große) Ganzzahlen speichern aund bdie durch den Bruch dargestellte Zahl enthalten a/b. Alle Ihre Arithmetik müsste auf Brüchen ausgeführt werden, genau wie in der Highschool-Mathematik (z a/b * c/d = ac/bd. B. ).

Aber natürlich würden Sie noch in die gleiche Art von Schwierigkeiten geraten , wenn pi, sqrt, log, sinusw. beteiligt sind.

TL; DR

Für hardwarebeschleunigte Arithmetik kann nur eine begrenzte Anzahl rationaler Zahlen dargestellt werden. Jede nicht darstellbare Zahl wird angenähert. Einige Zahlen (dh irrational) können unabhängig vom System niemals dargestellt werden.

LumpN
quelle
4
Interessanterweise existieren irrationale Grundlagen. Phinary zum Beispiel.
Veedrac
5
irrationale Zahlen können (nur) in ihrer Basis dargestellt werden. Zum Beispiel ist pi 10 in der Basis pi
phuclv
4
Punkt bleibt gültig: Einige Zahlen können unabhängig vom System niemals dargestellt werden. Sie erhalten nichts, wenn Sie Ihre Basis ändern, da dann einige andere Zahlen nicht mehr dargestellt werden können.
LumpN
4

Es gibt unendlich viele reelle Zahlen (so viele, dass man sie nicht aufzählen kann), und es gibt unendlich viele rationale Zahlen (es ist möglich, sie aufzuzählen).

Die Gleitkomma-Darstellung ist endlich (wie alles in einem Computer), so dass es unvermeidlich ist, viele, viele, viele Zahlen darzustellen. Insbesondere können Sie mit 64 Bit nur zwischen 18.446.744.073.709.551.616 verschiedenen Werten unterscheiden (was im Vergleich zu unendlich nichts ist). Mit der Standardkonvention gehört 9.2 nicht dazu. Diejenigen, die können, haben für einige ganze Zahlen m und e die Form m.2 ^ e.


Sie könnten sich ein anderes Nummerierungssystem einfallen lassen, beispielsweise 10, bei dem 9.2 eine genaue Darstellung haben würde. Aber andere Zahlen, sagen wir 1/3, wären immer noch unmöglich darzustellen.


Beachten Sie auch, dass Gleitkommazahlen mit doppelter Genauigkeit äußerst genau sind. Sie können eine beliebige Zahl in einem sehr weiten Bereich mit bis zu 15 exakten Ziffern darstellen. Für Berechnungen des täglichen Lebens sind 4 oder 5 Ziffern mehr als ausreichend. Sie werden diese 15 nie wirklich brauchen, es sei denn, Sie möchten jede Millisekunde Ihres Lebens zählen.

Yves Daoust
quelle
1

Warum können wir 9.2 nicht im binären Gleitkomma darstellen?

Gleitkommazahlen sind (leicht vereinfacht) ein Positionsnummerierungssystem mit einer begrenzten Anzahl von Ziffern und einem beweglichen Radixpunkt.

Ein Bruch kann nur dann exakt mit einer endlichen Anzahl von Ziffern in einem Positionsnummerierungssystem ausgedrückt werden, wenn die Primfaktoren des Nenners (wenn der Bruch in seinen niedrigsten Ausdrücken ausgedrückt wird) Faktoren der Basis sind.

Die Primfaktoren von 10 sind 5 und 2, daher können wir in Basis 10 jeden Bruchteil der Form a / (2 b 5 c ) darstellen.

Andererseits ist der einzige Primfaktor von 2 2, so dass wir in Basis 2 nur Brüche der Form a / (2 b ) darstellen können.

Warum verwenden Computer diese Darstellung?

Weil es ein einfaches Format ist und für die meisten Zwecke ausreichend genau ist. Grundsätzlich der gleiche Grund, warum Wissenschaftler "wissenschaftliche Notation" verwenden und ihre Ergebnisse bei jedem Schritt auf eine angemessene Anzahl von Stellen runden.

Es wäre sicherlich möglich, ein Bruchformat mit (zum Beispiel) einem 32-Bit-Zähler und einem 32-Bit-Nenner zu definieren. Es wäre in der Lage, Zahlen darzustellen, die IEEE-Gleitkommazahlen mit doppelter Genauigkeit nicht darstellen könnten, aber es gäbe auch viele Zahlen, die in Gleitkommazahlen mit doppelter Genauigkeit dargestellt werden könnten, die in einem solchen Bruchformat mit fester Größe nicht dargestellt werden könnten.

Das große Problem ist jedoch, dass ein solches Format schwierig zu berechnen ist. Aus zwei Gründen.

  1. Wenn Sie genau eine Darstellung jeder Zahl haben möchten, müssen Sie nach jeder Berechnung den Bruch auf die niedrigsten Werte reduzieren. Das bedeutet, dass Sie für jede Operation grundsätzlich eine Berechnung des größten gemeinsamen Teilers durchführen müssen.
  2. Wenn Sie nach Ihrer Berechnung ein nicht darstellbares Ergebnis erhalten, weil der Zähler oder Nenner Sie benötigen, um das am besten darstellbare Ergebnis zu finden. Das ist nicht zivil.

Einige Sprachen bieten Brucharten an, aber normalerweise tun sie dies in Kombination mit willkürlicher Genauigkeit. Dadurch müssen Sie sich keine Gedanken über die Annäherung von Brüchen machen, aber es entsteht ein eigenes Problem, wenn eine Zahl eine große Anzahl von Berechnungsschritten mit der Größe des Nenners und durchläuft daher kann der für die Fraktion benötigte Speicher explodieren.

Einige Sprachen bieten auch dezimale Gleitkommatypen an. Diese werden hauptsächlich in Szenarien verwendet, in denen es wichtig ist, dass die Ergebnisse, die der Computer erhält, mit bereits vorhandenen Rundungsregeln übereinstimmen, die für Menschen geschrieben wurden (hauptsächlich Finanzberechnungen). Diese sind etwas schwieriger zu bearbeiten als binäre Gleitkommazahlen, aber das größte Problem ist, dass die meisten Computer keine Hardwareunterstützung für sie anbieten.

Plugwash
quelle
-4

Versuche dies

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

' decimalValue' ist Ihr zu konvertierender Wert.

Popal
quelle