Pendahuluan

Praktikum P1 membahas Representasi & Analisis Galat dalam Komputasi Numerik. Praktikum ini digunakan untuk melihat secara langsung bagaimana round-off error muncul dalam komputasi menggunakan R.

Tujuan praktikum adalah memahami pengaruh keterbatasan representasi bilangan, membandingkan absolute error dan relative error, serta menggunakan fungsi sederhana untuk memeriksa angka signifikan.


Aktivitas 1 — Urutan Penjumlahan Ternyata Berpengaruh

Sebelum menjalankan kode, diperkirakan apakah a dan b akan bernilai sama.

a <- 1 + 1e-16 + 1e-16 + 1e-16 +
  1e-16 + 1e-16 + 1e-16 + 1e-16
print(a, digits = 17)
## [1] 1
b <- 1e-16 + 1e-16 + 1e-16 + 1e-16 +
  1e-16 + 1e-16 + 1e-16 + 1
print(b, digits = 17)
## [1] 1.0000000000000007
identical(a, b)
## [1] FALSE

Pertanyaan Refleksi 1

Jelaskan dengan bahasamu sendiri kenapa a dan b berbeda, padahal secara matematis keduanya menjumlahkan bilangan yang sama persis.

Nilai a dan b dapat berbeda karena komputer menggunakan representasi floating-point yang memiliki ketelitian terbatas. Pada a, bilangan 1e-16 ditambahkan satu per satu ke angka 1, sehingga perubahan yang sangat kecil dapat hilang akibat pembulatan. Pada b, bilangan-bilangan kecil dijumlahkan terlebih dahulu sebelum ditambahkan ke angka 1. Akibatnya, urutan penjumlahan dapat memengaruhi hasil akhir.

Pertanyaan Refleksi 2

Coba modifikasi kode di atas: ganti 1e-16 menjadi 1e-10. Apakah hasilnya masih berbeda? Kenapa?

Jika 1e-16 diganti menjadi 1e-10, pengaruh bilangan kecil lebih mudah terlihat karena 1e-10 jauh lebih besar daripada 1e-16. Dengan demikian, pembulatan tidak menghilangkan pengaruh bilangan tersebut dengan cara yang sama seperti pada kasus 1e-16.


Aktivitas 2 — Menghitung Limit yang “Rusak”

Secara matematis,

\[ f(x)=\frac{e^x-e^{-x}}{x} \]

seharusnya mendekati 2 ketika x mendekati 0.

f <- function(x) (exp(x) - exp(-x)) / x
xs <- 10^-(1:16)
hasil <- data.frame(x = xs, f_x = sapply(xs, f))
print(hasil, digits = 12)
##        x           f_x
## 1  1e-01 2.00333500040
## 2  1e-02 2.00003333350
## 3  1e-03 2.00000033333
## 4  1e-04 2.00000000333
## 5  1e-05 2.00000000002
## 6  1e-06 1.99999999995
## 7  1e-07 1.99999999895
## 8  1e-08 1.99999998785
## 9  1e-09 2.00000005446
## 10 1e-10 2.00000016548
## 11 1e-11 2.00000016548
## 12 1e-12 2.00006677886
## 13 1e-13 1.99951166735
## 14 1e-14 1.99840144433
## 15 1e-15 2.10942374679
## 16 1e-16 1.11022302463
# Bandingkan dengan bentuk yang setara secara aljabar:
g <- function(x) 2 + x^2 / 3
hasil$g_x <- sapply(xs, g)
print(hasil, digits = 12)
##        x           f_x           g_x
## 1  1e-01 2.00333500040 2.00333333333
## 2  1e-02 2.00003333350 2.00003333333
## 3  1e-03 2.00000033333 2.00000033333
## 4  1e-04 2.00000000333 2.00000000333
## 5  1e-05 2.00000000002 2.00000000003
## 6  1e-06 1.99999999995 2.00000000000
## 7  1e-07 1.99999999895 2.00000000000
## 8  1e-08 1.99999998785 2.00000000000
## 9  1e-09 2.00000005446 2.00000000000
## 10 1e-10 2.00000016548 2.00000000000
## 11 1e-11 2.00000016548 2.00000000000
## 12 1e-12 2.00006677886 2.00000000000
## 13 1e-13 1.99951166735 2.00000000000
## 14 1e-14 1.99840144433 2.00000000000
## 15 1e-15 2.10942374679 2.00000000000
## 16 1e-16 1.11022302463 2.00000000000

Pertanyaan Refleksi 3

Pada nilai x berapa hasil f(x) mulai terlihat ‘rusak’ (menjauh dari 2)? Mengapa ini terjadi (kaitkan dengan pengurangan dua bilangan yang hampir sama)?

Hasil f(x) mulai terlihat tidak stabil ketika x semakin kecil, terutama sekitar 10^-8 dan menjadi semakin jelas pada nilai yang lebih kecil. Hal ini terjadi karena exp(x) dan exp(-x) mempunyai nilai yang sangat dekat ketika x mendekati nol. Ketika dua bilangan yang hampir sama tersebut dikurangkan, sebagian ketelitian numerik dapat hilang. Akibatnya, round-off error menjadi lebih terlihat.

Pertanyaan Refleksi 4

Bandingkan kolom f_x dan g_x pada x yang sangat kecil (misalnya 1e-15). Mana yang lebih stabil, dan kenapa reformulasi aljabar bisa membantu?

Pada nilai x yang sangat kecil, g(x) lebih stabil dibandingkan f(x). Reformulasi aljabar membantu karena g(x) tidak melakukan pengurangan langsung terhadap dua bilangan yang hampir sama. Oleh karena itu, kehilangan ketelitian akibat pengurangan tersebut dapat dikurangi.


Aktivitas 3 — Deret Harmonik yang “Berhenti”

Secara matematis, deret harmonik

\[ \sum_{n=1}^{\infty}\frac{1}{n} \]

divergen, sehingga tidak benar-benar berhenti bertambah.

Kode berikut merupakan kode praktikum dan dipertahankan sesuai soal.

total <- 0; n <- 0
repeat {
  n <- n + 1
  term <- 1 / n
  if (total + term == total) break # suku sudah 'hilang'
  total <- total + term
}
cat('Berhenti pada n =', n, '\n')
cat('Total akhir =', total, '\n')

Catatan: blok di atas sengaja menggunakan eval=FALSE agar proses Knit tidak terhenti sangat lama. Kode R di dalamnya tidak diubah. Jika dijalankan langsung di R Console/RStudio, program membutuhkan jumlah iterasi yang sangat besar sebelum kondisi total + term == total tercapai.

Pertanyaan Refleksi 5

Apakah program di atas benar-benar menghitung ∞? Jika tidak, di angka berapa ia ‘membeku’, dan mengapa?

Program tersebut tidak benar-benar menghitung sampai tak hingga. Program berhenti ketika nilai 1/n sudah sangat kecil sehingga ketika ditambahkan ke total, hasilnya tidak mengalami perubahan lagi. Jadi, program “membeku” bukan karena deret harmonik benar-benar berhenti, tetapi karena komputer mempunyai ketelitian floating-point yang terbatas sehingga perubahan yang sangat kecil tidak lagi dapat direpresentasikan pada nilai total.

Pertanyaan Refleksi 6

Hubungkan hasil ini dengan Aktivitas 1 — konsep apa yang sama-sama berperan di kedua kasus?

Konsep yang sama dengan Aktivitas 1 adalah keterbatasan representasi bilangan floating-point dan round-off error. Pada Aktivitas 1, bilangan yang sangat kecil dapat kehilangan pengaruh ketika dijumlahkan dengan bilangan yang jauh lebih besar. Pada Aktivitas 3, suku 1/n yang semakin kecil akhirnya tidak memberikan perubahan pada total. Keduanya menunjukkan bahwa hasil komputasi komputer dapat berbeda dari perilaku matematika ideal karena ketelitian mesin terbatas.


Aktivitas 4 — Membuat Fungsi Absolute & Relative Error

Pada aktivitas ini dibuat fungsi untuk menghitung absolute error dan relative error.

absolute_error <- function(p, p_star) abs(p - p_star)
relative_error <- function(p, p_star) abs(p - p_star) / abs(p)
pasangan <- list(
  c(p = pi, p_star = 3.14),
  c(p = exp(1), p_star = 2.71828),
  c(p = 0.0005, p_star = 0.00045)
)
for (pr in pasangan) {
  cat(sprintf('p=%.6f p*=%.6f | abs=%.3e rel=%.3e\n',
  pr['p'], pr['p_star'],
  absolute_error(pr['p'], pr['p_star']),
  relative_error(pr['p'], pr['p_star'])))
}
## p=3.141593 p*=3.140000 | abs=1.593e-03 rel=5.070e-04
## p=2.718282 p*=2.718280 | abs=1.828e-06 rel=6.727e-07
## p=0.000500 p*=0.000450 | abs=5.000e-05 rel=1.000e-01

Pertanyaan Refleksi 7

Dari tiga pasangan di atas, mana yang punya absolute error terkecil? Mana yang punya relative error terkecil? Apakah dua urutan itu sama?

Dari tiga pasangan tersebut, absolute error terkecil dimiliki oleh pasangan p = exp(1) dan p* = 2.71828, yaitu sekitar 1.828 × 10^-6.

Relative error terkecil juga dimiliki oleh pasangan tersebut, yaitu sekitar 6.727 × 10^-7.

Jadi, pada tiga pasangan yang diberikan, urutan berdasarkan absolute error terkecil dan relative error terkecil adalah sama.

Pertanyaan Refleksi 8

Tambahkan satu pasangan (p, p*) buatanmu sendiri di mana absolute error KECIL tapi relative error BESAR. Jelaskan kenapa itu mungkin terjadi.

Contoh pasangan buatan sendiri:

p = 0.000001 dan p* = 0

Absolute error:

\[ |0.000001-0|=0.000001 \]

Relative error:

\[ \frac{|0.000001-0|}{|0.000001|}=1 \]

Absolute error terlihat kecil karena selisih kedua nilai hanya 0.000001. Namun relative error menjadi besar karena error tersebut dibandingkan dengan nilai p yang juga sangat kecil. Jadi, absolute error yang kecil tidak selalu berarti relative error juga kecil.


Aktivitas 5 — Fungsi Pengecek Angka Signifikan

Fungsi berikut digunakan untuk mencari t (angka signifikan) terbesar yang memenuhi:

\[ \frac{|p-p^*|}{|p|}\leq5\times10^{-t} \]

sig_digits <- function(p, p_star, t_max = 15) {
  rel <- abs(p - p_star) / abs(p)
  t <- 0
  for (k in 1:t_max) {
    if (rel <= 5 * 10^(-k)) t <- k else break
  }
  t
}
sig_digits(pi, 3.14) # bandingkan dengan hitungan manualmu
## [1] 3
sig_digits(pi, 3.1416)
## [1] 6
sig_digits(1234.5, 1234.0)
## [1] 4

Pertanyaan Refleksi 9

Jalankan sig_digits(pi, 3.14) — apakah hasilnya sama dengan Contoh Dasar yang kita hitung manual di kelas?

Hasil sig_digits(pi, 3.14) adalah 3. Jadi berdasarkan fungsi yang diberikan pada praktikum, pasangan pi dan 3.14 memenuhi syarat untuk 3 angka signifikan.

Pertanyaan Refleksi 10

Gunakan fungsi ini untuk memeriksa jawabanmu pada Contoh Menantang (kasus batas 4.9×10−4 vs 5.1×10−4) dari sesi Jenis-Jenis Error.

Untuk kasus batas 4.9 × 10^-4 dan 5.1 × 10^-4, nilai batasnya adalah 5 × 10^-4.

Karena:

\[ 4.9\times10^{-4}\leq5\times10^{-4} \]

maka 4.9 × 10^-4 masih memenuhi syarat.

Sedangkan:

\[ 5.1\times10^{-4}>5\times10^{-4} \]

maka 5.1 × 10^-4 tidak memenuhi syarat pada tingkat tersebut.

Hal ini menunjukkan bahwa nilai yang berada di bawah atau tepat pada batas masih memenuhi kriteria, sedangkan nilai yang melewati batas tidak memenuhi kriteria.


Kesimpulan

Dari praktikum ini dapat dilihat bahwa hasil perhitungan komputer tidak selalu sama persis dengan hasil matematika ideal. Keterbatasan representasi floating-point dapat menyebabkan round-off error, terutama ketika bilangan yang sangat kecil berinteraksi dengan bilangan yang jauh lebih besar atau ketika dua bilangan yang hampir sama dikurangkan.

Selain itu, absolute error dan relative error memberikan cara yang berbeda untuk melihat besarnya kesalahan. Relative error menjadi penting ketika nilai sebenarnya sangat kecil karena error yang secara absolut kecil dapat menjadi relatif besar.

Praktikum ini juga menunjukkan pentingnya memilih bentuk perhitungan yang lebih stabil secara numerik, seperti pada reformulasi fungsi di Aktivitas 2.