Setiap hasil numerik membawa galat — selisih antara hasil komputer dan nilai matematis yang sesungguhnya. Galat ini tidak selalu berasal dari bug; sebagian lahir dari cara komputer menyimpan angka, sebagian lagi dari rumus hampiran yang sengaja kita pilih. Masalahnya, galat jenis berbeda menuntut penanganan berbeda: memperkecil step size justru memperbesar galat satu jenis, dan rumus yang tampak wajar bisa menghancurkan presisi secara diam-diam. Chapter ini memetakan jenis galat, mendemonstrasikan catastrophic cancellation, dan merumuskan aturan praktis menjaga akurasi.
Dua Keluarga Galat: Round-off dan Truncation #
Aturan dasarnya: round-off lahir dari pembulatan representasi 52 bit mantissa (chapter Floating Point), sedangkan truncation lahir dari rumus yang dipotong — misalnya deret Taylor yang diambil beberapa suku pertamanya.
| Jenis galat | Sumber | Ukuran tipikal | Cara mengecilkan |
|---|---|---|---|
| Round-off | pembulatan ke representasi 64 bit | sekitar eps per operasi | susun ulang rumus, kurangi jumlah operasi |
| Truncation | rumus hampiran dipotong (deret, h) | bergantung suku/h yang dibuang | tambah suku atau perkecil h |
| Cancellation | pengurangan bilangan nyaris sama | bisa 100% galat relatif | pakai identitas matematis |
| Overflow/underflow | keluar rentang realmin sampai realmax | Inf atau 0 | skala ulang variabel |
Mekanisme truncation mudah didemonstrasikan dengan hampiran sin(x) ≈ x - x^3/6 (deret Taylor dipotong dua suku):
x = 0.5;
hampiran = x - x^3/6;
eksak = sin(x);
fprintf('hampiran = %.10f\n', hampiran);
fprintf('sin(0.5) = %.10f\n', eksak);
fprintf('galat = %.3e\n', abs(hampiran - eksak));Output yang Dihasilkan:
hampiran = 0.4791666667
sin(0.5) = 0.4794255386
galat = 2.589e-04Mari kita bedah baris-baris kode di atas:
- Baris 2 (
hampiran = x - x^3/6): rumus memotong deret setelah sukux^3— sukux^5/120dan seterusnya dibuang, dan suku pertama yang dibuang itulah yang menentukan besar galat. - Baris 3 (
eksak = sin(x)):sin()internal MATLAB jauh lebih akurat daripada dua suku, sehingga layak menjadi pembanding. - Baris 4-5: galat 2.6e-04 murni truncation; menambah satu suku deret akan memperkecilnya, bukan mengganti
doubledengansingle.
Catastrophic Cancellation #
Masalah nyatanya: mengurangkan dua bilangan nyaris sama membuang hampir semua digit signifikan. Contoh klasiknya adalah menghitung (1 - cos(x)) / x^2 untuk x kecil — nilainya menuju 0.5, tetapi 1 - cos(x) mengurangkan 1 dengan bilangan yang nyaris 1. Solusinya: identitas trigonometri 1 - cos(x) = 2 * sin(x/2)^2 yang sama sekali tidak membutuhkan pengurangan:
fprintf('%8s %14s %14s %12s\n', 'x', 'buruk', 'baik', 'galat rel');
for x = [1e-2 1e-4 1e-6 1e-8 1e-10]
buruk = (1 - cos(x)) / x^2;
baik = 2 * sin(x/2)^2 / x^2; % identitas 1 - cos(x) = 2 sin^2(x/2)
rel = abs(buruk - baik) / baik;
fprintf('%8.0e %14.8f %14.8f %12.2e\n', x, buruk, baik, rel);
endOutput yang Dihasilkan:
x buruk baik galat rel
1e-02 0.49999583 0.49999583 2.89e-13
1e-04 0.50000000 0.50000000 5.24e-09
1e-06 0.50004445 0.50000000 8.89e-05
1e-08 0.00000000 0.50000000 1.00e+00
1e-10 0.00000000 0.50000000 1.00e+00Mari kita bedah baris-baris kode di atas:
- Baris 2 (
for x = [1e-2 ... 1e-10]): loop mengujixyang makin kecil; semakin kecilx, semakin dekatcos(x)ke1, semakin parah pembatalannya. - Baris 3 (
buruk = ...): untukx = 1e-8, nilaicos(x)dibulatkan menjadi tepat1, sehingga1 - cos(x)menghasilkan0dan hasil akhirnya0— galat relatif 100%. - Baris 4 (
baik = 2 * sin(x/2)^2 / x^2): identitas menghitung bilangan kecil secara langsung, tanpa pengurangan bilangan besar; hasilnya stabil0.5untuk semuax.
Fungsi Buruk vs Fungsi Baik: Rumus Kuadrat #
Rumus kuadrat (-b ± sqrt(b^2 - 4ac)) / (2a) menyimpan jebakan yang sama. Untuk persamaan x^2 - 1e8*x + 1 = 0, akar kecilnya sekitar 1e-8. Menghitungnya lewat cabang -b - sqrt(D) berarti mengurangkan dua bilangan nyaris sama; cabang yang baik memakai teorema Vieta — hasil kali akar sama dengan c/a, jadi akar kecil = c / akarBesar:
a = 1; b = -1e8; c = 1;
D = b^2 - 4*a*c;
akarBesar = (-b + sqrt(D)) / (2*a); % aman: penjumlahan sebangun
akarKecilBuruk = (-b - sqrt(D)) / (2*a); % pembatalan: bilangan nyaris sama
akarKecilBaik = c / (a * akarBesar); % Vieta: x1 * x2 = c / a
fprintf('akar besar = %.4f\n', akarBesar);
fprintf('akar kecil buruk = %.6e\n', akarKecilBuruk);
fprintf('akar kecil baik = %.6e\n', akarKecilBaik);
fprintf('galat rel buruk = %.2e\n', abs(akarKecilBuruk - akarKecilBaik) / akarKecilBaik);Output yang Dihasilkan:
akar besar = 100000000.0000
akar kecil buruk = 7.450581e-09
akar kecil baik = 1.000000e-08
galat rel buruk = 2.55e-01Mari kita bedah baris-baris kode di atas:
- Baris 4 (
akarBesar): cabang plus aman —1e8dijumlahkan dengansqrt(D)yang nyaris sama besarnya; penjumlahan sebangun tidak membuang digit. - Baris 5 (
akarKecilBuruk): cabang minus mengurangkan bilangan nyaris sama; dua puluh lima persen digit hasilnya salah. - Baris 6 (
akarKecilBaik): rumus Vieta menghitung akar kecil lewat pembagian, bukan pengurangan — akurat penuh, dan cocok dengan keluaranroots([1 -1e8 1]).
Galat Absolut vs Galat Relatif #
Dua ukuran standar: galat absolut = abs(x - xhampiran), dan galat relatif = abs(x - xhampiran) / abs(x). Galat absolut 1e-3 fatal bila nilai sebenarnya 0.01 (galat relatif 10%), tetapi sepele bila nilainya 10000 (galat relatif 1e-7). Perbandingan floating point yang sehat menggabungkan keduanya: abs(x - y) <= tolAbs + tolRel * max(abs(x), abs(y)). Versi ringkasnya untuk nilai berskala normal:
tolAbs = 1e-9;
tolRel = 1e-9;
x = 0.1 + 0.2;
y = 0.3;
fprintf('x == y -> %d\n', x == y);
fprintf('abs(x - y) = %.6e\n', abs(x - y));
fprintf('tolAbs + tolRel*x = %.6e\n', tolAbs + tolRel * x);
fprintf('dekat? -> %d\n', abs(x - y) <= tolAbs + tolRel * x);Output yang Dihasilkan:
x == y -> 0
abs(x - y) = 5.551115e-17
tolAbs + tolRel*x = 1.300000e-09
dekat? -> 1Mari kita bedah baris-baris kode di atas:
- Baris 1-2 (
tolAbs,tolRel): toleransi absolut menjaga kasus nilai dekat nol; toleransi relatif menskalakan ambang mengikuti besarnya nilai. - Baris 4-5 (
x,y):xdanysecara matematis sama-sama0.3, tetapixmembawa galat representasi dari0.1 + 0.2. - Baris 7 (
x == y): perbandingan eksak bernilai0(false) — bukti operator==terlalu kaku untuk floating point. - Baris 10 (
dekat?): uji toleransi bernilai1(true) — galat 5.55e-17 jauh di bawah ambang 1.3e-9, sehingga kedua nilai layak dianggap sama secara numerik.
Aturan Praktis Mengurangi Galat #
| Aturan | Alasan | Contoh di chapter ini |
|---|---|---|
| Hindari pengurangan bilangan nyaris sama | memicu cancellation | 1 - cos(x) |
| Tulis ulang dengan identitas matematis | menghitung nilai kecil secara langsung | 2 * sin(x/2)^2 |
| Pilih cabang rumus yang aman | penjumlahan sebangun tidak membuang digit | Vieta pada rumus kuadrat |
Bandingkan dengan toleransi, bukan == | galat pembulatan kecil pasti ada | tolAbs + tolRel * x |
| Jumlahkan dari terkecil ke terbesar | galat pembulatan menumpuk pada akumulator besar | loop total = total + 0.1 |
Berkas contoh.m pada bagian bawah halaman menjalankan seluruh demonstrasi di atas — truncation deret, tabel cancellation, rumus kuadrat, dan uji toleransi.
Ringkasan #
Galat numerik terbagi menjadi round-off (pembulatan representasi, tak terhindarkan) dan truncation (rumus dipotong, bisa diperhalus), ditambah cancellation yang menghancurkan digit saat dua bilangan nyaris sama dikurangkan. Pola bahayanya terlihat jelas pada (1 - cos(x)) / x^2 dan cabang minus rumus kuadrat — hasil runtuh justru saat input mengecil. Penawarnya adalah identitas matematis dan pemilihan cabang hitung yang menghindari pengurangan berbahaya, serta perbandingan berbasis toleransi abs(x - y) <= tolAbs + tolRel * abs(x). Dua aturan ini akan kita pakai terus di chapter metode numerik berikutnya.