author-pic

Ferry S

An ISTJ, Type 5, Engineer, Gamer, and Thriller-Movies-Lover
Java: BigDecimal vs Double
Thursday Dec 15th, 2022 09:05 pm7 mins read
Java, Tips & Tutorial
Java: BigDecimal vs Double
Source: Adobe - Understanding Decimals

Ketika mengembangkan aplikasi, melakukan kalkulasi bilangan desimal terkadang cukup tricky. Apalagi kalau berhubungan dengan duit, seperti pada aplikasi perbankan, e-commerce, dan sejenisnya. Perhitungannya tentu harus akurat sesuai aturan yang diberlakukan oleh perusahaan. Kalau tidak teliti saat develop bisa salah perhitungannya. Salah satu hal yang dipertimbangkan saat develop adalah penggunaan tipe data. Seperti pada Java, ada dua tipe data yang bisa digunakan untuk menampung bilangan desimal dengan ukuran yang besar, yaitu BigDecimal dan Double. Walaupun sama-sama bisa menampung bilangan desimal dengan ukuran besar, keduanya memiliki perbedaan yang cukup signifikan.

Initialization

Untuk hal ini double lebih unggul. Pada double kita cukup input angka dan tanda decimal saat membuat variable. Sedangkan untuk BigDecimal kita harus membungkus value lewat constructor maupun lewat static creation method.

double d = 0.1;
BigDecimal bigDecimal = BigDecimal.valueOf(0.1);

Tapi perlu diingat, BigDecimal punya beberapa kelemahan saat membuat objek. Ketika menggunakan static method valueOf(double) dengan scale lebih dari 16, maka Java akan melakukan pembulatan pada double. Sehingga value yang dihasilkan juga akan di-scale oleh Java. Untuk itu kalau kita membutuhkan value double yang presisi dengan scale lebih dari 16, maka gunakan constructor yang parameternya String. Selain constructor yang parameternya String, juga ada constructor yang parameternya double yang menghasilkan BigDecimal dengan floating point tak terhingga.

BigDecimal notPrecisionDecimal = BigDecimal.valueOf(0.1234567890123456789); //will be scaled automatically
BigDecimal precisionDecimal = new BigDecimal("0.1234567890123456789"); //will be precised
BigDecimal unlimitedFloatingDecimal = new BigDecimal(0.1); //with unlimited floating point

Untuk itu, kalau mau menggunakan BigDecimal dengan presisi yang tidak terlalu tinggi dari double value maka gunakan BigDecimal.valueOf(double). Untuk presisi yang tinggi maka gunakan constructor yang menerima String, new BigDecimal(String). Sedangkan constructor yang menerima double, new BigDecimal(double) sebaiknya dihindari kecuali paham fungsinya karena akan menghasilkan unlimited floating point.

Native Operation

Saat melakukan kalkulasi, pada Double kita bisa langsung menggunakan operator matematika secara native. Sedangkan untuk BigDecimal kita hanya bisa melakukannya lewat method yang terdapat pada BigDecimal. Contohnya seperti berikut:

double dd = 0.5 + 0.3 - 0.1 * 4 / 2;

BigDecimal bigDecimal1 = BigDecimal.valueOf(0.5)
		.add(BigDecimal.valueOf(0.3))
		.subtract(BigDecimal.valueOf(0.1)
				.multiply(BigDecimal.valueOf(4)
						.divide(BigDecimal.valueOf(2), RoundingMode.HALF_EVEN)));

Oh ya, perlu diperhatikan ketika menggunakan BigDecimal, urutan kalkulasinya tidak seperti standar matematika, melainkan dari paling kiri ke kanan, kecuali ada perhitungan di dalam tanda kurung. Sedangkan pada Double, urutan kalkulasinya sesuai standar matematika, yaitu dari kiri ke kanan dengan prioritas perkalian/pembagian terlebih dahulu, baru setelah itu penjumlahan/pengurangan, kecuali ada perhitungan di dalam tanda kurung.

Calculation Result

Pada Double kalkulasi yang digunakan menggunakan system floating point yang dihitung secara binary. Contohnya ketika melakukan kalkulasi 0.1 + 0.2 maka hasilnya adalah 0.30000000000000004. Sedangkan secara akuntansi, 0.1 + 0.2 hasilnya adalah 0.3 seharusnya. Kalau kita menggunakannya pada use case yang berhubungan dengan perhitungan uang, tentu sangat tricky. Apalagi pada perbankan, beda sedikit saja, walaupun hanya selisih satu angka dibelakang koma, hasil kalkulasinya tentu sangat berdampak pada bisnis perusahaan. Tapi kalau kita menggunakan BigDecimal, hasilnya adalah 0.3 sesuai dengan yang kita harapkan.

double x = 0.1;
double y = 0.2;
System.out.println("(x + y) = " + (x + y));

BigDecimal a = BigDecimal.valueOf(0.1);
BigDecimal b = BigDecimal.valueOf(0.2);
System.out.println("a.add(b) = " + a.add(b));

Equals Comparison

Ini mungkin salah satu kekurangan dari BigDecimal. Ketika menggunakan primitive double, comparison bisa menggunakan symbol ==. Ketika menggunakan wrapper Double, comparison dilakukan menggunakan method equals(). Untuk BigDecimal kita tidak bisa menggunakan symbol == karena bukan primitive type, juga tidak bisa menggunakan method equals() karena akan membandingkan dua state objek secara strict. Seperti contoh seperti berikut:

double dua = 2;
double duaLagi = 2;

System.out.println("result = " + (dua == duaLagi));

Double tiga = 3;
Double tigaLagi = 3;

System.out.println("result = " + (tiga.equals(tigaLagi)));

BigDecimal satu = BigDecimal.valueOf(1);
BigDecimal satuDecimal = new BigDecimal("1.0");

System.out.println("result = " + satu.equals(satuDecimal));

Code BigDecimal di atas hasilnya adalah false karena akan membandingkan dua BigDecimal secara strict meskipun secara akuntansi "1" dan "1.0" itu valuenya sama. Untuk itu kita perlu menggunakan method compareTo() == 0 untuk equals, compareTo() < 0 untuk less than, dan compareTo() > 0 untuk greater than. Contohnya seperti berikut:

BigDecimal satu = BigDecimal.valueOf(1);
BigDecimal satuDecimal = new BigDecimal("1.0");

System.out.println("result = " + satu.compareTo(satuDecimal) == 0);

Dengan begitu sekarang hasilnya sudah sesuai dengan yang diharapkan. Sekarang yang dibandingkan hanya valuenya saja.

Scale

Pada BigDecimal kita bisa tentukan scale atau jumlah digit desimalnya sesuai yang kita mau saat digunakan. Ketika melakukan perhitungan, scale pada BigDecimal adalah tak terhingga. Makanya kita disarankan menentukan scale dan rounding pada saat melakukan pembagian untuk menghindari scale tak terhingga yang mengakibatkan error ArithmeticException. Contohnya ketika membagi 10 dibagi 3 yang menghasilkan angka desimal tak terhingga. Sedangkan pada Double kita tidak bisa langsung menentukan scale, kita hanya bisa melakukan pembulatan ketika dikonversi ke String. Atau dengan convert menjadi BigDecimal lalu set scale dan convert lagi ke Double. Pada saat melakukan pembagian 10 dibagi 3 menggunakan Double tidak akan error, tapi akan otomatis melakukan pembulatan menggunakan system floating point.

double d = 0.123456789;
String formatted = String.format("%.2f", d);
double formattedDouble = Double.valueOf(formatted);

BigDecimal dec = BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_EVEN);

double ten = 10;
double three = 3;
System.out.println(ten / three); //result 3.3333333333333335

BigDecimal tenDec = BigDecimal.valueOf(10);
BigDecimal threeDec = BigDecimal.valueOf(3);
System.out.println(tenDec.divide(threeDec, 2, RoundingMode.UP)); //no error
System.out.println(tenDec.divide(threeDec)); //throw exception

Rounding

Ini adalah alasan kuat BigDecimal lebih unggul daripada double. By default, RoundingMode yang digunakan pada Double adalah Half Even. Kita hanya bisa mengubahnya menggunakan bantuan objek DecimalFormat saat convert ke String. Sedangkan pada BigDecimal by default RoundingMode-nya adalah Unnecessary. Tapi kita bisa mengubah RoundingMode yang diinginkan secara langsung pada objeknya. Makanya, saat melakukan pembagian, selain menentukan scale kita juga disarankan menentukan RoundingMode yang diinginkan pada parameter untuk mencegah ArithmeticException. RoundingMode sangat penting pada bisnis perusahaan yang memiliki rules tertentu terutama saat melakukan pembulatan. Misalnya saat melakukan kalkulasi diskon, perusahaan tersebut memiliki kebijakan melakukan pembulatan terlebih dahulu sebelum melakukan perkalian/pembagian dengan RoundingMode tertentu.

Terdapat 6 jenis RoundingMode yang bisa digunakan.

RoundingMode Ceiling

Secara gampangnya, pembulatannya selalu mendekati ke arah yang lebih positif. Contohnya bilangan 0.563 dengan scale 2 angka di belakang koma menggunakan RoundingMode Ceiling, maka hasilnya adalah 0.57. Sedangkan untuk bilangan negatif, -0.563, maka hasilnya adalah -0.56 karena -0.56 lebih mendekati ke arah positif daripada -0.57.

RoundingMode Floor

Ini adalah kebalikan dari RoundingMode Ceiling, kalau RoundingMode Floor pembulatannya selalu mendekati ke arah yang lebih negatif. Contohnya bilangan 0.567 dengan scale 2 angka di belakang koma menggunakan RoundingMode Floor, maka hasilnya adalah 0.56. Sedangkan untuk bilangan negatif, -0.567, maka hasilnya adalah -0.57 karena -0.57 lebih mendekati ke arah negatif daripada -0.56.

RoundingMode Up

RoundingMode Up adalah pembulatannya menjauhi 0. Contohnya bilangan 0.563 dengan scale 2 angka di belakang koma menggunakan RoundingMode Up, maka hasilnya adalah 0.57. Begitu juga dengan bilangan negatif, -0.563, maka hasilnya adalah -0.57 karena -0.57 lebih jauh dari angka 0 dibanding -0.56.

RoundingMode Down

RoundingMode Down kebalikannya RoundingMode Up, yaitu pembulatannya mendekati 0. Contohnya bilangan 0.567 dengan scale 2 angka di belakang koma menggunakan RoundingMode Down, maka hasilnya adalah 0.56. Begitu juga dengan bilangan negatif, -0.567, maka hasilnya adalah -0.56 karena -0.56 lebih mendekati 0 daripada -0.57.

RoundingMode Half Up

Pada RoundingMode Up, pembulatannya selalu menjauhi 0 berapapun bilangan terakhirnya. Sedangkan untuk RoundingMode Half Up, jika angka pembulatan terakhirnya rentang 1-4 maka pembulatannya mendekati 0, untuk angka pembulatan terakhirnya rentang 6-9 maka pembulatannya menjauhi 0. Sedangkan untuk angka pembulatan terakhirnya 5, maka pembulatannya menjauhi 0. Contohnya pada tabel berikut menggunakan scale 2 digit:

Number Rounding Result
0.567 0.57
0.563 0.56
0.565 0.57
-0.565 -0.57
-0.563 -0.56
-0.567 -0.57

RoundingMode Half Down

Untuk RoundingMode Half Down, jika angka pembulatan terakhirnya rentang 1-4 maka pembulatannya mendekati 0, untuk angka pembulatan terakhirnya rentang 6-9 maka pembulatannya menjauhi 0. Sedangkan untuk angka pembulatan terakhirnya 5, maka pembulatannya mendekati 0. Contohnya pada tabel berikut menggunakan scale 2 digit:

Number Rounding Result
0.567 0.57
0.563 0.56
0.565 0.56
-0.565 -0.56
-0.563 -0.56
-0.567 -0.57

RoundingMode Half Even

Ini juga sama seperti kedua RoundingMode Half sebelumnya, untuk angka pembulatan terakhirnya rentang 1-4 pembulatannya mendekati 0 dan untuk rentang 6-9 pembulatannya menjauhi 0. Yang membedakan adalah ketika angka pembulatan terakhirnya 5, maka pembulatannya menjadi angka genap. Contohnya 0.565, maka pembulatannya ke bawah menjadi 0.56 karena itu adalah bilangan genap. Sedangkan untuk bilangan 0.575, maka pembulatannya digenapkan ke atas menjadi 0.58. Ini adalah RoundingMode yang umum digunakan perbankan untuk mengolah data keuangan.

Number Rounding Result
0.567 0.57
0.563 0.56
0.565 0.56
0.575 0.58
-0.575 -0.58
-0.565 -0.56
-0.563 -0.56
-0.567 -0.57

RoundingMode Unnecessary

Unnecessary artinya tidak ada pembulatan sama sekali. Ini harus dihindari, apalagi saat melakukan pembagian karena dapat menghasilkan error ArithmeticException. Ini adalah default rounding dari BigDecimal, makanya ketika melakukan pembagian menggunakan BigDecimal kita disarankan menentukan scale dan rounding pada method divide untuk menghindari hasil yang tidak diinginkan.

double d = 0.123456789;
NumberFormat numberInstance = NumberFormat.getNumberInstance();
numberInstance.setMaximumFractionDigits(6);
numberInstance.setRoundingMode(RoundingMode.UP);
String formatted = numberInstance.format(d);

BigDecimal dec = BigDecimal.valueOf(d).setScale(6, RoundingMode.UP);

Verdict

Itulah beberapa perbedaan BigDecimal dengan Double. BigDecimal memiliki fitur yang lebih advanced dibanding Double karena kita bisa menentukan scale dan rounding yang diinginkan sesuai kebutuhan bisnis. Ketika melakukan pembagian menggunakan BigDecimal, kita wajib menentukan RoundingMode dan scale untuk menghindari ArithmeticException karena default RoundingMode-nya adalah Unnecessary dan default scale-nya adalah tak terhingga. Juga perlu diperhatikan, urutan kalkulasi pada BigDecimal adalah dari yang paling kiri atau sesuai tanda kurung, bukan mengikuti standar matematika seperti Double. Secara penggunaan, BigDecimal lebih kompleks daripada Double karena merupakan objek khusus pada Java, kita harus membungkus setiap angka yang ingin dihitung menjadi BigDecimal dan menggunakan method yang ada di dalamnya untuk melakukan kalkulasi. Berbeda dengan Double yang lebih gampang digunakan menggunakan operator matematika biasa. Secara penggunaan, BigDecimal sangat cocok untuk perhitungan akuntansi seperti hal-hal yang berkaitan dengan keuangan. Sedangkan Double lebih cocok untuk hal-hal umum lainnya yang ga perlu angka desimal dengan presisi yang akurat seperti menghitung berat atau ukuran volume.