Çift mi BigDecimal mi?


Yanıtlar:


446

A BigDecimal, sayıları temsil etmenin kesin bir yoludur. A'nın Doublekesin bir hassasiyeti vardır. Çeşitli büyüklüklerde çiftler ile çalışmak (diyelim d1=1000.0ve söyleyin d2=0.001) 0.001, büyüklükteki fark çok büyük olduğu için toplanırken tamamen düşmesine neden olabilir . Bununla BigDecimalolmazdı.

Dezavantajı BigDecimaldaha yavaş olmasıdır, ve (nedeniyle program algoritmaları bu şekilde biraz daha zor + - *ve /olmayan aşırı edilmiştir).

Parayla uğraşıyorsanız veya hassasiyet şartsa, kullanın BigDecimal. Aksi takdirde Doublesyeterince iyi olma eğilimindedir.

Okumayı tavsiye edersiniz javadoc ait BigDecimalonlar daha iyi ben burada yapmak daha şeyleri açıklamak gibi :)


Evet, hisse senedinin fiyatını hesaplıyorum, bu yüzden BigDecimal'in bu durumda yararlı olduğuna inanıyorum.
Truong Ha

5
@Truong Ha: Fiyatlarla çalışırken BigDecimal kullanmak istiyorsunuz. Ve bunları veritabanında saklarsanız benzer bir şey istersiniz.
extraneon

98
"BigDecimal'ın sayıları temsil etmenin kesin bir yolu olduğunu" söylemek yanıltıcıdır. 1/3 ve 1/7, bir temel 10 sayı sisteminde (BigDecimal) veya temel 2 sayı sisteminde (float veya double) tam olarak ifade edilemez. 1/3, taban 3, taban 6, taban 9, taban 12, vb. İle tam olarak ifade edilebilir ve 1/7, taban 7, taban 14, taban 21 vb. İle tam olarak ifade edilebilir. BigDecimal avantajları, keyfi hassasiyet olmasıdır. ve insanlar üs 10'da aldığınız yuvarlama hatalarına
alışkınlar

3
Daha yavaş olmasıyla ilgili iyi bir nokta, Netflix Şerit yük dengeleyici kodunun neden if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
çiftlerle uğraştığını

@extraneon Sanırım " doğruluk bir zorunluluksa kullanın, BigDecimal", bir Double daha fazla "hassasiyet" (daha fazla basamak) olurdu demek istiyorsun .
jspinella

164

İngilizcem iyi değil, bu yüzden burada basit bir örnek yazacağım.

    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);

Program çıktısı:

0.009999999999999998
0.01

Birisi hala çift mi kullanmak istiyor? ;)


11
@eldjon Bu doğru değil, Şu örneğe bakın: BigDecimal two = new BigDecimal ("2"); BigDecimal sekiz = yeni BigDecimal ("8"); System.out.println (two.divide (sekiz)); Bu, 0,25 yazdırır.
Ludvig K

4
iki kez forevr: D
vach

Bununla birlikte, bir şamandıra kullanırsanız, bu durumda BigDecimal ile aynı hassasiyeti elde edersiniz, ancak çok daha iyi performans
EliuX

3
@EliuX Float 0.03-0.02 ile çalışabilir, ancak diğer değerler hala kesin değildir: System.out.println(0.003f - 0.002f);BigDecimal kesindir :System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin

50

Çifte iki temel fark vardır:

  • Rasgele hassasiyet, BigInteger'e benzer şekilde, rasgele hassasiyet ve boyut içerebilir
  • Base 2 yerine Base 10, bir BigDecimal n * 10 ^ ölçeğidir; burada n, rastgele büyük bir işaretli tamsayıdır ve ölçek, ondalık noktasını sola veya sağa hareket ettirmek için basamak sayısı olarak düşünülebilir

Parasal hesaplamalar için BigDecimal kullanmanızın nedeni, herhangi bir sayıyı temsil edebilmesi değil, ondalık kavramda temsil edilebilen ve parasal dünyadaki neredeyse tüm sayıları içeren tüm sayıları temsil edebilmesidir (asla 1/3 $ aktarmazsınız) birine).


2
Bu cevap, BigDecimal'ı iki katın üzerinde kullanmanın farkını ve nedenini gerçekten açıklıyor. Performans endişeleri ikincildir.
Vorteks

Bu% 100 doğru değil. Bir BigDecimal'ın "n * 10 ^ scale" olduğunu yazdınız. Java bunu yalnızca negatif sayılar için yapar. Bu yüzden doğru olur: "unscaledValue × 10 ^ -scale". Pozitif sayılar için BigDecimal "keyfi hassas tamsayı ölçeklenmemiş değeri ve 32 bit tamsayı ölçeğini" içerirken, ölçek ondalık noktasının sağındaki basamak sayısıdır.
NOD'un eli

25

1 / 7Ondalık değer gibi kesirli bir değer yazarsanız,

1/7 = 0.142857142857142857142857142857142857142857...

sonsuz bir dizi ile 142857 . Yalnızca sınırlı sayıda basamak yazabildiğiniz için, kaçınılmaz olarak bir yuvarlama (veya kesme) hatası verirsiniz.

Kesirli kısmı olan ikili sayılar gibi 1/10veya 1/100sayılarla ifade edilen sayılar ayrıca ondalık noktadan sonra sonsuz sayıda basamağa sahiptir:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles değerleri ikili olarak saklar ve bu nedenle herhangi bir aritmetik bile yapmadan yalnızca ondalık bir sayıyı ikili sayıya dönüştürerek hata verebilir.

Ondalık sayılar (örneğin BigDecimal), her ondalık basamağı olduğu gibi saklar. Bu, ondalık türün genel anlamda bir ikili kayan noktadan veya sabit nokta türünden daha kesin olmadığı anlamına gelir (yani saklayamaz1/7 , kesinlik kaybı olmadan ), ancak sonlu ondalık basamağa sahip sayılar için daha doğrudur. genellikle para hesaplamaları için geçerlidir.

Java'nın BigDecimalondalık noktanın her iki tarafında rasgele (ancak sonlu) sayıya sahip olması ve yalnızca kullanılabilir bellekle sınırlı olması ek bir avantaja sahiptir.


7

BigDecimal, Oracle'ın keyfi hassas sayısal kütüphanesidir. BigDecimal, Java dilinin bir parçasıdır ve finanstan bilime kadar çeşitli uygulamalar için yararlıdır (işte burası).

Belirli hesaplamalar için çiftler kullanmanın yanlış bir yanı yoktur. Bununla birlikte, Math.Pi * Math.Pi / 6'yı, yani gerçek bir iki argüman için Riemann Zeta İşlevinin değerini (şu anda üzerinde çalıştığım bir proje) hesaplamak istediğinizi varsayalım. Kayan nokta bölümü size acı veren bir yuvarlama hatası sorunu sunar.

Öte yandan BigDecimal, ifadeleri keyfi hassasiyete hesaplamak için birçok seçenek içerir. Aşağıdaki Oracle belgelerinde açıklandığı gibi ekleme, çarpma ve bölme yöntemleri, BigDecimal Java World uygulamasında +, * ve / öğelerinin "yerini alır":

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

CompareTo yöntemi özellikle döngülerde ve için kullanışlıdır.

Ancak, BigDecimal için yapıcıları kullanırken dikkatli olun. Dize yapıcı birçok durumda çok yararlıdır. Örneğin, kod

BigDecimal onethird = yeni BigDecimal ("0.33333333333");

1 / 3'lük bir dize temsilini kullanarak, sonsuz sayıda yinelenen sayıyı belirli bir doğruluk derecesine kadar temsil eder. Yuvarlama hatası büyük olasılıkla JVM'nin içinde o kadar derin bir yerde ki yuvarlama hataları pratik hesaplamalarınızın çoğunu rahatsız etmeyecektir. Bununla birlikte, kişisel deneyimlerimden ötürü, yuvarlanma sürünme gördüm. Oracle belgelerinde görülebileceği gibi, setScale yöntemi bu açıdan önemlidir.


BigDecimal olan kısım arasında Java'nın keyfi kesinlikte sayısal kütüphanesine. 'Şirket içi' bu bağlamda, özellikle IBM tarafından yazıldığı gibi, anlamsızdır.
Lorne Marquis,

@EJP: BigDecimal sınıfına baktım ve sadece bir kısmının IBM tarafından yazıldığını öğrendim. Aşağıdaki telif hakkı yorumu: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK

7

Hesaplamayla uğraşıyorsanız, nasıl hesaplamanız ve hangi hassasiyeti kullanmanız gerektiğine dair yasalar vardır. Eğer başarısız olursanız yasadışı bir şey yapacaksınız. Tek gerçek neden, ondalık vakaların bit sunumunun kesin olmamasıdır. Basil'in basitçe ifade ettiği gibi, bir örnek en iyi açıklamadır. Sadece örneğini tamamlamak için şöyle olur:

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)));
}

Çıktı:

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

Ayrıca:

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)));
}

Bize çıktıyı verir:

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

Fakat:

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)));
}

Çıktıya sahiptir:

BigDec:  10 / 3 = 3.3333 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.