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_tolrelativ tolerans, standard 1e-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_tolabsolut tolerans, standard 0. 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.