Coba hitung 0.1 + 0.2 di MATLAB, lalu bandingkan dengan 0.3 — jawabannya: tidak sama. Bukan bug, bukan kesalahan MATLAB: seluruh angka desimal kita disimpan sebagai pecahan biner IEEE 754, dan 0.1 tidak punya representasi biner yang tepat, sama seperti 1/3 tidak punya desimal yang tepat. Bab ini membongkar mekanisme tersebut supaya galat-galat kecil tidak lagi terasa seperti keajaiban: dari eps (jarak antar angka), perbandingan berbasis toleransi, galat kumulatif, sampai pilihan single vs double. Cakupan chapter ini:
- IEEE 754 double dan mantissa 53-bit
eps: jarak ke angka terdekat berikutnya- Masalah klasik
0.1 + 0.2dan galat relatif - Perbandingan toleransi
abs(a-b) < toldanismembertol - Galat kumulatif pada loop ribuan iterasi,
singlevsdouble
IEEE 754 double: 53-bit Mantissa #
Aturannya: setiap double MATLAB disimpan sebagai tanda + eksponen + mantissa 53-bit (52 bit tersimpan + 1 bit tersembunyi) — sekitar 15–17 digit desimal signifikan. Mekanismenya: angka biner dibangun dari pecahan 1/2, 1/4, 1/8, …; nilai seperti 0.5 atau 0.75 jatuh persis di kisi ini, tetapi 0.1 harus dibulatkan ke pecahan biner terdekat — selisih pembulatannya itulah yang menumpuk:
fprintf('0.1+0.2 (20 digit) : %.20f\n', 0.1 + 0.2);
fprintf('selisih dengan 0.3 : %.20f\n', 0.1 + 0.2 - 0.3);
fprintf('tepat sama? : %d\n', 0.1 + 0.2 == 0.3);Output yang Dihasilkan:
0.1+0.2 (20 digit) : 0.30000000000000004441
selisih dengan 0.3 : 0.00000000000000005551
tepat sama? : 0Dengan format %.20f kita bisa melihat isi sebenarnya: 0.1 + 0.2 adalah 0.30000000000000004441…, bukan 0.3. Selisihnya 5.55×10⁻¹⁷ — kira-kira 100 miliar kali lebih kecil dari 0.3, tak terlihat oleh format short, tetapi nyata bagi operator ==. Inspeksi cepat di Command Window memakai format long menampilkan 15 digit (masih menyembunyikan jejaknya); untuk membedah seperti di atas, fprintf dengan %.20f atau %.17g adalah alat yang jujur.
eps: Jarak ke Angka Berikutnya #
Aturannya: eps(x) adalah jarak dari x ke angka double terdekat berikutnya — granularitas sistem di sekitar x. Mekanismenya: kisi angka biner renggang secara proporsional; di sekitar 1 jaraknya 2.22×10⁻¹⁶, di sekitar 2 jaraknya dua kali lebih besar, dan mengecil drastis saat mendekati nol (wilayah denormal):
fprintf('eps(1) : %.16e\n', eps(1));
fprintf('eps(2) : %.16e\n', eps(2));
fprintf('eps(0) (denormal) : %.16e\n', eps(0));
fprintf('1 + eps/2 == 1 : %d\n', 1 + eps/2 == 1);
fprintf('1 + eps > 1 : %d\n', 1 + eps > 1);Output yang Dihasilkan:
eps(1) : 2.2204460492503131e-16
eps(2) : 4.4408920985006262e-16
eps(0) (denormal) : 4.9406564584124654e-324
1 + eps/2 == 1 : 1
1 + eps > 1 : 1Dua baris terakhir adalah demonstrasi pembulatan: menambahkan separuh eps ke 1 masih membulat kembali ke 1 (setengah jarak kalah dari aturan round to nearest), sedangkan menambahkan eps penuh berhasil mencapai angka berikutnya. eps juga menjadi satuan natural untuk toleransi: galat lebih kecil dari eps relatif terhadap magnitudo angka praktis tak terhindarkan.
Perbandingan dengan Toleransi #
Aturannya: jangan membandingkan hasil komputasi floating point dengan ==; bandingkan jaraknya terhadap toleransi yang relevan dengan skala data. Mekanismenya: abs(a − b) < tol menyatakan “cukup dekat menurut kebutuhan kita”, bukan “identik menurut bit”; untuk data bertoleransi relatif, MATLAB menyediakan ismembertol (tersedia sejak R2015a) yang membandingkan elemen dengan skala otomatis:
tol = 1e-12;
fprintf('toleransi lolos : %d\n', abs(0.1 + 0.2 - 0.3) < tol);
fprintf('galat relatif : %.3e\n', abs(0.1 + 0.2 - 0.3)/abs(0.3));
fprintf('ismembertol : %d\n', ismembertol(0.1 + 0.2, 0.3, 1e-12));Output yang Dihasilkan:
toleransi lolos : 1
galat relatif : 1.850e-16
ismembertol : 1Galat relatifnya hanya 1.85×10⁻¹⁶ — di level eps, artinya komputasi kita sebenarnya sebaik yang mungkin untuk double; menuntut ketepatan lebih tidak masuk akal. Toleransi 1e-12 memadai untuk sebagian besar perhitungan teknik; untuk data hasil pengukuran (galat intrinsik 10⁻³–10⁻⁵), toleransi bisa jauh lebih longgar.
Galat Kumulatif: Loop Ribuan Iterasi #
Aturannya: galat pembulatan satu operasi memang sepele, tetapi penjumlahan berulang menumpukkannya secara sistematis — 100 iterasi kecil, ribuan iterasi terlihat, jutaan iterasi bisa menyerap digit signifikan. Mekanismenya: tiap penambahan membulatkan hasil ke double terdekat, dan pembulatan berikutnya berdiri di atas hasil pembulatan sebelumnya:
bal = 1000;
for k = 1:100
bal = bal + 0.1;
end
fprintf('loop 100 x 0.1 : %.17g\n', bal);
fprintf('1000 + 100*0.1 : %.17g\n', 1000 + 100*0.1);Output yang Dihasilkan:
loop 100 x 0.1 : 1010.0000000000023
1000 + 100*0.1 : 1010Rekening yang di-top up 0.1 seratus kali berhenti di 1010.0000000000023 — meleset 2.3×10⁻¹² dari nilai ideal, karena seratus kali pembulatan menumpuk. Jalur kanan (satu perkalian 100*0.1 lalu satu penjumlahan) jatuh tepat di 1010: urutan dan bentuk operasi mengubah hasil, dan versi fewer-operasi hampir selalu lebih bersih — alasan performa tambahan untuk vectorization. Ketika akumulasi murni seperti ini memang diperlukan, cumsum juga menjumlahkan berurutan sehingga polanya serupa; untuk kebutuhan presisi tinggi, sum dengan koreksi Kahan atau tipe sym menjawab.
single vs double #
Aturannya: double memakai 64 bit (mantissa 53-bit); single memakai 32 bit (mantissa 24-bit, ~7 digit desimal) — setengah memorinya. Mekanismenya: pembulatan single jauh lebih kasar, eps('single') ≈ 1.19×10⁻⁷, sehingga galat-galat yang tak terlihat di double menjadi nyata di single — tetapi kebetulan arahnya bisa saja berbalik untuk pasangan angka tertentu:
fprintf('eps single : %g\n', eps('single'));
fprintf('double 0.1+0.2==0.3: %d\n', double(0.1) + double(0.2) == double(0.3));
fprintf('single 0.1+0.2==0.3: %d\n', single(0.1) + single(0.2) == single(0.3));Output yang Dihasilkan:
eps single : 1.19209e-07
double 0.1+0.2==0.3: 0
single 0.1+0.2==0.3: 1Hasil yang tampak paradoks: versi double gagal tes == sedangkan versi single lolos. Penyebabnya: pembulatan 0.1f + 0.2f ke kisi single kebetulan jatuh tepat pada tetangga terdekat 0.3f, sedangkan di kisi double yang lebih halus hasilnya jatuh di antara dua angka. Pelajarannya penting: kebetulan satu kasus “pas” bukan bukti presisi lebih baik — kisi yang lebih halus (double) selalu memberi galat maksimum lebih kecil.
| Aspek | double (default) | single |
|---|---|---|
| Penyimpanan | 8 byte | 4 byte |
| Digit signifikan | ~15–17 | ~7 |
eps di sekitar 1 | 2.22×10⁻¹⁶ | 1.19×10⁻⁷ |
| Kapan dipilih | default semua komputasi | array raksasa (gambar, sinyal) saat memori dan bandwidth dominan |
Ringkasan #
Double IEEE 754 menyimpan angka pada kisi biner bermantissa 53-bit; nilai desimal seperti 0.1 dibulatkan, dan galat seukuran eps ini menular ke hasil (0.1 + 0.2 ≠ 0.3). Bandingkan angka hasil komputasi dengan toleransi abs(a−b) < tol atau ismembertol, ukur kualitas lewat galat relatif, dan waspadai akumulasi ribuan operasi yang bisa menggerus digit signifikan — bentuk fewer-operasi (vectorization) membantu dua hal sekaligus. single menghemat setengah memori dengan presisi ~7 digit. Setelah aturan main numerik ini, kita siap bertemu sisi sebaliknya: matematika eksak tanpa pembulatan sama sekali — Symbolic Math.