Çift Java ile hassasiyeti koruyun


Yanıtlar:


151

Diğerlerinin de belirttiği gibi, BigDecimal11.4'ün tam bir temsilini istiyorsanız, muhtemelen sınıfı kullanmak isteyeceksiniz.

Şimdi, bunun neden olduğuna dair küçük bir açıklama:

floatVe doubleJava ilkel türleri vardır kayan nokta sayısı fraksiyonu ve bir üs bir ikili temsili olarak depolanır numaraları.

Daha spesifik olarak, tür gibi çift kesinlikli kayar nokta değeri double64 bitlik bir değerdir, burada:

  • 1 bit işareti belirtir (pozitif veya negatif).
  • Üs için 11 bit.
  • Anlamlı basamaklar için 52 bit (ikili olarak kesirli kısım).

Bu parçalar, doublebir değerin temsilini üretmek için birleştirilir .

(Kaynak: Wikipedia: Çift hassasiyet )

Kayan nokta değerlerinin Java'da nasıl işlendiğine ilişkin ayrıntılı bir açıklama için bkz. Bölüm 4.2.3: Kayan Nokta Türleri, Biçimleri ve Java Dili Belirtiminin Değerleri .

byte, char, int, longTürleri vardır sabit nokta numaralarının tam representions olan sayılar,. Sabit nokta numaralarının aksine, kayan nokta sayıları bazen ("çoğu zaman" varsaymak güvenlidir) bir sayının tam bir temsilini döndüremeyecektir. 11.399999999999Sonuç olarak bununla sonuçlanmanızın nedeni budur 5.6 + 5.8.

Tam olarak 1.5 veya 150.1005 gibi bir değere ihtiyaç duyduğunuzda, sayıyı tam olarak temsil edebilecek sabit nokta türlerinden birini kullanmak istersiniz.

Zaten birkaç kez bahsedildiği gibi, Java BigDecimalçok büyük sayıları ve çok küçük sayıları işleyecek bir sınıfa sahiptir.

BigDecimalSınıfın Java API Başvurusu'ndan :

Değişmez, rasgele kesinlikli imzalı ondalık sayılar. BigDecimal, rasgele bir hassas tam sayı ölçeklenmemiş değer ve 32 bit tam sayı ölçeğinden oluşur. Sıfır veya pozitifse, ölçek ondalık virgülünün sağındaki basamak sayısıdır. Negatifse, sayının ölçeklendirilmemiş değeri, ölçeğin reddinin gücüyle on ile çarpılır. Bu nedenle BigDecimal tarafından temsil edilen sayının değeri (ölçeklenmemişDeğeri × 10 ^ ölçek) 'dir.

Kayan Taşma ile ilgili kayan nokta sayıları ve kesinliği ile ilgili birçok soru vardır. İlginizi çekebilecek ilgili soruların listesi:

Kayan nokta sayılarının gerçekten cesur ayrıntılarına inmek istiyorsanız, Her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler'e bir göz atın .


3
Aslında, genellikle 53 önemli bit vardır, çünkü "ondalık" noktadan önceki 1, denormalize edilmiş değerler hariç tümü için ima edilir ve ek bir hassasiyet verir. örneğin 3 (1.) 1000 ... x 2 ^ 1 olarak kaydedilirken 0.5 (1) 0000 ... x 2 ^ -1 olarak depolanır. Değer denormalize edildiğinde (tüm üs bitleri sıfırdır), ve genellikle daha az anlamlı basamak olacaktır, örneğin 1 x 2 ^ -1030 (0.) 00000001 x 2 ^ -1022 olarak saklanır, böylece ölçek için yedi önemli basamak feda edilir.
Sarah Phillips

1
Bu durumda BigDecimalolduğundan çok daha yavaş olsa da , çiftin double15 ondalık doğruluk yerine sahip olmasına gerek yoktur, sadece yuvarlamaya ihtiyacınız vardır.
Peter Lawrey

2
@PeterLawrey Hepsi ondalık basamaktan önce ise 15 ondalık basamaklı doğruluk rakamına sahiptir . Ondalık noktadan sonra, ondalık ve ikili kesirlerin ölçülemezliği nedeniyle her şey olabilir.
Lorne Marquis

@EJP Haklısınız, yaklaşık 15 önemli basamak hassasiyeti var. 16 olabilir ama 15 veya belki 14 olduğunu varsaymak daha güvenlidir.
Peter Lawrey

@PeterLawrey EJP'nin düzeltmesi sorumdan kaynaklanıyordu: stackoverflow.com/questions/36344758/… neden tam olarak 15 değil ve 16 veya 14 olabileceği durumları genişletebilir misiniz?
Shivam Sinha

103

Örneğin, bir çift sayı girdiğinizde 33.33333333333333, aldığınız değer aslında en yakın temsil edilebilir çift kesinlikli değerdir;

33.3333333333333285963817615993320941925048828125

Bunu 100'e bölmek:

0.333333333333333285963817615993320941925048828125

bu da çift kesinlikli bir sayı olarak gösterilemez, bu yüzden yine en yakın temsil edilebilir değere yuvarlanır, ki bu tam olarak:

0.3333333333333332593184650249895639717578887939453125

Bu değeri yazdırdığınızda, yine 17 ondalık basamağa yuvarlanır ve şunları verir:

0.33333333333333326

114
Gelecekte bunu okuyan ve cevabın neden soru ile ilgisi olmadığı konusunda şaşkın olan herkese: Bazı moderatörler ben (ve diğerleri) bu soru ile cevaplamış olan soruyu birleştirmeye karar verdiler, oldukça farklı bir soru.
Stephen Canon

Tam çift değeri nasıl biliyorsunuz?
Michael Yaworski

@mikeyaworski en.wikipedia.org/wiki/Double-precision_floating-point_format Çift kesinlikli örneklere bakın
Jaydee

23

Değerleri kesir olarak işlemek istiyorsanız, bir pay ve payda alanı tutan bir Kesir sınıfı oluşturabilirsiniz.

Toplama, çıkarma, çarpma ve bölme yöntemlerinin yanı sıra bir toDouble yöntemini yazın. Bu şekilde hesaplamalar sırasında dalgalanmaları önleyebilirsiniz.

EDIT: Hızlı uygulama,

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}

1
Kesinlikle numeratorve s denominatorolmalı int? Neden kayan nokta hassasiyeti istiyorsunuz?
Samir Talwar

Gerçekten gerekli değildir ama toDouble işlevinde dökümden kaçınır, böylece kod daha iyi okunur.
Viral Şah

5
ViralShah: Matematiksel işlemlerle uğraşırken potansiyel olarak kayan nokta hatası da getirir. Bu egzersizin amacının tam olarak bundan kaçınmak olduğu göz önüne alındığında, onu değiştirmek ihtiyatlı görünüyor.
Samir Talwar

Yukarıda Samir Talwar tarafından belirtilen nedenlerden dolayı çiftler yerine ints kullanmak için düzenlendi.
Viral Şah

3
Kesirlerin bu şekilde uygulanması, onları en basit bir biçime indirgemediği için sorunlara sahiptir. 2/3 * 1/2, cevabın 1/3 olmasını istediğiniz yere 2/6 verin. İdeal olarak kurucuda pay ve bölücünün gcd'sini bulmak ve her ikisini de bölmek istersiniz.
Salix alba

15

Sınırlı duyarlıklı ondalık aritmetik kullandıysanız ve 1/3: 0.333333333 * 3 ile 1.00000000 değil, 0.999999999 ile uğraşmak istiyorsanız, aynı soruna sahip olduğunuzu gözlemleyin.

Ne yazık ki, 5.6, 5.8 ve 11.4 sadece ikili sayılardan ibaret değildir, çünkü beşinci içerirler. Dolayısıyla, bunların şamandıra temsili tam değil, 0.3333'ün tam olarak 1/3 olmadığı gibi.

Kullandığınız tüm sayılar yinelenmeyen ondalık sayılarsa ve kesin sonuçlar istiyorsanız, BigDecimal kullanın. Veya başkalarının söylediği gibi, değerleriniz hepsi 0.01 veya 0.001'in katları veya bir şey olduğu anlamında paraya benziyorsa, her şeyi 10'luk sabit bir güçle çarpın ve int veya long kullanın (toplama ve çıkarma önemsiz: çarpmaya dikkat edin).

Ancak, hesaplama için ikilikten memnunsanız, ancak işleri biraz daha dostça bir biçimde yazdırmak istiyorsanız, java.util.Formatterveya öğesini deneyin String.format. Biçim dizesinde, bir çiftin tam hassasiyetinden daha düşük bir kesinlik belirtin. Örneğin, 11.399999999999, 11 önemli sayıya 11.4'tür, bu nedenle ikili sonucun sadece birkaç ondalık basamak gerektiren bir değere çok yakın olduğu durumlarda sonuç neredeyse aynı ve daha insan tarafından okunabilir olacaktır.

Belirlenecek hassasiyet, rakamlarınızla ne kadar matematik yaptığınıza bağlıdır - genel olarak ne kadar çok yaparsanız, o kadar çok hata birikir, ancak bazı algoritmalar diğerlerinden çok daha hızlı birikir ("kararsız" olarak adlandırılırlar) yuvarlama hataları ile ilgili olarak "durağan" a karşı). Yaptığınız tek şey birkaç değer eklemekse, sadece bir ondalık basamak hassasiyetini düşürmenin bir şeyleri çözeceğini tahmin ediyorum. Deney.


3
Hayır, yok değil parasal değerlerle çift kullanın! Parayla hassasiyete ihtiyacınız var, bunun yerine BigDecimal kullanın. Aksi takdirde, cevabınız iyidir. Hassasiyetle ihtiyacınız olan her şey, BigDecimal kullanın, hassasiyet o kadar önemli değilse, şamandıra veya çift kullanabilirsiniz.
MetroidFan2002

1
Soru artık paranın dahil olduğunu belirtmiyor veya ima etmiyor. Özellikle para için BigDecimal veya tamsayıları kullanmayı söylüyorum. Sorun ne?
Steve Jessop

1
Ve "para için çift kullanmayın", "BigDecimal veya üçte iki kullanmayın " anlamına gelir. Ancak bazen bir sorun, tüm paydaların tüm ana faktörleri tarafından bölünemeyen tüm üslerin eşit derecede kötü olduğu bölünmeyi içerir.
Steve Jessop

1
0,9999 = 1 hassasiyetiniz 4 anlamlı basamaktan azsa
Brian Leahy

9

Gerçekten hassas matematik gerekiyorsa, java.math.BigDecimal sınıfını kullanarak bakmak isteyebilirsiniz. İşte BigDecimal için Oracle / Sun'dan iyi bir makale . Birisi belirtildiği gibi 1/3 temsil asla mümkün olmakla birlikte, sen yapabilirsiniz sen sonuç olmak istiyorum tam olarak nasıl kesin karar yetkisine sahiptir. setScale () senin arkadaşın .. :)

Tamam, çünkü şu anda ellerimde çok fazla zamanım var, burada sorunuzla ilgili bir kod örneği:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

ve en sevdiğim dili Groovy'yi bağlamak için, aynı şeyin daha güzel bir örneği:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333

5

Bunu üç satırlı bir örnek haline getirebileceğinden eminim. :)

Kesin hassasiyet istiyorsanız, BigDecimal kullanın. Aksi takdirde, istediğiniz hassasiyeti 10 ^ ile çarparak ints kullanabilirsiniz.


5

Diğerlerinin de belirttiği gibi, ondalık değerlerin tümü ikili olarak gösterilemez, çünkü ondalık 10'un güçlerine ve ikili ikili ikisinin güçlerine dayanır.

Hassasiyet önemliyse, BigDecimal kullanın, ancak yalnızca kolay çıktı elde etmek istiyorsanız:

System.out.printf("%.2f\n", total);

Sana vereceğim:

11.40


5

Yapamazsınız, çünkü 7.3 ikili dosyada sınırlı bir temsile sahip değildir. En yakın 2054767329987789/2 ** 48 = 7.3 + 1/1407374883553280 olsun.

Daha fazla açıklama için http://docs.python.org/tutorial/floatingpoint.html adresine bakın . (Python web sitesinde, ancak Java ve C ++ aynı "soruna" sahip.)

Çözüm, sorunun tam olarak ne olduğuna bağlıdır:

  • Tüm bu gürültü basamaklarını görmekten hoşlanmıyorsanız, dize biçimlendirmenizi düzeltin. 15'ten fazla önemli basamak (veya kayan nokta için 7) görüntülemeyin.
  • Eğer sayılarınızın yanlış olması "if" ifadeleri gibi şeyleri kırıyorsa, if (x == 7.3) yerine if (abs (x - 7.3) <TOLERANCE) yazmalısınız.
  • Para ile çalışıyorsanız, muhtemelen gerçekten istediğiniz ondalık sabit noktadır. Tamsayı sayıda sent veya paranızın en küçük birimi ne olursa olsun depolayın.
  • (ÇOK BEKLENMEYEN) Hassasiyet için 53'ten fazla önemli bite (15-16 anlamlı basamak) ihtiyacınız varsa, BigDecimal gibi yüksek hassasiyetli bir kayan nokta türü kullanın.

7.3 ikili sonlu bir temsili olmayabilir, ama eminim ben C ++ aynı şeyi denemek -7.3 olsun 0
yanlış kullanıcı adı

2
yanlış kullanıcı adı: Hayır, bilmiyorsun. Sadece bu şekilde görüntüleniyor. Gerçek yanıtı görmek için "% .17g" biçimini (veya daha iyisi "% .51g") kullanın.
dan04 18:10

4
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}

3

Java.math.BigDecimal kullanın

Çiftler dahili olarak ikili kesirlerdir, bu nedenle bazen kesin ondalığa ondalık kesirleri temsil edemezler.


1
BigDecimal'i körü körüne tavsiye etmek için -1. Aslında ondalık aritmetiğe ihtiyacınız yoksa (yani para ile hesaplamalar yapıyorsanız), BigDecimal size yardımcı olmaz. Tüm kayan nokta hatalarınızı çözmez: Hala 1/3 * 3 = 0.9999999999999999999999999999 ve sqrt (2) ** 2 = 1.999999999999999999999999999 ile uğraşmak zorundasınız. Ayrıca, BigDecimal büyük bir hız cezası taşır. Daha da kötüsü, Java'nın operatör aşırı yüklenmesi nedeniyle, tüm kodunuzu yeniden yazmanız gerekir.
dan04

2
@ dan04 - Eğer para ile hesaplama yaparsanız neden içindeki hatayı bilerek kayan temsil kullanın .... sent hiçbir kesir olmadığından ondalık kullanabilirsiniz ve yaklaşık dolar kullanmak yerine sent hesaplayabilirsiniz tam yüzde tutarı var. Eğer gerçekten yüzde kısmını kullanmak istiyorsanız aa uzun ve binlerce sent hesaplayın. Dahası, OP irrasyonel sayılardan hiç bahsetmedi, endişelendiği tek şey toplama idi. Gönderiyi dikkatlice okuyun ve cevaplamadan önce sorunu anlayın, size biraz utanç verebilir.
Newtopian

3
@Newtopian: Utanacak hiçbir şeyim yok. OP yapılan YOK paranın söz, ne de onun sorunu herhangi doğasında decimalness sahip herhangi bir gösterge.
dan04

@ dan04 - Hayır OP yapmadım ... SİZ yaptı Ve Körü körüne sağlanan ayrıntıların zayıf miktarı göz önüne alındığında, büyük olasılıkla mükemmel kabul edilebilir bir cevap olduğu için bağlamsal görüş sundu
Newtopian

2

Her şeyi 100 ile çarpın ve uzun bir sentte saklayın.


2
@Draemon - son düzenlemeden önceki gönderiye bakın - "shoppingTotal" ve "calcGST" ve "calcPST" gibi şeyler bana para gibi geliyor.
Paul Tomblin

2

Bilgisayarlar sayıları ikili olarak depolar ve 33.333333333 veya 100.0 gibi sayıları tam olarak temsil edemez. Bu, çiftleri kullanmanın zor şeylerinden biridir. Bir kullanıcıya göstermeden önce yanıtı yuvarlamanız gerekir. Neyse ki çoğu uygulamada, o kadar çok ondalık basamağa ihtiyacınız yok.


Mümkün olan en yüksek hassasiyete sahip olmayı tercih edeceğim bazı olasılık hesaplamaları yapıyorum. Ama sınırlamalar olduğunu anlıyorum
Aly

2

Kayan nokta sayıları gerçek sayılardan farklıdır, çünkü herhangi bir kayan nokta numarası için bir sonraki daha yüksek kayan nokta sayısı vardır. Tamsayılarla aynı. 1 ile 2 arasında bir tam sayı yoktur.

Şamandıra olarak 1/3'ü temsil etmenin bir yolu yoktur. Altında bir şamandıra var ve üstünde bir şamandıra var ve aralarında belirli bir mesafe var. Ve 1/3 o alanda.

Apfloat for Java, keyfi hassas kayan nokta sayılarıyla çalıştığını iddia ediyor, ancak hiç kullanmadım. Muhtemelen bir göz atmaya değer. http://www.apfloat.org/apfloat_java/

Benzer bir soru burada Java kayan nokta yüksek hassasiyetli kütüphane önce soruldu


1

Çiftler, Java kaynağınızdaki ondalık sayıların yaklaşık değerleridir. İkili (ikili kodlu bir değerdir) ile kaynağınız (ondalık kodlu) arasındaki uyumsuzluğun sonucunu görüyorsunuz.

Java en yakın ikili yaklaşımı üretiyor. Daha iyi görünen bir ondalık değer görüntülemek için java.text.DecimalFormat öğesini kullanabilirsiniz.


1

Bir BigDecimal kullanın. Hatta yuvarlama kurallarını belirtmenize izin verir (her ikisi de aynı mesafede ise, hatta komşuya yuvarlayarak istatistiksel hatayı en aza indirecek ROUND_HALF_EVEN gibi; yani 1.5 ve 2.5 tur 2'ye).


1

Kısa cevap: Her zaman BigDecimal kullanın ve yapıcıyı String ile kullandığınızdan emin olun argümanı , çift olanı değil.

Örneğinize göre, aşağıdaki kod istediğiniz gibi 11.4 yazdırılacaktır.

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}

0

BigDecimal'i kontrol edin, kayan nokta aritmetiği ile ilgili problemleri ele alır.

Yeni çağrı şöyle görünecektir:

term[number].coefficient.add(co);

Kullanılacak ondalık basamak hassasiyetini ayarlamak için setScale () öğesini kullanın.


0

Neden Math sınıfından round () yöntemini kullanmıyorsunuz?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

0

Çift değer kullanmak dışında bir seçeneğiniz yoksa aşağıdaki kodu kullanabilirsiniz.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

-1

BigDecimal kullanarak gücünüzü boşa harcamayın. % 99.99999'da buna ihtiyacınız yoktur. java çift tip yaklaşık kurudur, ancak hemen hemen tüm durumlarda, yeterince hassastır. 14. önemli basamakta bir hatanız olduğunu unutmayın. Bu gerçekten ihmal edilebilir!

İyi çıktı kullanımı elde etmek için:

System.out.printf("%.2f\n", total);

2
Bence sayısal hassasiyetten değil çıktıdan endişe ediyor. ve BigDecimal, örneğin, yardımcı olmaz. üçe böl. Hatta işleri daha da kötüleştirebilir ...
Maciek D.

Asla para için kayan nokta kullanmamalısınız. Talimat verilmesine rağmen bu kuralı ihlal eden bir müteahhit üzerinde büyük bir yeniden çalışma yürüttüm.
Lorne Marquis
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.