2011-07-26 149 views
15

當我開始使用相等運算符比較兩個浮點值時,我使用的代碼審查工具與下面的投訴。什麼是正確的方式,以及如何做到這一點?是否有幫助功能(commons- *),我可以重複使用?使用==運算符比較浮點數/雙精度值

說明

無法比擬的使用equals(==)運算符

說明

通過使用平等浮點值進行比較(==)浮點值或不等式(!=)運算符由於舍入誤差而不總是準確的。

建議

比較兩個浮點值,看看它們是否值相近。

float a; 
float b; 

if(a==b) 
{ 
.. 
} 
+4

作爲一個側面說明,如果您使用'float'且涉及金錢(或者通常表示爲小數的任何數字),您應該考慮'BigDecimal'。 –

+0

好評。我意識到這一點,我使用BigDecimal所有貨幣的東西。但這是其他的一切。 –

+0

使用java.lang.Float.compare(float1,float2); //返回整數值,但不能用來代替float1 == float2在if條件中 –

回答

30

IBM has a recommendation用於比較兩個浮動,使用除法 - 而不是減法 - 這可以更容易地選擇適用於所有範圍的epsilon的輸入。

if (abs(a/b - 1) < epsilon) 

至於小量的價值,我會用5.96e-08作爲this Wikipedia table給出的,或者是2倍該值。

+7

如果'b == 0'怎麼辦? –

+1

@DrewNoakes,好問題。你可以用if(abs(b)<= 1.18e-38)檢查這個條件,如果它是真的,那麼檢查'a'是否相同。 –

+2

Ick。代碼不僅非常緩慢,而且在很多情況下也很容易出錯。如果有人正在比較x到(y-z),容差需要基於y和z的大小,而不是差異的大小,並且上面的代碼沒有辦法考慮這一點。 x和y之間的精確的相等測試通常是合適的,例如,一個已經計算出f(x)並且想知道是否應該計算f(y)[或者簡單地使用f(x)]的值。不幸的是,精確的平等測試通常很困難,儘管在大多數情況下'=='運算符已經足夠好了。 – supercat

6
private static final float EPSILON = <very small positive number>; 

if (Math.abs(a-b) < EPSILON) 
    ... 

您作爲浮點提供可變的,但無法控制的精度(即,你不能設置,當你使用doublefloat之間做出選擇以外的精度),你必須選擇自己的固定精度進行比較。

請注意,這不是一個真正的等價操作符,因爲它不是傳遞。你可以很容易地得到a等於bb等於c但是a不等於c

編輯:也請注意,如果a爲負,b是一個非常大的正數,減法可以溢出,結果將是負無窮大,但測試仍然可以工作,爲負無窮大的絕對值正無窮大,這將大於EPSILON

+0

你能解釋一下EPSILON的實際價值嗎? –

+1

個人將EPSILON定義爲應用領域特定的最大容限。如果被比較的數字非常大,它實際上可能是一個相當大的數字! :) – Affe

+0

@Affe正確。它將取決於應用程序本身。 – biziclop

9

它希望你能夠在你需要的精度內進行比較。例如,如果你需要,你的花車第4張十進制數是相等的,那麼你可以使用:

if(-0.00001 <= a-b && a-b <= 0.00001) 
{ 
.. 
} 

或者:

if(Math.abs(a-b) < 0.00001){ ... } 

當你添加所需的精度兩者的區別數字並將其與兩倍的期望精度比較。

無論你怎麼想,它都更具可讀性。我更喜歡第一個,因爲它清楚地顯示了你在雙方允許的精度。

a = 5.43421b = 5.434205將通過比較

+0

如果'a'爲2且'b'爲3,則第一次比較將失敗(正確),但第二次比較將通過(錯誤地)。 – StriplingWarrior

+1

@StriplingWarrior,你是對的。我的意思是'0''' – Paulpro

3

使用commons-郎

org.apache.commons.lang.math.NumberUtils#compare 

而且公地數學(在您的情況更合適的解決方案):

http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double) 
+0

該函數不會緩解他的工具正在引起的擔憂。 – Affe

+0

@Affe哪一個?比較 - 同意;在commons-math中相當於其他答案的狀態,MathUtils中也提供了很酷的重載版本。 – Andrey

+0

是的,MathUtils#equals中使用的方法通常是最好的解決方案,除非應用程序域定義更粗糙的東西。第一條評論是在你的編輯前回答:) – Affe

2

float類型是大約值 - 有一個指數部分和有限精度的值部分。
例如:

System.out.println((0.6/0.2) == 3); // false 

的風險是一個很小的舍入誤差可以做一個對比false,當數學應該true

的解決方法是比較花車允許微小的差別仍然是「平等」:

static float e = 0.00000000000001f; 
if (Math.abs(a - b) < e) 

阿帕奇commons-math救援:MathUtils.(double x, double y, int maxUlps)

返回true,如果兩個參數相等或在允許的錯誤範圍(包括)。如果兩個浮點數之間存在(maxUlps - 1)(或更少)浮點數,則認爲兩個浮點數相等,即認爲兩個相鄰的浮點數相等。

2

下面是實際的代碼形式下議院數學執行:

private static final int SGN_MASK_FLOAT = 0x80000000; 

public static boolean equals(float x, float y, int maxUlps) { 
    int xInt = Float.floatToIntBits(x); 
    int yInt = Float.floatToIntBits(y); 

    if (xInt < 0) 
     xInt = SGN_MASK_FLOAT - xInt; 

    if (yInt < 0) 
     yInt = SGN_MASK_FLOAT - yInt; 

    final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps; 

    return isEqual && !Float.isNaN(x) && !Float.isNaN(y); 
} 

這使您可以在兩個值之間在目前的規模表示花車的數量,這應該工作不是絕對的好小量。

1

我根據java實現==雙打的方式對此進行了刺探。它首先轉換爲IEEE 754長整數形式,然後進行按位比較。 Double還提供了static doubleToLongBits()來獲取整數形式。使用位擺動可以通過添加1/2(一位)和截斷來「舍入」雙精度的尾數。

與supercat的觀察一致,該函數首先嚐試一個簡單的==比較,如果失敗則僅進行四捨五入。這是我想出了一些(希望)有用的評論。

我做了一些有限的測試,但不能說我已經嘗試過所有的邊緣情況。另外,我沒有測試性能。它不應該太糟糕。

我剛剛意識到這與Dmitri提供的解決方案基本相同。也許更簡潔一點。

static public boolean nearlyEqual(double lhs, double rhs){ 
    // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers) 
    // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). 
    // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL<<n 
    // e.g. 4 bits are: additive: 0x10L = 0x1L << 4 and mask: 0xffffffffffffffe0L = 0xffffffffffffffffL << 5 
    //int bitsToIgnore = 5; 
    //long additive = 1L << (bitsToIgnore - 1); 
    //long mask = ~0x0L << bitsToIgnore; 
    //return ((Double.doubleToLongBits(lhs)+additive) & mask) == ((Double.doubleToLongBits(rhs)+additive) & mask); 
    return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0xffffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0xffffffffffffffe0L); 
} 

以下修改處理中標誌的情況下的變化,其中所述值是0

return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L); 
0

任一側有其中一個想要把兩個浮點數字作爲等於許多情況下只如果它們完全相同,並且「三角洲」比較將是錯誤的。例如,如果f是一個純函數),並且知道q = f(x)和y === x,那麼應該知道q = f(y)而不必計算它。不幸的是==在這方面有兩個缺陷。

  • 如果一個值是正零,另一個是負零,他們會比較平等,即使他們並不一定等同。例如,如果f(d)= 1/d,則a = 0且b = -1 * a,則a == b但是f(a)!= f(b)。

  • 如果其中任何一個值是NaN,則即使一個值直接從另一個值指定,比較也將始終產生錯誤。

雖然在許多情況檢查浮點數爲一個確切的是正確和恰當的很多情況下,我不知道在哪裏的==實際行爲應被視爲最好的任何情況。可以說,所有的等價測試都應該通過一個實際測試等價性的函數來完成(例如通過比較按位形式)。

1

首先,要注意以下幾點:

  • 「標準」的方式來做到這一點是選擇一個常數ε,但對於所有的數字範圍不斷epsilons無法正常工作。
  • 如果你想使用一個常數epsilon sqrt(EPSILON) epsilon從float.h的平方根通常被認爲是一個很好的值。 (這是來自臭名昭着的「橙皮書」,現在這個名字讓我不知所措)。
  • 浮點除法將是緩慢的,所以你可能要避免它的比較,即使它的行爲就像在撿被定製爲數字大小做了小量。

你真的想要做什麼?像這樣:
比較多少可表示的浮點數的值不同。

此代碼來自this布魯斯道森真正偉大的文章。該文章自更新here。主要區別在於舊文章打破了嚴格別名規則。 (將浮點指針轉換爲int指針,解引用,寫入,轉換)。儘管C/C++純粹主義者會很快指出這個缺陷,但實際上這是有效的,我認爲代碼更具可讀性。但是,新文章使用工會,C/C++保持其尊嚴。爲了簡潔起見,我給出了下面打破嚴格別名的代碼。

// Usable AlmostEqual function 
bool AlmostEqual2sComplement(float A, float B, int maxUlps) 
{ 
    // Make sure maxUlps is non-negative and small enough that the 
    // default NAN won't compare as equal to anything. 
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); 
    int aInt = *(int*)&A; 
    // Make aInt lexicographically ordered as a twos-complement int 
    if (aInt < 0) 
     aInt = 0x80000000 - aInt; 
    // Make bInt lexicographically ordered as a twos-complement int 
    int bInt = *(int*)&B; 
    if (bInt < 0) 
     bInt = 0x80000000 - bInt; 
    int intDiff = abs(aInt - bInt); 
    if (intDiff <= maxUlps) 
     return true; 
    return false; 
} 

在上面的代碼的基本思路是,鑑於IEEE 754浮點格式,{sign-bit, biased-exponent, mantissa},如果解釋爲符號的振幅整數的數字字典順序的第一通知。這就是符號位變成了符號位,並且指數總是在定義浮點數的尾數時完全超過尾數,並且因爲它首先確定被解釋爲整數的數量的大小。

因此,我們將浮點數的位表示解釋爲符號幅度int。然後,如果數字爲負數,則將有符號數的整數轉換爲二進制補碼整數,並將它們從0x80000000中減去。然後,我們只是比較兩個值,就像我們對任何帶有二進制補碼的符號進行比較,並查看它們之間有多少個值。如果這個數量小於你選擇多少浮點數的閾值,這些數值可能會有所不同,但仍然被認爲是相等的,那麼你就說它們​​是「相等的」。請注意,此方法正確地讓「相等」的數字對較大幅度的浮點數的值較大,對於較小幅度的浮點數的值較小。