2.39. Arbeta med flyttal¶
Flyttal ser ut som vanliga decimaltal, men några av deras beteenden överraskar läsare som inte stött på dem tidigare – och ett av dessa beteenden är mer uttalat på MicroPython än på desktop-Python. Den här sidan beskriver vad man kan förvänta sig och hur man skriver flyttalskod som inte i tysthet missköter sig.
2.39.1. Precision¶
Pythons float är ett IEEE 754 binärt flyttal. På de flesta MicroPython-byggen är det enkel precision (32-bitars), där desktop-CPython använder dubbel precision (64-bitars). Enkel precision bär ungefär sju decimalsiffrors noggrannhet; dubbel bär ungefär femton.
>>> 0.1 + 0.2
0.3000000
>>> 1.0 / 3.0
0.3333333
>>> 1e30 * 1e30
inf
Det representerbara intervallet är också smalare i båda ändar: tal vars magnitud är större än ungefär 3.4e38 flödar över till inf, och tal mindre än ungefär 1.2e-38 avrundas till noll.
2.39.2. Jämföra flyttal¶
Den vanligaste fallgropen är att testa likhet med ==:
>>> 0.1 + 0.2 == 0.3
False
Båda uttrycken ser ut som om de borde vara lika, men resultatet av 0.1 + 0.2 är det närmast representerbara värdet, vilket inte är exakt 0.3. Använd istället en toleranskontroll – fråga om två flyttal är tillräckligt nära snarare än identiska:
if abs(a - b) < 1e-6:
# close enough
...
Valet av tolerans beror på värdenas storleksordning. En fast 1e-6 fungerar bra när talen är i storleksordningen 1; en relativ tolerans är bättre när värdena varierar med flera storleksordningar.
math.isclose() hanterar båda samtidigt:
from math import isclose
isclose(0.1 + 0.2, 0.3) # True
isclose(1.0e6 + 1, 1.0e6) # True (within default tolerance)
De två nyckelordsargumenten styr vilken sorts ”nära” som räknas:
rel_tol– relativ tolerans, standard1e-9. Två värden matchar om deras skillnad ligger inom denna andel av det större. Bra för allmänna jämförelser över alla storleksordningar.abs_tol– absolut tolerans, standard0. Två värden matchar om deras skillnad ligger inom detta fasta belopp.
math.isclose() returnerar True om endera toleransen uppfylls. Standardvärdena duger för de flesta par av nollskilda tal; fällan är när ett av värdena kan vara exakt noll. Den relativa toleranskontrollen blir ”skillnad ≤ rel_tol × största värdet”, och det största värdet är noll, så kontrollen misslyckas alltid:
>>> isclose(0.0, 1e-12)
False
Den absoluta toleranskontrollen har inte detta problem – skicka in en abs_tol när noll är ett värde du kan tänkas jämföra mot:
>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True
2.39.3. Ackumuleringsdrift¶
Långa summor av flyttal förlorar precision snabbare på MicroPython än på CPython, eftersom varje mellanresultat avrundas tillbaka till 32-bitars precision:
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
För upprepade additioner där noggrannhet spelar roll hjälper två mönster:
Ackumulera i ett heltal när värdena kan skalas till heltal – arbeta i millisekunder istället för sekunder, eller millivolt istället för volt, och konvertera sedan en gång på slutet.
Beräkna i mindre satser och summera satsresultaten, så att varje addition sker mellan värden av liknande magnitud.
Heltalssidan har ingen sådan begränsning – MicroPython-heltal har godtycklig precision, precis som CPythons. När du har valet, föredra heltalsaritmetik för allt där precisionsförlusten skulle ackumuleras.