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.