2.39. Bekerja dengan float

Bilangan floating-point terlihat seperti desimal biasa, tetapi beberapa perilakunya mengejutkan pembaca yang belum pernah menemukannya sebelumnya -- dan salah satu perilaku tersebut lebih terasa pada MicroPython dibandingkan Python desktop. Halaman ini mencakup apa yang diharapkan dan cara menulis kode float yang tidak berperilaku buruk secara diam-diam.

2.39.1. Presisi

Python float adalah bilangan floating-point biner IEEE 754. Pada sebagian besar build MicroPython, presisinya adalah single precision (32-bit), sedangkan CPython desktop menggunakan double precision (64-bit). Single precision membawa sekitar tujuh digit desimal akurasi; double membawa sekitar lima belas.

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

Rentang yang dapat direpresentasikan juga lebih sempit di kedua ujungnya: angka yang lebih besar dari sekitar 3.4e38 dalam besaran meluap ke inf, dan angka yang lebih kecil dari sekitar 1.2e-38 dibulatkan ke nol.

2.39.2. Membandingkan float

Kesalahan paling umum adalah menguji kesetaraan dengan ==:

>>> 0.1 + 0.2 == 0.3
False

Kedua ekspresi terlihat seolah seharusnya sama, tetapi hasil dari 0.1 + 0.2 adalah nilai terdekat yang dapat direpresentasikan, yang tidak sama persis dengan 0.3. Gunakan pemeriksaan toleransi sebagai gantinya -- tanyakan apakah dua float cukup dekat daripada identik:

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

Pilihan toleransi tergantung pada skala nilai. Toleransi tetap 1e-6 bekerja dengan baik ketika angka-angka berada di sekitar orde 1; toleransi relatif lebih baik ketika nilai bervariasi berdasarkan orde besaran.

math.isclose() menangani keduanya sekaligus:

from math import isclose

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

Dua argumen kata kunci mengontrol jenis "dekat" mana yang dihitung:

  • rel_tol -- toleransi relatif, default 1e-9. Dua nilai cocok jika perbedaannya berada dalam fraksi ini dari yang lebih besar. Baik untuk perbandingan umum di seluruh skala.

  • abs_tol -- toleransi absolut, default 0. Dua nilai cocok jika perbedaannya berada dalam jumlah tetap ini.

math.isclose() mengembalikan True jika salah satu toleransi terpenuhi. Default-nya baik untuk sebagian besar pasangan bilangan bukan nol; jebakannya adalah ketika salah satu nilai bisa persis nol. Pemeriksaan toleransi-relatif menghasilkan "difference ≤ rel_tol × nilai terbesar", dan nilai terbesar adalah nol, sehingga pemeriksaan selalu gagal:

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

Pemeriksaan toleransi-absolut tidak memiliki masalah tersebut -- berikan abs_tol kapan pun nol adalah nilai yang mungkin Anda bandingkan:

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

2.39.3. Penyimpangan akumulasi

Penjumlahan panjang float kehilangan presisi lebih cepat pada MicroPython dibandingkan CPython, karena setiap hasil antara dibulatkan kembali ke presisi 32-bit:

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

print(total)        # noticeably off from 100000.0

Untuk penambahan berulang di mana akurasi penting, dua pola membantu:

  • Akumulasi ke dalam integer kapan pun nilainya dapat diskalakan ke integer -- bekerja dalam milidetik daripada detik, atau milivolt daripada volt, lalu konversikan sekali di akhir.

  • Hitung dalam batch lebih kecil dan jumlahkan hasil batch, sehingga setiap penambahan berada di antara nilai-nilai dengan besaran yang serupa.

Sisi integer tidak memiliki batasan seperti itu -- integer MicroPython memiliki presisi arbitrer, seperti CPython. Di mana Anda memiliki pilihan, utamakan aritmatika integer untuk hal-hal di mana kehilangan presisi akan bertambah.