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.2 dan galat relatif
  • Perbandingan toleransi abs(a-b) < tol dan ismembertol
  • Galat kumulatif pada loop ribuan iterasi, single vs double

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:

matlab
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:

text
0.1+0.2 (20 digit) : 0.30000000000000004441
selisih dengan 0.3 : 0.00000000000000005551
tepat sama?        : 0

Dengan 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):

matlab
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:

text
eps(1)             : 2.2204460492503131e-16
eps(2)             : 4.4408920985006262e-16
eps(0) (denormal)  : 4.9406564584124654e-324
1 + eps/2 == 1     : 1
1 + eps   > 1      : 1

Dua 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:

matlab
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:

text
toleransi lolos    : 1
galat relatif      : 1.850e-16
ismembertol        : 1

Galat 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:

matlab
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:

text
loop 100 x 0.1     : 1010.0000000000023
1000 + 100*0.1     : 1010

Rekening 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:

matlab
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:

text
eps single         : 1.19209e-07
double 0.1+0.2==0.3: 0
single 0.1+0.2==0.3: 1

Hasil 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.

Aspekdouble (default)single
Penyimpanan8 byte4 byte
Digit signifikan~15–17~7
eps di sekitar 12.22×10⁻¹⁶1.19×10⁻⁷
Kapan dipilihdefault semua komputasiarray 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.