Float64 menyimpan sekitar 15-16 angka signifikan, sedangkan float32 hanya sekitar 7-8. Hemat memori setengahnya membuat float32 menarik untuk data besar, tetapi presisi yang lebih pendek memunculkan efek yang tidak terlihat pada array kecil: penjumlahan jutaan nilai bisa meleset terlihat jelas. Bab ini mengamati efek tersebut secara langsung dan menutup section dengan praktik menjaga akurasi.

float64 vs float32 dalam Akumulasi Besar #

Kita jumlahkan satu juta salinan nilai 0.1 — nilai yang sebenarnya tidak dapat direpresentasikan persis dalam biner — dengan tiga cara: di akumulasi float64, di akumulasi float32, serta nilai float32 yang diakumulasi kembali dalam float64:

python
import numpy as np

data = np.full(1_000_000, 0.1)
print(np.sum(data))
print(np.sum(data, dtype=np.float32))
data32 = data.astype(np.float32)
print(np.sum(data32, dtype=np.float64))

Output yang Dihasilkan:

text
99999.9999999998
100000.086
100000.00149011612

Mari kita bedah ketiga hasil:

  1. np.sum(data): array float64 dengan akumulasi float64 menghasilkan 99999.9999999998 — meleset sekitar 2x10^-10 dari satu juta, tingkat kesalahan yang wajar untuk presisi float64.
  2. np.sum(data, dtype=np.float32): parameter dtype memaksa akumulasi dilakukan dalam float32; kesalahan pembulatan menumpuk hingga 100000.086, meleset 0.086 dari nilai sebenarnya.
  3. np.sum(data32, dtype=np.float64): array float32 diakumulasi dalam float64, sehingga hasilnya memperlihatkan penyimpangan murni dari konversi 0.1 ke float32: 100000.00149011612.

Tabel berikut merangkum perbedaan kedua tipe:

Aspekfloat32float64
Jumlah bit3264
Angka signifikan (kira-kira)7-815-16
Hasil jumlah 1 juta x 0.1100000.08699999.9999999998
Penggunaan lazimGrafika, model ML, citraPerhitungan ilmiah dan keuangan

Urutan Penjumlahan Juga Memengaruhi Hasil #

Presisi bukan satu-satunya faktor; urutan operasi ikut menentukan. Penjumlahan berurutan kiri-ke-kanan menumpuk galat sepanjang jalan, sedangkan np.sum memakai algoritma pairwise summation yang mengelompokkan penjumlahan menjadi blok-blok kecil:

python
import numpy as np

data = np.full(10, 0.1)
total = 0.0
for v in data.tolist():
    total += v
print(total)
print(np.sum(data))

Output yang Dihasilkan:

text
0.9999999999999999
1.0

Loop menjumlahkan sepuluh nilai 0.1 satu per satu dan berakhir di 0.9999999999999999, sementara np.sum pada data yang sama menghasilkan 1.0 persis. Pada array besar, keunggulan pairwise summation inilah yang membuat np.sum lebih akurat dibanding akumulasi berurutan manual.

Menampilkan Hasil: repr vs Format #

Nilai hasil perhitungan dan tampilannya adalah dua hal berbeda. print menampilkan representasi terpendek yang masih merekonstruksi nilai float64 secara persis, sementara format f-string bisa menutupi atau justru membuka penyimpangan:

python
import numpy as np

total = np.sum(np.full(1_000_000, 0.1))
print(total)
print(f"{total:.2f}")
print(f"{total:.15f}")

Output yang Dihasilkan:

text
99999.9999999998
100000.00
99999.999999999796273

Format :.2f menampilkan 100000.00 — tampilan yang rapi untuk laporan, tetapi menyembunyikan selisih dari angka bulat. Sebaliknya :.15f membuka nilai penuh 99999.999999999796273. Praktiknya: format hanya pada lapisan tampilan, dan biarkan nilai mentah tetap dipakai untuk perhitungan lanjutan.

Kapan float32 Cukup, Kapan Menjadi Risiko #

float32 bukan pilihan yang salah; pilihannya bergantung pada jenis perhitungan:

  • Aman memakai float32: piksel dan tekstur grafika, bobot model machine learning yang dipakai untuk inferensi, sinyal per blok pendek, dan data citra — rentang nilainya terkendali dan galat kecil tidak mengumpul.
  • Risiko memakai float32: akumulasi keuangan (total tagihan jutaan transaksi), penjumlahan berjalan (running total) sepanjang waktu, selisih dua nilai yang hampir sama, dan data yang terus ditambahkan tanpa reset.

Panduan praktis menjaga akurasi:

  1. Untuk akumulasi besar, simpan data dalam float32 bila memori terbatas tetapi akumulasi dengan dtype=np.float64.
  2. Bandingkan bilangan pecahan dengan np.isclose atau np.allclose, bukan dengan ==.
  3. Format angka hanya saat menampilkan; jangan membulatkan data yang masih dipakai untuk menghitung.
  4. Saat mengurangi dua nilai yang hampir sama, pertimbangkan menghitung selisihnya di float64 sejak awal.

Dengan kebiasaan ini, presisi terbatas menjadi pertukaran yang terkendali, bukan sumber hasil yang mengejutkan.