2008-12-10 22 views
34

Java'da iki katına eşit, daha az ve daha büyük eşitliği sınamak için bir sınıf yazdım. Genel durumum, yarım sent doğruluğu olan fiyatı karşılaştırıyor. 59.005 ile 59.395 arasında. Bu davalar için yeterli olan epsilon'u seçtiniz mi?Java çift karşılaştırması epsilon

private final static double EPSILON = 0.00001; 


/** 
* Returns true if two doubles are considered equal. Tests if the absolute 
* difference between two doubles has a difference less then .00001. This 
* should be fine when comparing prices, because prices have a precision of 
* .001. 
* 
* @param a double to compare. 
* @param b double to compare. 
* @return true true if two doubles are considered equal. 
*/ 
public static boolean equals(double a, double b){ 
    return a == b ? true : Math.abs(a - b) < EPSILON; 
} 


/** 
* Returns true if two doubles are considered equal. Tests if the absolute 
* difference between the two doubles has a difference less then a given 
* double (epsilon). Determining the given epsilon is highly dependant on the 
* precision of the doubles that are being compared. 
* 
* @param a double to compare. 
* @param b double to compare 
* @param epsilon double which is compared to the absolute difference of two 
* doubles to determine if they are equal. 
* @return true if a is considered equal to b. 
*/ 
public static boolean equals(double a, double b, double epsilon){ 
    return a == b ? true : Math.abs(a - b) < epsilon; 
} 


/** 
* Returns true if the first double is considered greater than the second 
* double. Test if the difference of first minus second is greater then 
* .00001. This should be fine when comparing prices, because prices have a 
* precision of .001. 
* 
* @param a first double 
* @param b second double 
* @return true if the first double is considered greater than the second 
*    double 
*/ 
public static boolean greaterThan(double a, double b){ 
    return greaterThan(a, b, EPSILON); 
} 


/** 
* Returns true if the first double is considered greater than the second 
* double. Test if the difference of first minus second is greater then 
* a given double (epsilon). Determining the given epsilon is highly 
* dependant on the precision of the doubles that are being compared. 
* 
* @param a first double 
* @param b second double 
* @return true if the first double is considered greater than the second 
*    double 
*/ 
public static boolean greaterThan(double a, double b, double epsilon){ 
    return a - b > epsilon; 
} 


/** 
* Returns true if the first double is considered less than the second 
* double. Test if the difference of second minus first is greater then 
* .00001. This should be fine when comparing prices, because prices have a 
* precision of .001. 
* 
* @param a first double 
* @param b second double 
* @return true if the first double is considered less than the second 
*    double 
*/ 
public static boolean lessThan(double a, double b){ 
    return lessThan(a, b, EPSILON); 
} 


/** 
* Returns true if the first double is considered less than the second 
* double. Test if the difference of second minus first is greater then 
* a given double (epsilon). Determining the given epsilon is highly 
* dependant on the precision of the doubles that are being compared. 
* 
* @param a first double 
* @param b second double 
* @return true if the first double is considered less than the second 
*    double 
*/ 
public static boolean lessThan(double a, double b, double epsilon){ 
    return b - a > epsilon; 
} 
+3

Burada bazı insanların gazabını uyandırdınız! Kayan nokta sayılarını gerçekten kullanmak istiyorsanız buraya bakın: http://docs.sun.com/source/806-3568/ncg_goldberg.html – Loki

+2

Yinelenen diğer sorunlar, çoğaltılmış kodu kaldırarak kodlama hatası olasılığını azaltır. İlk statik yöntem dönüş eşittir (a, b, EPSILON); – nslntmnx

+3

Sadece güzelce konuşmak, a == b? true: x', çok daha güzel ve okunması kolay bir versiyonla değiştirilebilir. a == b || x'. – Matthias

cevap

10

Evet. Java ikilileri, hassaslık derecenizi 0,00001 verilen veriminizden daha iyi tutacaktır.

Kayan nokta değerlerinin depolanması nedeniyle oluşan herhangi bir yuvarlama hatası 0.00001'den küçük olarak gerçekleşir. Java'da çift epsilon için 1E-6 veya 0.000001'i sorunsuz olarak kullanıyorum.

İlgili bir notta, epsilon = 1E-5; biçimini beğendim çünkü daha okunabilir olduğunu hissediyorum (Java'da 1E-5 = 1 x 10^-5). Kod okurken 1E-6'yı 1E-5'den ayırt etmek kolaydır; 0,00001 ve 0,000001 kodlara bakarken benzer görünmektedir, aynı değerde olduklarını düşünüyorum.

7

Whoa whoa whoa. Para birimi için kayan nokta kullanmanın belirli bir nedeni var mı, yoksa bir arbitrary-precision, fixed-point number format ile işler daha iyi mi olacak? Çözmeye çalıştığınız özel problemin ne olduğu hakkında hiçbir fikrim yok, ama yarım sentin çalışmak istediğiniz gerçek bir şey olup olmadığını ya da sadece kesin olmayan bir sayı biçimi kullanmanın bir ürünü olup olmadığını düşünmelisiniz. Parayı göstermek için çifte KULLANMAYIN.

90

Asla. Bunun yerine java.math.BigDecimal kullanın.

Daha sonra, yuvarlama işleminin tam olarak nasıl yapılacağını belirtebilirsiniz (bu, bazen mali uygulamalarda yasalarca belirlenir!) Ve bu epsilon şeyi gibi aptal korsanlık yapmak zorunda değilsiniz.

Cidden, parayı temsil etmek için kayan nokta türleri kullanmak son derece profesyonelce değildir.

+54

+1 aslında parayı temsil etmek için kayan nokta sayıları kullanmazsınız çünkü -1 (bu yüzden sayımı değiştirmedim) çünkü bir epsilon kullanmak neredeyse bir "aptal hack" değildir. Bilimsel hesaplamalarda temel bir şey, "aptal bir hack" değil. Goldberg'in konuyla ilgili makalesi bu konuda hemfikir. – SyntaxT3rr0r

+47

Cidden, bunun herşeyin en iyi yolu olduğu şeyleri yaptığınız için bunu varsaymamalısınız. Dört farklı bankada çalışmış olduğum için, BigDecimal'i kullanan bir ticaret sistemini hiç görmedim, ya da bunları kullanmanızı tavsiye ederim. –

+2

Peter, bunun yerine para için ne önerirsiniz? Benim tercihim uzun olurdu. Para sınıfı için kısa vadeli kombinasyon. Ancak durum için kendi başıma dönmeyi çok tereddüt ediyorum. Bunu daha önce yaptım ... ama bunun işe yaradığını kanıtlayabileceğim bir şey değil. – monksy

1

Kayan nokta sayıları yalnızca çok fazla anlamlı basamağa sahiptir, ancak daha yüksek gidebilirler. Uygulamanız çok sayıda işlem yapacaksa, epsilon değerinin farklı olması gerektiğini fark edeceksiniz.

+ 0,001 = 0,002 0,001 ama 12.345.678.900.000.000.000.000 + 1 = 12.345.678.900.000.000.000.000 yüzen noktası ve çift kullanıyor ise. Bu sistemde bir milyon dolardan fazla işlemeyeceğinizden emin değilseniz, paranın iyi bir temsili değildir.

+0

Kayan nokta, değeri içsel olarak 2^üs * (1 + kesir) olarak kaydettiğinden, 0,1 gibi değerleri temsil etmez. 0,001 + 0,001 gibi makul bir aralıkta bile. Perl varsa "print int (1.13 * 100.0)/100.0" komutunu çalıştırın. 1.12 değerini döndürür. –

1

Cents? Para değerlerini hesaplıyorsanız, float değerlerini kullanmamalısınız. Para aslında sayılabilir değerlerdir. Kuryeler veya kuruşlar vb. Bir tamsayıya ait iki (veya her neyse) en az anlamlı sayı olarak kabul edilebilir. Para değerlerini tam sayı olarak kaydedebilir ve hesaplayabilir ve 100'e bölebilirsiniz (örneğin, son iki basamaktan önce yer işareti veya iki virgül). senin epsilon doğruluğunu tanımlamak gerekiyordu eğer şamandıra en Neyse ... garip yuvarlama hatalarına yol

açabilir, görünüşe çok küçük bir bit (çok doğru) ...

5

para ile ilgili iseniz Para tasarım desenini kontrol etmenizi öneririm (başlangıçta Martin Fowler's book on enterprise architectural design).

Ben motivasyon için bu bağlantıyı okumanızı öneririz: Para için kötü çift düşüncesiyle kabul ederken http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

+2

Moredesignpatterns sunucusunun kaybolduğu ve değiştirilmediği görünüyor. Makale archive.org adresinde olsa da: http://web.archive.org/web/20090105214308/http://wiki.moredesignpatterns.com/space/Value%2BObject%2BMotivation%2Bv2 –

2

, hala karşılaştıran çift fikri ilgi duymaktadır. Özellikle önerilen epsilon kullanımı sadece belirli bir aralıktaki sayılara uygundur.İki sayının oranına göre bir epsilon'un daha genel bir kullanımı (0 için test dışı bırakılmıştır):

boole eşit (çift d1, çift d2) { çift d = d1/d2; dönüş (Math.abs (d-1.0) < 0.001); Eğer BigDecimal kullanabiliyorsa }

+1

Sıfırdan dolayı çok tehlikeli bölünme. – lethalman

+0

Gerçekten de, "0.000001" ve "0" bu kodla eşit olmayacaktı. – Joey

4

, o zaman başka, bunu kullanın:

/** 
    *@param precision number of decimal digits 
    */ 
public static boolean areEqualDouble(double a, double b, int precision) { 
    return Math.abs(a - b) <= Math.pow(10, -precision); 
} 
+0

Bu Double.compare (Math.abs (a-b), Math.pow (10, -precision)) değil mi? – Michael

0

Diğer commenters doğru belirtildiği gibi, asla kullanım kayan nokta aritmetik kesin değerler böyle gelince, gerekli gerekirken parasal değerler. Asıl sebep, gerçekten de kayan noktaların içselleştirdiği yuvarlama davranışıdır, ancak kayan noktalarla uğraşmanın da sonsuz ve NaN değerleriyle uğraşmak anlamına geldiğini unutmayalım.

Bir yaklaşımın sizin yaklaşımınızın işe yaramadığı bir örnek olarak, burada basit bir test kodu var. Ben sadece senin EPSILON10.0 eklemek ve sonuç 10.0 eşit olup olmadığını bakmak - fark açıkça azEPSILON geride olduğu, olması gereken örnek olarak:

double a = 10.0; 
    double b = 10.0 + EPSILON; 
    if (!equals(a, b)) { 
     System.out.println("OK: " + a + " != " + b); 
    } else { 
     System.out.println("ERROR: " + a + " == " + b); 
    } 

Sürpriz:

ERROR: 10.0 == 10.00001 

İki kayan nokta değeri farklı üslere sahipse, çıkarmada önemli bitler varsa, kayıp nedeniyle hatalar oluşur.

diğer commenters tarafından önerildiği gibi daha gelişmiş "göreli fark" yaklaşımı uygulanması düşünüyorsanız, bu yaklaşımın benzer eksiklikleri olduğunu gösteriyor Bruce Dawson mükemmel makale Comparing Floating Point Numbers, 2012 Edition, okumalısınız ve aslında hiçbir süreli kesilmesine var tüm kayan noktalı sayı aralıklarında çalışan güvenli yaklaşık kayan nokta karşılaştırması.

Bir şeyleri kısaltmak için: Parasal değerler için double s arasından emin olun ve BigDecimal gibi tam sayı gösterimlerini kullanın. Verimlilik uğruna, aşırı ve yetersiz akışları güvenilir bir şekilde engellediğiniz sürece "millis" (sentlerin onda biri) olarak yorumlanan longs'u da kullanabilirsiniz. Bu, çoğu gerçek dünyadaki uygulamalar için yeterli olan 9'223'372'036'854'775.807'un maksimum temsil edilebilir değerlerini verir.