Menguasai BigDecimal di Java: Perhitungan Moneter yang Akurat Tanpa Kesalahan Floating-Point

目次

1. Pengantar

Masalah Presisi dalam Perhitungan Numerik di Java

Dalam pemrograman Java, perhitungan numerik dilakukan secara harian. Misalnya, menghitung harga produk, menentukan pajak atau bunga — operasi ini diperlukan dalam banyak aplikasi. Namun, ketika perhitungan seperti itu dilakukan menggunakan tipe titik mengambang seperti float atau double, kesalahan tak terduga mungkin terjadi. Ini terjadi karena float dan double merepresentasikan nilai sebagai aproksimasi biner. Nilai seperti “0.1” atau “0.2,” yang dapat diekspresikan secara akurat dalam desimal, tidak dapat direpresentasikan secara tepat dalam biner — dan akibatnya, kesalahan kecil menumpuk.

BigDecimal Sangat Penting untuk Perhitungan Moneter atau Presisi

Kesalahan seperti itu bisa kritis di bidang seperti perhitungan moneter dan perhitungan ilmiah/teknik presisi. Misalnya, dalam perhitungan tagihan, bahkan perbedaan 1 yen bisa menyebabkan masalah kredibilitas. Di sinilah kelas BigDecimal Java unggul. BigDecimal dapat menangani angka desimal dengan presisi arbitrer dan dengan menggunakannya sebagai pengganti float atau double, perhitungan numerik dapat dilakukan tanpa kesalahan.

Apa yang Akan Anda Dapatkan Dari Artikel Ini

Dalam artikel ini, kami akan menjelaskan dasar-dasar penggunaan BigDecimal di Java, teknik lanjutan, serta kesalahan umum dan peringatan secara sistematis. Ini berguna bagi mereka yang ingin menangani perhitungan moneter secara akurat di Java atau sedang mempertimbangkan untuk mengadopsi BigDecimal dalam proyek mereka.

2. Apa Itu BigDecimal?

Gambaran Umum BigDecimal

BigDecimal adalah kelas di Java yang memungkinkan aritmatika desimal presisi tinggi. Ia termasuk dalam paket java.math dan dirancang khusus untuk perhitungan yang tidak toleran terhadap kesalahan seperti komputasi keuangan/akuntansi/pajak. Dengan float dan double Java, nilai numerik disimpan sebagai aproksimasi biner — artinya desimal seperti “0.1” atau “0.2” tidak dapat direpresentasikan secara tepat, yang menjadi sumber kesalahan. Sebaliknya, BigDecimal menyimpan nilai sebagai representasi desimal berbasis string, sehingga menekan kesalahan pembulatan dan aproksimasi.

Penanganan Angka Presisi Arbitrer

Karakteristik terbesar dari BigDecimal adalah “presisi arbitrer.” Bagian integer dan desimal secara teori dapat menangani hampir tak terbatas digit, menghindari pembulatan atau kehilangan digit karena batasan digit. Misalnya, angka besar berikut dapat ditangani secara akurat:

BigDecimal bigValue = new BigDecimal("12345678901234567890.12345678901234567890");

Dapat melakukan aritmatika sambil mempertahankan presisi seperti ini adalah kekuatan utama dari BigDecimal.

Kasus Penggunaan Utama

BigDecimal direkomendasikan dalam situasi seperti:

  • Perhitungan moneter — bunga, komputasi tarif pajak di aplikasi keuangan
  • Pemrosesan jumlah invoice / kutipan
  • Komputasi ilmiah/teknik yang memerlukan presisi tinggi
  • Proses di mana akumulasi jangka panjang menyebabkan penumpukan kesalahan

Misalnya, dalam sistem akuntansi dan perhitungan gaji — di mana perbedaan 1 yen bisa menyebabkan kerugian besar atau sengketa — presisi BigDecimal sangat penting.

3. Penggunaan Dasar BigDecimal

Cara Membuat Instance BigDecimal

Tidak seperti literal numerik normal, BigDecimal secara umum harus dibangun dari string. Ini karena nilai yang dibuat dari double atau float mungkin sudah mengandung kesalahan aproksimasi biner. Direkomendasikan (bangun dari String):

BigDecimal value = new BigDecimal("0.1");

Hindari (bangun dari double):

BigDecimal value = new BigDecimal(0.1); // may contain error

Cara Melakukan Aritmatika

BigDecimal tidak dapat digunakan dengan operator aritmatika normal (+, -, *, /). Sebaliknya, metode khusus harus digunakan. Penjumlahan (add)

BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");
BigDecimal result = a.add(b); // 12.8

Pengurangan (subtract)

BigDecimal result = a.subtract(b); // 8.2

Perkalian (multiply)

BigDecimal result = a.multiply(b); // 24.15

Pembagian (divide) dan Mode Pembulatan Pembagian memerlukan kehati-hatian. Jika tidak dapat dibagi secara merata, ArithmeticException akan terjadi kecuali mode pembulatan ditentukan.

BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 3.33

Di sini kami menentukan “2 tempat desimal” dan “pembulatan setengah ke atas.”

Pengaturan Skala dan Mode Pembulatan dengan setScale

setScale dapat digunakan untuk membulatkan ke jumlah digit yang ditentukan.

BigDecimal value = new Big BigDecimal("123.456789");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46

Nilai RoundingMode umum:

Mode NameDescription
HALF_UPRound half up (standard rounding)
HALF_DOWNRound half down
HALF_EVENBanker’s rounding
UPAlways round up
DOWNAlways round down

BigDecimal Tidak Dapat Diubah

BigDecimal adalah immutable. Artinya — metode aritmatika (add, subtract, dll.) tidak memodifikasi nilai asli — mereka mengembalikan instance baru.

BigDecimal original = new BigDecimal("5.0");
BigDecimal result = original.add(new BigDecimal("1.0"));
System.out.println(original); // still 5.0
System.out.println(result);   // 6.0

4. Penggunaan Lanjutan BigDecimal

Membandingkan Nilai: Perbedaan Antara compareTo dan equals

Di BigDecimal, ada dua cara untuk membandingkan nilai: compareTo() dan equals(), dan perilaku ini berbeda.

  • compareTo() membandingkan hanya nilai numerik (mengabaikan skala).
  • equals() membandingkan termasuk skala (jumlah digit desimal).
    BigDecimal a = new BigDecimal("10.0");
    BigDecimal b = new BigDecimal("10.00");
    
    System.out.println(a.compareTo(b)); // 0 (values are equal)
    System.out.println(a.equals(b));    // false (scale differs)
    

Poin: Untuk pemeriksaan kesetaraan numerik — seperti kesetaraan moneter — compareTo() umumnya direkomendasikan.

Mengonversi Ke/Dari String

Dalam input pengguna dan impor file eksternal, konversi dengan tipe String umum. String → BigDecimal

BigDecimal value = new Big BigDecimal("1234.56");

BigDecimal → String

String str = value.toString(); // "1234.56"

Menggunakan valueOf Java juga memiliki BigDecimal.valueOf(double val), tetapi ini juga secara internal mengandung kesalahan double, sehingga membangun dari string masih lebih aman.

BigDecimal unsafe = BigDecimal.valueOf(0.1); // contains internal error

Presisi dan Aturan Pembulatan melalui MathContext

MathContext memungkinkan Anda mengontrol presisi dan mode pembulatan sekaligus — berguna saat menerapkan aturan umum di banyak operasi.

MathContext mc = new MathContext(4, RoundingMode.HALF_UP);
BigDecimal result = new BigDecimal("123.4567").round(mc); // 123.5

Juga dapat digunakan dalam aritmatika:

BigDecimal a = new BigDecimal("10.456");
BigDecimal b = new BigDecimal("2.1");
BigDecimal result = a.multiply(b, mc); // 4-digit precision

Pemeriksaan null dan Inisialisasi Aman

Formulir mungkin meneruskan nilai null atau kosong — kode penjaga adalah standar.

String input = ""; // empty
BigDecimal value = (input == null || input.isEmpty()) ? BigDecimal.ZERO : new BigDecimal(input);

Memeriksa Skala BigDecimal

Untuk mengetahui digit desimal, gunakan scale():

BigDecimal value = new BigDecimal("123.45");
System.out.println(value.scale()); // 3

5. Kesalahan Umum dan Cara Memperbaikinya

ArithmeticException: Ekspansi desimal yang tidak berakhir

Contoh Kesalahan:

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b); // exception

Ini adalah “1 ÷ 3” — karena menjadi desimal yang tidak berakhir, jika tidak ada mode pembulatan/skala yang diberikan, eksepsi akan dilempar. Perbaikan: tentukan skala + mode pembulatan

BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // OK (3.33)

Kesalahan Saat Membangun Langsung Dari double

Meneruskan double secara langsung mungkin sudah mengandung kesalahan biner — menghasilkan nilai tak terduga. Contoh Buruk:

BigDecimal val = new BigDecimal(0.1);
System.out.println(val); // 0.100000000000000005551115123...

Benar: Gunakan String

BigDecimal val = new BigDecimal("0.1"); // exact 0.1

Catatan: BigDecimal.valueOf(0.1) menggunakan Double.toString() secara internal, sehingga hampir sama dengan new BigDecimal("0.1") — tetapi string adalah yang paling aman 100 %.

Kesalahpahaman equals Karena Ketidaksesuaian Skala

Karena equals() membandingkan skala, ia dapat mengembalikan false meskipun nilai secara numerik sama.

BigDecimal a = new BigDecimal("10.0");
BigDecimal b = new BigDecimal("10.00");

System.out.println(a.equals(b)); // false

Solusi: gunakan compareTo() untuk kesetaraan numerik

System.out.println(a.compareTo(b)); // 0

Hasil Tak Terduga Karena Presisi Tidak Cukup

Jika menggunakan setScale tanpa menentukan mode pembulatan — pengecualian dapat terjadi.
Contoh Buruk:

BigDecimal value = new BigDecimal("1.2567");
BigDecimal rounded = value.setScale(2); // exception

Solusi:

BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // OK

NumberFormatException Ketika Nilai Input Tidak Valid

Jika teks tidak valid yang tidak dapat diurai sebagai angka diberikan (misalnya, input pengguna / bidang CSV), NumberFormatException akan terjadi.
Solusi: gunakan penanganan pengecualian

try {
    BigDecimal value = new BigDecimal(userInput);
} catch (NumberFormatException e) {
    // show error message or fallback logic
}

6. Contoh Penggunaan Praktis

Di sini kami memperkenalkan skenario dunia nyata yang menunjukkan bagaimana BigDecimal dapat digunakan dalam praktik. Terutama dalam perhitungan keuangan/akuntansi/pajak, pentingnya penanganan numerik yang akurat menjadi jelas.

Menangani Desimal dalam Perhitungan Harga (Pembulatan Pecahan)

Contoh: Menghitung harga termasuk pajak konsumsi 10 %

BigDecimal price = new BigDecimal("980"); // price w/o tax
BigDecimal taxRate = new BigDecimal("0.10");
BigDecimal tax = price.multiply(taxRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal total = price.add(tax);

System.out.println("Tax: " + tax);         // Tax: 98
System.out.println("Total: " + total);     // Total: 1078

Poin:

  • Hasil perhitungan pajak sering diproses sebagai bilangan bulat, menggunakan setScale(0, RoundingMode.HALF_UP) untuk membulatkan.
  • double cenderung menghasilkan kesalahan — BigDecimal disarankan.

Perhitungan Diskon (% OFF)

Contoh: Diskon 20 %

BigDecimal originalPrice = new BigDecimal("3500");
BigDecimal discountRate = new BigDecimal("0.20");
BigDecimal discount = originalPrice.multiply(discountRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal discountedPrice = originalPrice.subtract(discount);

System.out.println("Discount: " + discount);         // Discount: 700
System.out.println("After discount: " + discountedPrice); // 2800

Poin: Perhitungan diskon harga tidak boleh kehilangan presisi.

Perhitungan Harga Satuan × Kuantitas (Skenario Aplikasi Bisnis Umum)

Contoh: 298,5 yen × 7 barang

BigDecimal unitPrice = new BigDecimal("298.5");
BigDecimal quantity = new BigDecimal("7");
BigDecimal total = unitPrice.multiply(quantity).setScale(2, RoundingMode.HALF_UP);

System.out.println("Total: " + total); // 2089.50

Poin:

  • Sesuaikan pembulatan untuk perkalian pecahan.
  • Penting untuk sistem akuntansi / pemesanan.

Perhitungan Bunga Majemuk (Contoh Keuangan)

Contoh: Bunga tahunan 3 % × 5 tahun

BigDecimal principal = new BigDecimal("1000000"); // base: 1,000,000
BigDecimal rate = new BigDecimal("0.03");
int years = 5;

BigDecimal finalAmount = principal;
for (int i = 0; i < years; i++) {
    finalAmount = finalAmount.multiply(rate.add(BigDecimal.ONE)).setScale(2, RoundingMode.HALF_UP);
}

System.out.println("After 5 years: " + finalAmount); // approx 1,159,274.41

Poin:

  • Perhitungan berulang mengakumulasi kesalahan — BigDecimal menghindarinya.

Validasi & Konversi Input Pengguna

public static BigDecimal parseAmount(String input) {
    try {
        return new BigDecimal(input).setScale(2, RoundingMode.HALF_UP);
    } catch (NumberFormatException e) {
        return BigDecimal.ZERO; // treat invalid input as 0
    }
}

Poin:

  • Mengonversi string numerik yang diberikan pengguna dengan aman.
  • Validasi + penanganan kesalahan meningkatkan ketahanan.

7. Ringkasan

Peran BigDecimal

Dalam pemrosesan numerik Java — terutama logika moneter atau yang memerlukan pres — kelas BigDecimal sangat penting. Kesalahan yang melekat pada float / double dapat dihindari secara signifikan dengan menggunakan BigDecimal.
Artikel ini membahas dasar-dasar, operasi aritmetika, perbandingan, pembulatan, penanganan kesalahan, dan contoh dunia nyata.

Poin Tinjauan Utama

  • BigDecimal menangani desimal dengan presisi tak terbatas — ideal untuk uang dan perhitungan presisi
  • Inisialisasi sebaiknya menggunakan literal string, misalnya new BigDecimal("0.1")
  • Gunakan add(), subtract(), multiply(), divide(), dan selalu tentukan mode pembulatan saat melakukan pembagian
  • Gunakan compareTo() untuk kesetaraan — pahami perbedaan dengan equals()
  • setScale() / MathContext memungkinkan Anda mengontrol skala + pembulatan secara halus
  • Contoh kasus logika bisnis nyata meliputi uang, pajak, kuantitas × harga satuan, dll.

Bagi Mereka yang Akan Menggunakan BigDecimal

Meskipun “menangani angka di Java” tampak sederhana — masalah presisi / pembulatan / kesalahan numerik selalu ada di baliknya. BigDecimal adalah alat yang secara langsung mengatasi masalah tersebut — menguasainya memungkinkan Anda menulis kode yang lebih dapat diandalkan.
Pada awalnya Anda mungkin kesulitan dengan mode pembulatan — tetapi dengan penggunaan proyek nyata, hal itu menjadi alami.
Bab berikutnya adalah bagian FAQ yang merangkum pertanyaan umum tentang BigDecimal — berguna untuk tinjauan dan pencarian semantik spesifik.

8. FAQ: Pertanyaan yang Sering Diajukan tentang BigDecimal

Q1. Mengapa saya harus menggunakan BigDecimal alih-alih float atau double?

A1.
Karena float/double merepresentasikan angka sebagai aproksimasi biner — pecahan desimal tidak dapat direpresentasikan secara tepat. Hal ini menyebabkan hasil seperti “0.1 + 0.2 ≠ 0.3.”
BigDecimal mempertahankan nilai desimal secara tepat — ideal untuk uang atau logika yang memerlukan presisi tinggi.

Q2. Apa cara paling aman untuk membuat instance BigDecimal?

A2.
Selalu buat dari string.
Buruk (error):

new BigDecimal(0.1)

Benar:

new BigDecimal("0.1")

BigDecimal.valueOf(0.1) menggunakan Double.toString() secara internal, jadi hampir sama — tetapi string adalah yang paling aman.

Q3. Mengapa divide() melempar pengecualian?

A3.
Karena BigDecimal.divide() melempar ArithmeticException ketika hasilnya merupakan desimal yang tidak berakhir.
Solusi: tentukan skala + mode pembulatan

BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);

Q4. Apa perbedaan antara compareTo() dan equals()?

A4.

  • compareTo() memeriksa kesetaraan numerik (skala diabaikan)
  • equals() memeriksa kesetaraan tepat termasuk skala
    new BigDecimal("10.0").compareTo(new BigDecimal("10.00")); // → 0
    new BigDecimal("10.0").equals(new BigDecimal("10.00"));    // → false
    

Q5. Bagaimana cara melakukan pembulatan?

A5.
Gunakan setScale() dengan mode pembulatan yang eksplisit.

BigDecimal value = new BigDecimal("123.4567");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46

Mode pembulatan utama:

  • RoundingMode.HALF_UP (pembulatan setengah ke atas)
  • RoundingMode.DOWN (pembulatan ke bawah)
  • `RoundingMode.UPpembulatan### Q6.akah saya memeriksa digit desimal (skala)?

A6.
Ya — gunakan scale().

BigDecimal val = new BigDecimal("123.45");
System.out.println(val.scale()); // → 3

Q7. Bagaimana cara menangani input null/kosong dengan aman?

A7.
Selalu sertakan pemeriksaan null + penanganan pengecualian.

public static BigDecimal parseSafe(String input) {
    if (input == null || input.trim().isEmpty()) return BigDecimal.ZERO;
    try {
        return new BigDecimal(input.trim());
    } catch (NumberFormatException e) {
        return BigDecimal.ZERO;
    }
}