2.39. Práce s čísly s plovoucí desetinnou čárkou¶
Čísla s plovoucí desetinnou čárkou vypadají jako běžná desetinná čísla, ale několik jejich vlastností překvapí čtenáře, kteří se s nimi dosud nesetkali – a jedna z těchto vlastností je na MicroPythonu výraznější než na desktopovém Pythonu. Tato stránka popisuje, co očekávat a jak psát kód s plovoucí desetinnou čárkou, který se nebude tiše chovat nesprávně.
2.39.1. Přesnost¶
Typ float v Pythonu je binární číslo s plovoucí desetinnou čárkou podle normy IEEE 754. Na většině sestavení MicroPythonu má jednoduchou přesnost (32 bitů), zatímco desktopový CPython používá dvojnásobnou přesnost (64 bitů). Jednoduchá přesnost nese přibližně sedm desetinných míst přesnosti; dvojnásobná zhruba patnáct.
>>> 0.1 + 0.2
0.3000000
>>> 1.0 / 3.0
0.3333333
>>> 1e30 * 1e30
inf
Reprezentovatelný rozsah je také užší na obou koncích: čísla větší v absolutní hodnotě než přibližně 3.4e38 přetečou na inf a čísla menší než přibližně 1.2e-38 se zaokrouhlí na nulu.
2.39.2. Porovnávání čísel s plovoucí desetinnou čárkou¶
Nejčastější úskalí je testování rovnosti pomocí ==:
>>> 0.1 + 0.2 == 0.3
False
Oba výrazy vypadají, jako by si měly být rovny, ale výsledek 0.1 + 0.2 je nejbližší reprezentovatelná hodnota, která není přesně 0.3. Místo toho použijte kontrolu s tolerancí – ptejte se, zda jsou dvě čísla dostatečně blízko, místo zda jsou totožná:
if abs(a - b) < 1e-6:
# close enough
...
Volba tolerance závisí na řádu hodnot. Pevná hodnota 1e-6 funguje dobře, když jsou čísla řádově kolem 1; relativní tolerance je lepší, když se hodnoty liší o řády.
math.isclose() zvládá oboje najednou:
from math import isclose
isclose(0.1 + 0.2, 0.3) # True
isclose(1.0e6 + 1, 1.0e6) # True (within default tolerance)
Dva pojmenované argumenty řídí, jaký druh „blízkosti“ se počítá:
rel_tol– relativní tolerance, výchozí1e-9. Dvě hodnoty se shodují, pokud je jejich rozdíl v rámci tohoto zlomku větší z nich. Vhodné pro obecná porovnání napříč libovolným řádem.abs_tol– absolutní tolerance, výchozí0. Dvě hodnoty se shodují, pokud je jejich rozdíl v rámci tohoto pevného množství.
math.isclose() vrací True, pokud je splněna kterákoli z tolerancí. Výchozí hodnoty jsou v pořádku pro většinu dvojic nenulových čísel; past nastává, když jedna z hodnot může být přesně nula. Kontrola relativní tolerance se redukuje na „rozdíl ≤ rel_tol × největší hodnota“, a největší hodnota je nula, takže kontrola vždy selže:
>>> isclose(0.0, 1e-12)
False
Kontrola absolutní tolerance takový problém nemá – předejte abs_tol vždy, když nula je hodnota, vůči které možná budete porovnávat:
>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True
2.39.3. Hromadění chyb¶
Dlouhé součty čísel s plovoucí desetinnou čárkou ztrácejí na MicroPythonu přesnost rychleji než na CPythonu, protože každý mezivýsledek se zaokrouhluje zpět na 32bitovou přesnost:
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
U opakovaných sčítání, kde záleží na přesnosti, pomáhají dva postupy:
Hromaďte do celého čísla, kdykoli lze hodnoty převést na celá čísla – pracujte v milisekundách místo sekund nebo v milivoltech místo voltů a poté na konci proveďte jediný převod.
Počítejte v menších dávkách a sečtěte výsledky dávek, aby každé sčítání probíhalo mezi hodnotami podobného řádu.
Strana celých čísel takové omezení nemá – celá čísla v MicroPythonu mají libovolnou přesnost, stejně jako v CPythonu. Kde máte na výběr, dávejte přednost celočíselné aritmetice u všeho, kde by se ztráta přesnosti hromadila.