Gandakan vs BigDecimal?

303

Saya harus menghitung beberapa variabel floating point dan kolega saya menyarankan saya untuk menggunakan BigDecimalalih-alih doublekarena akan lebih tepat. Tapi saya ingin tahu apa itu dan bagaimana memanfaatkannya BigDecimal?

Truong Ha
sumber
Lihatlah yang ini; stackoverflow.com/questions/322749/…
Espen Schulstad

Jawaban:

446

A BigDecimaladalah cara yang tepat untuk merepresentasikan angka. A Doublememiliki presisi tertentu. Bekerja dengan dobel dari berbagai besaran (katakan d1=1000.0dan d2=0.001) dapat mengakibatkan 0.001dijatuhkan secara bersamaan ketika dijumlahkan karena perbedaan besarnya sangat besar. Dengan BigDecimalini tidak akan terjadi.

Kerugiannya BigDecimaladalah bahwa itu lebih lambat, dan itu sedikit lebih sulit untuk memprogram algoritma seperti itu (karena + - *dan /tidak kelebihan beban).

Jika Anda berurusan dengan uang, atau ketepatan adalah suatu keharusan, gunakan BigDecimal. Kalau tidak, Doublescenderung cukup baik.

Saya sarankan membaca javadoc dari BigDecimalseperti yang mereka lakukan menjelaskan hal-hal yang lebih baik daripada yang saya lakukan di sini :)

ekstraneon
sumber
Yap, saya sedang menghitung harga untuk stok jadi saya percaya BigDecimal berguna dalam kasus ini.
Truong Ha
5
@Truong Ha: Ketika bekerja dengan harga yang Anda ingin gunakan BigDecimal. Dan jika Anda menyimpannya dalam database Anda menginginkan hal yang serupa.
extraneon
98
Mengatakan bahwa "BigDecimal adalah cara yang tepat untuk mewakili angka" adalah menyesatkan. 1/3 dan 1/7 tidak dapat diekspresikan secara tepat dalam sistem basis 10 angka (BigDecimal) atau dalam sistem basis nomor 2 (float atau dobel). 1/3 dapat diekspresikan secara tepat dalam basis 3, basis 6, basis 9, basis 12, dll. Dan 1/7 dapat diekspresikan dengan tepat di basis 7, basis 14, basis 21, dll. Keuntungan BigDecimal adalah bahwa itu adalah presisi yang berubah-ubah dan bahwa manusia terbiasa dengan kesalahan pembulatan yang Anda dapatkan di basis 10.
procrastinate_later
3
Poin bagus tentang hal ini menjadi lebih lambat, membantu saya memahami mengapa Netflix Ribbon memuat kode penyeimbang yang berurusan dengan ganda, dan kemudian memiliki garis-garis seperti ini:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok
@extraneon Saya pikir Anda bermaksud mengatakan "jika akurasi adalah suatu keharusan, gunakan BigDecimal", Double akan memiliki lebih banyak "presisi" (lebih banyak digit).
jspinella
164

Bahasa Inggris saya tidak bagus jadi saya hanya akan menulis contoh sederhana di sini.

    double a = 0.02;
    double b = 0.03;
    double c = b - a;
    System.out.println(c);

    BigDecimal _a = new BigDecimal("0.02");
    BigDecimal _b = new BigDecimal("0.03");
    BigDecimal _c = _b.subtract(_a);
    System.out.println(_c);

Output program:

0.009999999999999998
0.01

Adakah yang masih ingin menggunakan dobel? ;)

Kemangi
sumber
11
@eldjon Thats tidak benar, Lihat contoh ini: BigDecimal two = BigDecimal baru ("2"); BigDecimal delapan = BigDecimal baru ("8"); System.out.println (two.divide (delapan)); Ini mencetak 0,25.
Ludvig W
4
dobel forevr: D
vach
Meskipun demikian, jika Anda menggunakan pelampung, Anda mendapatkan presisi yang sama dari BigDecimal dalam hal itu, tetapi kinerja yang jauh lebih baik
EliuX
3
@EliuX Float dapat bekerja dengan 0,03-0,02, tetapi nilai-nilai lain masih tidak tepat: System.out.println(0.003f - 0.002f);BigDecimal tepat:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Ada dua perbedaan utama dari ganda:

  • Presisi sewenang-wenang, mirip dengan BigInteger mereka dapat berisi jumlah presisi dan ukuran sewenang-wenang
  • Basis 10 dan bukan Basis 2, BigDecimal adalah skala n * 10 ^ di mana n adalah bilangan bulat bertanda besar dan skala dapat dianggap sebagai jumlah digit untuk memindahkan titik desimal ke kiri atau ke kanan

Alasan Anda harus menggunakan BigDecimal untuk perhitungan moneter bukan karena ia dapat mewakili angka apa pun, tetapi ia dapat mewakili semua angka yang dapat direpresentasikan dalam konsep desimal dan yang mencakup hampir semua angka dalam dunia moneter (Anda tidak pernah mentransfer 1/3 $ untuk seseorang).

Meros
sumber
2
Jawaban ini benar-benar menjelaskan perbedaan dan alasan penggunaan BigDecimal lebih dari dua kali lipat. Masalah kinerja adalah hal sekunder.
Vortex
Ini tidak 100% benar. Anda menulis bahwa BigDecimal adalah "n * 10 ^ skala". Java hanya melakukan itu untuk angka negatif. Jadi yang benar adalah: "unscaledValue × 10 ^ -scale". Untuk bilangan positif, BigDecimal terdiri dari "nilai unscaled integer arbitrary presisi dan skala integer 32-bit" sedangkan skalanya adalah jumlah digit di sebelah kanan titik desimal.
tangan NOD
25

Jika Anda menuliskan nilai pecahan seperti 1 / 7nilai desimal yang Anda dapatkan

1/7 = 0.142857142857142857142857142857142857142857...

dengan urutan tak terbatas 142857. Karena Anda hanya dapat menulis jumlah digit terbatas, Anda pasti akan membuat kesalahan pembulatan (atau pemotongan).

Angka seperti 1/10atau 1/100dinyatakan sebagai angka biner dengan bagian fraksional juga memiliki jumlah digit tak terbatas setelah titik desimal:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles menyimpan nilai-nilai sebagai biner dan karena itu dapat memperkenalkan kesalahan semata-mata dengan mengubah angka desimal menjadi angka biner, tanpa melakukan aritmatika apa pun.

Angka desimal (seperti BigDecimal), di sisi lain, menyimpan setiap angka desimal apa adanya. Ini berarti bahwa jenis desimal tidak lebih tepat daripada titik mengambang biner atau tipe titik tetap dalam arti umum (yaitu tidak dapat menyimpan 1/7tanpa kehilangan presisi), tetapi lebih akurat untuk angka yang memiliki jumlah terbatas angka desimal sebagai sering terjadi untuk perhitungan uang.

Jawa BigDecimalmemiliki keuntungan tambahan yang dapat memiliki sewenang-wenang (namun terbatas) jumlah digit di kedua sisi titik desimal, hanya dibatasi oleh memori yang tersedia.

Olivier Jacot-Descombes
sumber
7

BigDecimal adalah perpustakaan numerik presisi arbitrer Oracle. BigDecimal adalah bagian dari bahasa Jawa dan berguna untuk berbagai aplikasi mulai dari yang finansial hingga yang ilmiah (di situlah saya ada).

Tidak ada yang salah dengan menggunakan ganda untuk perhitungan tertentu. Misalkan, Anda ingin menghitung Math.Pi * Math.Pi / 6, yaitu, nilai Riemann Zeta Function untuk argumen nyata dua (proyek yang sedang saya kerjakan). Divisi floating-point memberi Anda masalah kesalahan pembulatan yang menyakitkan.

BigDecimal, di sisi lain, mencakup banyak opsi untuk menghitung ekspresi dengan ketepatan arbitrer. Metode tambah, gandakan, dan bagi seperti dijelaskan dalam dokumentasi Oracle di bawah ini "take the place" dari +, *, dan / di BigDecimal Java World:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

Metode compareTo sangat berguna untuk while dan untuk loop.

Namun, berhati-hatilah dalam penggunaan konstruktor untuk BigDecimal. Konstruktor string sangat berguna dalam banyak kasus. Misalnya, kodenya

BigDecimal onethird = BigDecimal baru ("0.33333333333");

menggunakan representasi string 1/3 untuk mewakili nomor berulang berulang hingga tingkat akurasi yang ditentukan. Kesalahan pembulatan kemungkinan besar terjadi di suatu tempat yang jauh di dalam JVM sehingga kesalahan pembulatan tidak akan mengganggu sebagian besar perhitungan praktis Anda. Namun, dari pengalaman pribadi saya, saya melihat banyak hal. Metode setScale penting dalam hal ini, seperti yang dapat dilihat dari dokumentasi Oracle.

nelayan itu
sumber
BigDecimal adalah bagian dari perpustakaan numerik presisi arbitrer Jawa . 'In-house' agak tidak berarti dalam konteks ini, terutama seperti yang ditulis oleh IBM.
Marquis of Lorne
@ EJP: Saya melihat ke dalam kelas BigDecimal dan mengetahui bahwa hanya sebagian yang ditulis oleh IBM. Komentar hak cipta di bawah: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

Jika Anda berurusan dengan perhitungan, ada undang-undang tentang bagaimana Anda harus menghitung dan presisi apa yang harus Anda gunakan. Jika Anda gagal Anda akan melakukan sesuatu yang ilegal. Satu-satunya alasan sebenarnya adalah bahwa representasi bit dari kasus desimal tidak tepat. Seperti yang dikatakan Basil, sebuah contoh adalah penjelasan terbaik. Hanya untuk melengkapi contohnya, inilah yang terjadi:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

Keluaran:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Kami juga memiliki itu:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

Memberi kami hasilnya:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

Tapi:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

Memiliki output:

BigDec:  10 / 3 = 3.3333 
jfajunior
sumber