2.39. Munka lebegőpontos számokkal¶
A lebegőpontos számok hétköznapi tizedes törteknek tűnnek, de néhány viselkedésük meglepi azokat az olvasókat, akik korábban nem találkoztak velük – és az egyik ilyen viselkedés hangsúlyosabb MicroPython-on, mint az asztali Python-on. Ez az oldal arról szól, mire kell számítani, és hogyan írhatunk olyan lebegőpontos kódot, amely nem viselkedik csendben hibásan.
2.39.1. Pontosság¶
A Python float típusa egy IEEE 754 bináris lebegőpontos szám. A legtöbb MicroPython buildben ez egyszeres pontosságú (32 bites), míg az asztali CPython kétszeres pontosságot (64 bites) használ. Az egyszeres pontosság körülbelül hét tizedesjegynyi pontosságot hordoz; a kétszeres körülbelül tizenötöt.
>>> 0.1 + 0.2
0.3000000
>>> 1.0 / 3.0
0.3333333
>>> 1e30 * 1e30
inf
Az ábrázolható tartomány is szűkebb mindkét végén: a körülbelül 3.4e38 nagyságrendnél nagyobb számok inf értékre csordulnak túl, a körülbelül 1.2e-38 értéknél kisebb számok pedig nullára kerekednek.
2.39.2. Lebegőpontos számok összehasonlítása¶
A leggyakoribb buktató az egyenlőség tesztelése a == operátorral:
>>> 0.1 + 0.2 == 0.3
False
Mindkét kifejezésről úgy tűnik, hogy egyenlőnek kellene lennie, de a 0.1 + 0.2 eredménye a legközelebbi ábrázolható érték, amely nem pontosan 0.3. Helyette használj tűréshatár-ellenőrzést – azt kérdezd, hogy két lebegőpontos szám elég közel van-e egymáshoz, ahelyett, hogy azonosak-e:
if abs(a - b) < 1e-6:
# close enough
...
A tűréshatár megválasztása az értékek nagyságrendjétől függ. Egy rögzített 1e-6 jól működik, amikor a számok 1 körüli nagyságrendűek; egy relatív tűréshatár jobb, amikor az értékek nagyságrendekkel eltérnek.
A math.isclose() mindkettőt egyszerre kezeli:
from math import isclose
isclose(0.1 + 0.2, 0.3) # True
isclose(1.0e6 + 1, 1.0e6) # True (within default tolerance)
A két kulcsszavas argumentum szabályozza, hogy melyik fajta „közelség” számít:
rel_tol– relatív tűréshatár, alapértelmezetten1e-9. Két érték egyezik, ha különbségük a nagyobbik ezen hányadán belül van. Általános, bármilyen nagyságrendet átfogó összehasonlításokhoz jó.abs_tol– abszolút tűréshatár, alapértelmezetten0. Két érték egyezik, ha különbségük ezen a rögzített mennyiségen belül van.
A math.isclose() True értéket ad vissza, ha bármelyik tűréshatár teljesül. Az alapértelmezések megfelelőek a legtöbb nem nulla számpár esetén; a csapda akkor jön, amikor az egyik érték pontosan nulla lehet. A relatív tűréshatár-ellenőrzés azt jelenti, hogy „különbség ≤ rel_tol × legnagyobb érték”, és ha a legnagyobb érték nulla, akkor az ellenőrzés mindig megbukik:
>>> isclose(0.0, 1e-12)
False
Az abszolút tűréshatár-ellenőrzésnek nincs ilyen problémája – adj át egy abs_tol értéket, valahányszor a nulla olyan érték, amellyel összehasonlíthatsz:
>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True
2.39.3. Felhalmozódási elcsúszás¶
Lebegőpontos számok hosszú összegei MicroPython-on gyorsabban veszítenek pontosságukból, mint CPython-on, mert minden köztes eredmény visszakerekül 32 bites pontosságra:
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
Ismétlődő összeadásoknál, ahol a pontosság számít, két minta segít:
Halmozz egész számba, valahányszor az értékek egész számokká skálázhatók – dolgozz ezredmásodpercben másodperc helyett, vagy millivoltban volt helyett, majd egyszer, a végén alakítsd át.
Számolj kisebb csoportokban, és összegezd a csoportok eredményeit, hogy minden összeadás hasonló nagyságrendű értékek között történjen.
Az egész számok oldalán nincs ilyen korlát – a MicroPython egész számai tetszőleges pontosságúak, akárcsak a CPython-éi. Ahol választhatsz, részesítsd előnyben az egész számokkal végzett aritmetikát mindenhol, ahol a pontosságvesztés halmozódna.