2.39. Rad s brojevima s pomičnim zarezom

Brojevi s pomičnim zarezom izgledaju kao obični decimalni brojevi, ali nekoliko njihovih ponašanja iznenadi čitatelje koji se s njima dosad nisu susreli – a jedno od tih ponašanja izraženije je na MicroPythonu nego na stolnom Pythonu. Ova stranica pokriva što očekivati i kako napisati kod s pomičnim zarezom koji se ne ponaša pogrešno u tišini.

2.39.1. Preciznost

Pythonov float jest binarni broj s pomičnim zarezom prema standardu IEEE 754. Na većini MicroPython verzija on je jednostruke preciznosti (32-bitni), dok stolni CPython koristi dvostruku preciznost (64-bitnu). Jednostruka preciznost nosi otprilike sedam decimalnih znamenki točnosti; dvostruka nosi otprilike petnaest.

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

Raspon koji se može prikazati također je uži na oba kraja: brojevi veći od približno 3.4e38 po veličini prelijevaju se u inf, a brojevi manji od približno 1.2e-38 zaokružuju se na nulu.

2.39.2. Uspoređivanje brojeva s pomičnim zarezom

Najčešća zamka jest provjera jednakosti pomoću ==:

>>> 0.1 + 0.2 == 0.3
False

Oba izraza izgledaju kao da bi trebali biti jednaki, ali rezultat 0.1 + 0.2 jest najbliža vrijednost koja se može prikazati, a to nije točno 0.3. Umjesto toga koristite provjeru tolerancije – pitajte jesu li dva broja dovoljno blizu, a ne jesu li identična:

if abs(a - b) < 1e-6:
    # close enough
    ...

Izbor tolerancije ovisi o veličini vrijednosti. Fiksna 1e-6 dobro radi kada su brojevi reda veličine 1; relativna tolerancija bolja je kada vrijednosti variraju za redove veličine.

math.isclose() rješava oboje odjednom:

from math import isclose

isclose(0.1 + 0.2, 0.3)         # True
isclose(1.0e6 + 1, 1.0e6)       # True (within default tolerance)

Dva imenovana argumenta upravljaju time koja vrsta „blizine” se računa:

  • rel_tolrelativna tolerancija, zadano 1e-9. Dvije vrijednosti se podudaraju ako je njihova razlika unutar tog udjela veće od njih. Dobro za opće usporedbe na bilo kojoj skali.

  • abs_tolapsolutna tolerancija, zadano 0. Dvije vrijednosti se podudaraju ako je njihova razlika unutar tog fiksnog iznosa.

math.isclose() vraća True ako je zadovoljena bilo koja od tolerancija. Zadane vrijednosti odgovaraju većini parova brojeva različitih od nule; zamka je kada jedna od vrijednosti može biti točno nula. Provjera relativne tolerancije svodi se na „razlika ≤ rel_tol × najveća vrijednost”, a najveća vrijednost je nula, pa provjera uvijek ne uspijeva:

>>> isclose(0.0, 1e-12)
False

Provjera apsolutne tolerancije nema takav problem – proslijedite abs_tol kad god je nula vrijednost s kojom biste mogli uspoređivati:

>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True

2.39.3. Akumulacijsko odstupanje

Dugi zbrojevi brojeva s pomičnim zarezom gube preciznost brže na MicroPythonu nego na CPythonu, jer se svaki međurezultat zaokružuje natrag na 32-bitnu preciznost:

total = 0.0
for _ in range(1000000):
    total += 0.1

print(total)        # noticeably off from 100000.0

Za ponovljena zbrajanja gdje je točnost važna pomažu dva obrasca:

  • Akumulirajte u cijeli broj kad god se vrijednosti mogu skalirati na cijele brojeve – radite u milisekundama umjesto u sekundama, ili u milivoltima umjesto u voltima, a zatim pretvorite jednom na kraju.

  • Računajte u manjim serijama i zbrojite rezultate serija, tako da je svako zbrajanje između vrijednosti slične veličine.

Cjelobrojna strana nema takvo ograničenje – MicroPython cijeli brojevi proizvoljne su preciznosti, baš kao i CPythonovi. Gdje imate izbor, dajte prednost cjelobrojnoj aritmetici za sve gdje bi se gubitak preciznosti gomilao.