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_tolrelativní 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_tolabsolutní 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.