Dlaczego 1 // 0,01 == 99 w Pythonie?

31

Wyobrażam sobie, że jest to klasyczne pytanie precyzyjne zmiennoprzecinkowe, ale staram się owinąć głowę tym wynikiem, działając 1//0.01w wydajności Pythona 3.7.5 99.

Wyobrażam sobie, że jest to oczekiwany wynik, ale czy jest jakiś sposób, aby zdecydować, kiedy jest bezpieczniejszy w użyciu int(1/f)niż 1//f?

Albert James Teddy
źródło
4
Tak, zawsze jest bezpieczniej int (1 / f). Po prostu dlatego, że // jest dywizją PODŁOGI, a błędnie myślisz o niej jako OKRĄGŁY.
Perdi Estaquel
2
Nie duplikat. Może to działać zgodnie z oczekiwaniami 99,99%, zawsze używając round()i nigdy //lub int(). Powiązane pytanie dotyczy porównania zmiennoprzecinkowego, które nie ma nic wspólnego ze obcięciem i nie ma takiej łatwej poprawki.
maxy

Odpowiedzi:

23

Gdyby to był podział z liczbami rzeczywistymi, 1//0.01wynosiłby dokładnie 100. Ponieważ są to przybliżenia zmiennoprzecinkowe, 0.01jest on jednak nieco większy niż 1/100, co oznacza, że ​​iloraz jest nieco mniejszy niż 100. To jest ta 99. wartość do 99.

chepner
źródło
3
Nie dotyczy to części „czy istnieje sposób, aby zdecydować, kiedy jest bezpieczniej”.
Scott Hunter,
10
„Bezpieczniejszy” nie jest dobrze zdefiniowany.
chepner
1
Wystarczająco, aby całkowicie go zignorować, szczególnie. kiedy PO jest świadomy problemów zmiennoprzecinkowych?
Scott Hunter,
3
@chepner Jeśli „bezpieczniejszy” nie jest dobrze zdefiniowany, być może lepiej poprosić o wyjaśnienie: /
2
jest dla mnie całkiem jasne, że „bezpieczniejszy” oznacza „błąd nie gorszy niż tani kieszonkowy kalkulator”
maksymalnie
9

Przyczyny tego wyniku są takie, jak podajesz i zostały wyjaśnione w Czy matematyka zmiennoprzecinkowa jest zepsuta? i wiele innych podobnych pytań i odpowiedzi.

Kiedy znasz liczbę miejsc po przecinku licznika i mianownika, bardziej wiarygodnym sposobem jest pomnożenie tych liczb w pierwszej kolejności, aby można je było traktować jako liczby całkowite, a następnie wykonać na nich dzielenie liczb całkowitych:

Więc w twoim przypadku 1//0.01należy przeliczyć najpierw na 1*100//(0.01*100)100.

W bardziej ekstremalnych przypadkach nadal można uzyskać „nieoczekiwane” wyniki. Konieczne może być dodanie roundwywołania licznika i mianownika przed wykonaniem podziału na liczby całkowite:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Ale jeśli chodzi o pracę ze stałymi liczbami dziesiętnymi (pieniądze, centy), to rozważ pracę z centami jako jednostką , aby cała arytmetyka mogła być wykonana jako arytmetyka liczb całkowitych i podczas konwersji tylko do / z głównej jednostki pieniężnej (dolara) I / O.

Lub alternatywnie użyj biblioteki dziesiętnej, na przykład dziesiętnej , która:

... zapewnia obsługę szybko poprawnie zaokrąglonej arytmetyki zmiennoprzecinkowej dziesiętnej.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100
trincot
źródło
3
„co oczywiście wynosi 100.” Niekoniecznie: jeśli .01 nie jest dokładny, to .01 * 100 nie jest tak dobrze. Musi być „dostrojony” ręcznie.
glglgl
8

Co trzeba wziąć pod uwagę to, że //jest to flooroperator i jako taki powinien najpierw pomyśleć jak jeśli mają równe prawdopodobieństwo spadku w 100 jak w 99 (*) (ponieważ operacja będzie 100 ± epsilonz epsilon>0warunkiem, że szanse na uzyskanie dokładnie 100,00 ..0 są bardzo niskie.)

Możesz zobaczyć to samo ze znakiem minus,

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

i powinieneś być (nie) zaskoczony.

Z drugiej strony int(-1/.01)wykonuje najpierw podział, a następnie stosuje int()liczbę, która nie jest podłogą, ale obcięciem w kierunku 0 ! co oznacza, że ​​w takim przypadku

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

W związku z tym,

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

Zaokrąglenie dałoby TWOIM oczekiwany wynik dla tego operatora, ponieważ znowu błąd jest niewielki dla tych liczb.

(*) Nie twierdzę, że prawdopodobieństwo jest takie samo, po prostu mówię, że a priori, kiedy wykonujesz takie obliczenia z zmienną arytmetyką, która jest oszacowaniem tego, co otrzymujesz.

myradio
źródło
7

Liczby zmiennoprzecinkowe nie mogą dokładnie reprezentować większości liczb dziesiętnych, więc po wpisaniu literału zmiennoprzecinkowego faktycznie otrzymujesz przybliżenie tego literału. Przybliżenie może być większe lub mniejsze niż wpisana liczba.

Możesz zobaczyć dokładną wartość liczby zmiennoprzecinkowej, rzutując ją na Dziesiętny lub Ułamek.

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

Możemy użyć typu Frakcja, aby znaleźć błąd spowodowany przez nasz niedokładny literał.

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

Możemy również dowiedzieć się, jak szczegółowe są liczby zmiennoprzecinkowe o podwójnej precyzji o wartości około 100, używając nextafter z numpy.

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

Na podstawie tego możemy przypuszczać, że najbliższa liczba zmiennoprzecinkowa 1/0.01000000000000000020816681711721685132943093776702880859375 to w rzeczywistości dokładnie 100.

Różnica między 1//0.01iint(1/0.01) jest zaokrąglenie. 1 // 0,01 zaokrągla dokładny wynik w dół do następnej liczby całkowitej w jednym kroku. Otrzymujemy wynik 99.

int (1 / 0,01) z drugiej strony zaokrągla w dwóch etapach, najpierw zaokrągla wynik do najbliższej liczby zmiennoprzecinkowej podwójnej precyzji (czyli dokładnie 100), a następnie zaokrągla tę liczbę zmiennoprzecinkową w dół do następnej liczby całkowitej (która jest ponownie dokładnie 100).

płyn do płukania
źródło
Nazywanie tego zaokrągleniem jest mylące. Należy to nazwać skróceniem lub zaokrągleniem do zera : int(0.9) == 0iint(-0.9) == 0
maksymalnie
To binarne typy zmiennoprzecinkowe, o których tu mówisz. (Istnieją również dziesiętne typy zmiennoprzecinkowe.)
Stephen C
3

Jeśli wykonasz następujące czynności

from decimal import *

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

Dane wyjściowe będą:

99.99999999999999791833182883

W ten sposób jest on reprezentowany wewnętrznie, więc zaokrąglenie w dół //da99

Deszcz
źródło
2
Jest wystarczająco dokładny, aby pokazać błąd w tym przypadku, ale należy pamiętać, że arytmetyka dziesiętna też nie jest dokładna.
plugwash
Gdy Decimal(0.01)jesteś za późno, błąd już się pojawił, zanim zadzwonisz Decimal. Nie jestem pewien, jak to jest odpowiedź na pytanie ... Musisz najpierw obliczyć dokładną wartość 0,01 Decimal(1) / Decimal(100), tak jak pokazałem w mojej odpowiedzi.
trincot
@trincot Moja odpowiedź brzmi: pytanie „Dlaczego 1 // 0,01 == 99” Próbowałem pokazać PO, jak liczby zmiennoprawne są traktowane wewnętrznie.
Deszcz