Ich weiß, dass die meisten Dezimalstellen keine exakte Gleitkommadarstellung haben ( Ist die Gleitkomma-Mathematik gebrochen? ).
Aber ich verstehe nicht, warum gut 4*0.1
gedruckt wird 0.4
, aber 3*0.1
nicht, wenn beide Werte tatsächlich hässliche Dezimaldarstellungen haben:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
0.3000000000000000444089209850062616169452667236328125
wie0.30000000000000004
und0.40000000000000002220446049250313080847263336181640625
wie.4
auch wenn sie die gleiche Genauigkeit zu haben scheinen, und damit die Frage nicht beantworten.Antworten:
Die einfache Antwort ist, weil
3*0.1 != 0.3
aufgrund eines Quantisierungsfehlers (Rundungsfehlers) (während das4*0.1 == 0.4
Multiplizieren mit einer Zweierpotenz normalerweise eine "exakte" Operation ist).Sie können die
.hex
Methode in Python verwenden, um die interne Darstellung einer Zahl anzuzeigen (im Grunde genommen den exakten binären Gleitkommawert anstelle der Basis-10-Näherung). Dies kann helfen zu erklären, was unter der Haube vor sich geht.0,1 ist 0x1,999999999999a mal 2 ^ -4. Das "a" am Ende bedeutet die Ziffer 10 - mit anderen Worten, 0,1 im binären Gleitkomma ist sehr geringfügig größer als der "exakte" Wert von 0,1 (da das endgültige 0x0,99 auf 0x0a aufgerundet wird). Wenn Sie dies mit 4 multiplizieren, einer Zweierpotenz, verschiebt sich der Exponent nach oben (von 2 ^ -4 auf 2 ^ -2), aber die Zahl bleibt ansonsten unverändert
4*0.1 == 0.4
.Wenn Sie jedoch mit 3 multiplizieren, vergrößert sich der kleine Unterschied zwischen 0x0,99 und 0x0a0 (0x0,07) zu einem 0x0,15-Fehler, der an der letzten Position als einstelliger Fehler angezeigt wird. Dies führt dazu, dass 0,1 * 3 geringfügig größer als der gerundete Wert von 0,3 ist.
Der Float von Python 3
repr
ist so konzipiert, dass er rund ausgelöst werden kann. Das heißt, der angezeigte Wert sollte genau in den ursprünglichen Wert konvertierbar sein. Daher kann es nicht angezeigt werden0.3
und0.1*3
genau die gleiche Art und Weise, oder die beiden unterschiedlichen Zahlen würden die gleiche nach Round-Tripping enden. Folglich zeigt die Python 3-repr
Engine eine mit einem leichten offensichtlichen Fehler an.quelle
.hex()
; ich wusste nicht, dass es existiert.)e
weil das bereits eine hexadezimale Ziffer ist. Vielleichtp
für Macht statt Exponent .p
in diesem Zusammenhang geht (zumindest) auf C99 zurück und erscheint auch in IEEE 754 und in verschiedenen anderen Sprachen (einschließlich Java). Alsfloat.hex
undfloat.fromhex
implementiert wurden (von mir :-), kopierte Python lediglich die bis dahin etablierte Praxis. Ich weiß nicht, ob die Absicht für "Power" "p" war, aber es scheint eine gute Möglichkeit zu sein, darüber nachzudenken.repr
(undstr
in Python 3) gibt so viele Ziffern aus, wie erforderlich sind, um den Wert eindeutig zu machen. In diesem Fall das Ergebnis der Multiplikation3*0.1
nicht der Wert, der 0,3 am nächsten kommt (0x1,33333333333333p-2 in hex), sondern tatsächlich ein LSB höher (0x1,3333333333333p-2), sodass mehr Ziffern erforderlich sind, um ihn von 0,3 zu unterscheiden.Auf der anderen Seite, die Multiplikation
4*0.1
hat den nächsten Wert auf 0,4 (0x1.999999999999ap-2 in hex) erhalten, so dass es keine zusätzlichen Stellen nicht brauchen nicht.Sie können dies ganz einfach überprüfen:
Ich habe oben die Hex-Notation verwendet, weil sie schön und kompakt ist und den Bitunterschied zwischen den beiden Werten zeigt. Sie können dies selbst tun, indem Sie z
(3*0.1).hex()
. Wenn Sie sie lieber in ihrer ganzen Dezimalpracht sehen möchten, können Sie loslegen:quelle
3*0.1 == 0.3
und zu veranschaulichen4*0.1 == 0.4
?Hier ist eine vereinfachte Schlussfolgerung aus anderen Antworten.
quelle
str
undrepr
für Floats identisch sind. Hat für Python 2.7repr
die Eigenschaften, die Sie identifizieren, ist aberstr
viel einfacher - es berechnet einfach 12 signifikante Ziffern und erzeugt eine darauf basierende Ausgabezeichenfolge. Für Python <= 2.6 basieren beiderepr
undstr
auf einer festen Anzahl signifikanter Ziffern (17 fürrepr
, 12 fürstr
). (Und niemand kümmert sich um Python 3.0 oder Python 3.1 :-)repr
daher wäre das Verhalten von Python 2.7 identisch ...Nicht wirklich spezifisch für die Implementierung von Python, sollte aber für alle Float-to-Decimal-String-Funktionen gelten.
Eine Gleitkommazahl ist im Wesentlichen eine Binärzahl, jedoch in wissenschaftlicher Notation mit einer festen Grenze signifikanter Zahlen.
Die Umkehrung einer Zahl mit einem Primzahlfaktor, der nicht mit der Basis geteilt wird, führt immer zu einer wiederkehrenden Punktpunktdarstellung. Zum Beispiel hat 1/7 einen Primfaktor 7, der nicht mit 10 geteilt wird, und hat daher eine wiederkehrende Dezimaldarstellung, und dasselbe gilt für 1/10 mit den Primfaktoren 2 und 5, wobei letzterer nicht mit 2 geteilt wird ;; Dies bedeutet, dass 0,1 nicht genau durch eine endliche Anzahl von Bits nach dem Punktpunkt dargestellt werden kann.
Da 0.1 keine exakte Darstellung hat, versucht eine Funktion, die die Approximation in eine Dezimalzeichenfolge konvertiert, normalerweise, bestimmte Werte zu approximieren, damit sie keine unintuitiven Ergebnisse wie 0.1000000000004121 erhalten.
Da der Gleitkomma in wissenschaftlicher Notation vorliegt, wirkt sich jede Multiplikation mit einer Potenz der Basis nur auf den Exponententeil der Zahl aus. Zum Beispiel 1.231e + 2 * 100 = 1.231e + 4 für die Dezimalschreibweise und ebenso 1.00101010e11 * 100 = 1.00101010e101 in der Binärschreibweise. Wenn ich mit einer Nicht-Potenz der Basis multipliziere, werden auch die signifikanten Ziffern beeinflusst. Zum Beispiel 1.2e1 * 3 = 3.6e1
Abhängig vom verwendeten Algorithmus wird möglicherweise versucht, gemeinsame Dezimalstellen nur anhand der signifikanten Zahlen zu erraten. Sowohl 0,1 als auch 0,4 haben die gleichen signifikanten Zahlen in binärer Form, da ihre Floats im Wesentlichen Kürzungen von (8/5) (2 ^ -4) bzw. (8/5) (2 ^ -6) sind. Wenn der Algorithmus das 8/5-Sigfig-Muster als Dezimalzahl 1,6 identifiziert, funktioniert er mit 0,1, 0,2, 0,4, 0,8 usw. Es kann auch magische Sigfig-Muster für andere Kombinationen geben, z. B. Float 3 geteilt durch Float 10 und andere magische Muster, die statistisch wahrscheinlich durch Division durch 10 gebildet werden.
Im Fall von 3 * 0,1 unterscheiden sich die letzten signifikanten Zahlen wahrscheinlich von der Division eines Floats 3 durch Float 10, was dazu führt, dass der Algorithmus die magische Zahl für die Konstante 0,3 abhängig von seiner Toleranz für Präzisionsverlust nicht erkennt.
Bearbeiten: https://docs.python.org/3.1/tutorial/floatingpoint.html
Es gibt keine Toleranz für Präzisionsverluste. Wenn float x (0,3) nicht genau gleich float y (0,1 * 3) ist, ist repr (x) nicht genau gleich repr (y).
quelle