Warum 1 // 0,01 == 99 in Python?

31

Ich stelle mir vor, dass dies eine klassische Gleitkomma-Präzisionsfrage ist, aber ich versuche, mich mit diesem Ergebnis zu beschäftigen, das 1//0.01in Python 3.7.5 ausgeführt wird 99.

Ich stelle mir vor, es ist ein erwartetes Ergebnis, aber gibt es eine Möglichkeit zu entscheiden, wann die Verwendung sicherer ist int(1/f)als 1//f?

Albert James Teddy
quelle
4
Ja, es ist immer sicherer int (1 / f). Einfach, weil // die FLOOR-Abteilung ist und Sie sie fälschlicherweise als RUND betrachten.
Perdi Estaquel
4
Mögliches Duplikat von Ist Gleitkomma-Mathematik kaputt?
pppery
2
Kein Duplikat. Dies kann wie erwartet zu 99,99% funktionieren, wenn immer round()und nie //oder verwendet wird int(). Die verknüpfte Frage zum Float-Vergleich hat nichts mit Abschneiden und keiner so einfachen Lösung zu tun.
Maxy

Antworten:

23

Wenn dies eine Division mit reellen Zahlen 1//0.01wäre , wäre dies genau 100. Da es sich jedoch um Gleitkomma-Näherungen handelt, 0.01ist sie etwas größer als 1/100, was bedeutet, dass der Quotient etwas kleiner als 100 ist. Es ist dieser Wert, der dann auf dem Boden liegt bis 99.

chepner
quelle
3
Dies betrifft nicht den Teil "Gibt es eine Möglichkeit zu entscheiden, wann es sicherer ist".
Scott Hunter
10
"Sicherer" ist nicht genau definiert.
Chepper
1
Genug, um es völlig zu ignorieren, insb. Wann sind dem OP Gleitkommaprobleme bekannt?
Scott Hunter
3
@chepner Wenn "sicherer" nicht genau definiert ist, ist es vielleicht besser, um Klarstellung zu bitten: /
2
Mir ist ziemlich klar, dass "sicherer" "Fehler nicht schlimmer als ein billiger Taschenrechner" bedeutet
maxy
9

Die Gründe für dieses Ergebnis sind wie von Ihnen angegeben und werden in Ist Gleitkomma-Mathematik gebrochen? und viele andere ähnliche Fragen und Antworten.

Wenn Sie die Anzahl der Dezimalstellen von Zähler und Nenner kennen, ist es zuverlässiger, diese Zahlen zuerst zu multiplizieren, damit sie als Ganzzahlen behandelt werden können, und dann eine Ganzzahldivision für sie durchzuführen:

In Ihrem Fall 1//0.01sollte also zuerst auf 1*100//(0.01*100)100 umgerechnet werden .

In extremeren Fällen können immer noch "unerwartete" Ergebnisse erzielt werden. Möglicherweise müssen Sie roundZähler und Nenner aufrufen, bevor Sie die Ganzzahldivision ausführen:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Wenn es jedoch darum geht, mit festen Dezimalstellen (Geld, Cent) zu arbeiten, sollten Sie in Betracht ziehen, mit Cent als Einheit zu arbeiten , damit alle Arithmetik als Ganzzahlarithmetik ausgeführt werden kann, und dabei nur in / von der Hauptwährungseinheit (Dollar) konvertieren I / O.

Oder verwenden Sie alternativ eine Bibliothek für Dezimalstellen wie Dezimalstellen , die:

... bietet Unterstützung für schnelle, korrekt gerundete Dezimal-Gleitkomma-Arithmetik.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100
Trincot
quelle
3
"was offensichtlich 100 ist." Nicht unbedingt: Wenn die .01 nicht genau ist, ist die .01 * 100 nicht so gut. Es muss manuell "abgestimmt" werden.
glglgl
8

Was Sie berücksichtigen müssen, ist, dass //es sich um den floorOperator handelt, und als solcher sollten Sie zunächst denken, dass Sie die gleiche Wahrscheinlichkeit haben, in 100 zu fallen wie in 99 (*) (da die Operation 100 ± epsilonmit epsilon>0vorausgesetzt wird, dass die Chancen genau 100,00 betragen ..0 sind extrem niedrig.)

Sie können das gleiche tatsächlich mit einem Minuszeichen sehen,

>>> 1//.01
99.0
>>> -1//.01
-100.0

und Sie sollten als (un) überrascht sein.

Auf der anderen Seite int(-1/.01)führt zuerst die Division durch und wendet dann die int()in der Zahl an, die kein Boden ist, sondern eine Kürzung in Richtung 0 ! was bedeutet, dass in diesem Fall

>>> 1/.01
100.0
>>> -1/.01
-100.0

daher,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

Eine Rundung würde jedoch das IHR erwartete Ergebnis für diesen Operator ergeben, da der Fehler für diese Zahlen wiederum gering ist.

(*) Ich sage nicht, dass die Wahrscheinlichkeit gleich ist, ich sage nur, dass a priori, wenn Sie eine solche Berechnung mit schwebender Arithmetik durchführen, eine Schätzung dessen ist, was Sie erhalten.

Myradio
quelle
7

Gleitkommazahlen können die meisten Dezimalzahlen nicht genau darstellen. Wenn Sie also ein Gleitkomma-Literal eingeben, erhalten Sie tatsächlich eine Annäherung an dieses Literal. Die Annäherung kann größer oder kleiner als die von Ihnen eingegebene Zahl sein.

Sie können den genauen Wert einer Gleitkommazahl anzeigen, indem Sie sie in Dezimal oder Bruch umwandeln.

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Wir können den Bruch-Typ verwenden, um den Fehler zu finden, der durch unser ungenaues Literal verursacht wird.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

Wir können auch herausfinden, wie detailliert Gleitkommazahlen mit doppelter Genauigkeit um 100 sind, indem wir nextafter von numpy verwenden.

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

Daraus können wir schließen, dass die nächste Gleitkommazahl 1/0.01000000000000000020816681711721685132943093776702880859375tatsächlich genau 100 ist.

Der Unterschied zwischen 1//0.01und int(1/0.01)ist die Rundung. 1 // 0,01 rundet das genaue Ergebnis in einem einzigen Schritt auf die nächste ganze Zahl ab. Wir erhalten also ein Ergebnis von 99.

int (1 / 0.01) hingegen rundet in zwei Schritten, zuerst rundet es das Ergebnis auf die nächste Gleitkommazahl mit doppelter Genauigkeit (genau 100), dann rundet es diese Gleitkommazahl auf die nächste Ganzzahl (dh) wieder genau 100).

Plugwash
quelle
Dies nur als Rundung zu bezeichnen, ist irreführend. Es sollte entweder Kürzung oder Rundung gegen Null genannt werden : int(0.9) == 0undint(-0.9) == 0
maxy
Es handelt sich um binäre Gleitkommatypen, über die Sie hier sprechen. (Es gibt auch dezimale Gleitkommatypen.)
Stephen C
3

Wenn Sie Folgendes ausführen

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

Die Ausgabe wird sein:

99.99999999999999791833182883

Auf diese Weise wird es intern dargestellt, sodass eine Abrundung //ergibt99

Regen
quelle
2
Es ist genau genug, um den Fehler in diesem Fall anzuzeigen, aber beachten Sie, dass die "Dezimal" -Arithmetik auch nicht genau ist.
Plugwash
Wenn Decimal(0.01)Sie zu spät sind, hat sich der Fehler bereits eingeschlichen, bevor Sie anrufen Decimal. Ich bin mir nicht sicher, wie dies eine Antwort auf die Frage ist ... Sie müssen zuerst eine genaue 0,01 mit berechnen Decimal(1) / Decimal(100), wie ich in meiner Antwort gezeigt habe.
Trincot
@trincot Meine Antwort ist auf die Frage im Titel "Warum 1 // 0.01 == 99". Ich habe versucht, dem OP zu zeigen, wie schwebende Zahlen intern behandelt werden.
Regen