2016-12-18 48 views
1

我試圖避免比較浮點類型的epsilon比較。我可以拿出最好的解決方案應用於ULPS(單位在最後的地方)的區別,雖然this article不得不使用整數表示一個更好的解決方案(///表明我自己的看法):使用別名與整數表示和ULP正確比較雙打

/* See 
https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/ 
for the potential portability problems with the union and bit-fields below. 
*/ 

#include <stdint.h> // For int32_t, etc. 

union Float_t 
{ 
    Float_t(float num = 0.0f) : f(num) {} 
    // Portable extraction of components. 
    bool Negative() const { return i < 0; } 
    int32_t RawMantissa() const { return i & ((1 << 23) - 1); } 
    int32_t RawExponent() const { return (i >> 23) & 0xFF; } 

    int32_t i; /// Perhaps overflow when using doubles? 
    float f; 

    #ifdef _DEBUG 
    struct 
    { // Bitfields for exploration. Do not use in production code. 
     uint32_t mantissa : 23; /// 52 for double? 
     uint32_t exponent : 8; /// 11 for double? 
     uint32_t sign : 1; 
    } parts; 
    #endif 
}; 

bool AlmostEqualUlps(float A, float B, int maxUlpsDiff) 
{ 
    Float_t uA(A); 
    Float_t uB(B); 

    // Different signs means they do not match. 
    if (uA.Negative() != uB.Negative()) 
    { 
     // Check for equality to make sure +0==-0 
     if (A == B) 
      return true; 
     return false; 
    } 

    // Find the difference in ULPs. 
    int ulpsDiff = abs(uA.i - uB.i); 
    if (ulpsDiff <= maxUlpsDiff) 
     return true; 

    return false; 
} 

不過,我可以似乎不會以支持雙打的方式重新格式化代碼。我甚至讀到了解釋,found here

有誰知道什麼是解決這個問題的最好方法?


在任何人決定,以紀念這是一個重複:不這樣做,因爲只有相似的問題是爲那些JavaScript和C++的回答是:

bool IsAlmostEqual(double A, double B) 
{ 
    //http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm 
    long long aInt = reinterpret_cast<long long&>(A); 
    if (aInt < 0) aInt = -9223372036854775808LL - aInt; 
    long long bInt = reinterpret_cast<long long&>(B); 
    if (bInt < 0) bInt = -9223372036854775808LL - bInt; 
    return (std::abs(aInt - bInt) <= 10000); 
} 

哪個沒有按不使用ULP,但有一些綁定,我不確定什麼-9223372036854775808LL - aInt是什麼(可能在int64溢出)。

+0

我建議你嘗試在你的評論中提到的修改,它們看起來合乎邏輯。讓我們看看它出錯的地方,並嘗試修復它。 –

+0

@ A.S.H應用我的評論,我得到錯誤:'錯誤C2034'double_t :: mantissa':位字段的類型對於位數太小。因此,我將尾數設置爲'uint64_t',並且一切順利,但符號不正確地爲'1',而我將1.0與1.0進行比較。 '1.0'的十六進制值似乎是正確的:'0x3ff0000000000000',但它由指數保存?所以我把尾數和指數設置爲'uint64_t',它似乎工作。我還錯過了什麼嗎? – Nikita

+0

它們都應該是uint64_t,因爲它們表示64位「double」的位域。我想你也改變了'f'和'i'的類型分別爲double和int64_t,'RawMantissa'和'RawExponent'等的差別等。 –

回答

1

我不認爲你的代碼工作。下面是一個錯誤的例子。 (這不是一個答案,但對此的解釋是太長,無法進入評論)

int main() 
{ 
    Float_t x; 
    Float_t y; 
    x.i = 0x7F800000; 
    y.i = 0x7F7FFFFF; 
    AlmostEqualUlps(x.f, y.f, 1); // return true 
} 

然而,x.f實際上是無窮大,y.fFLT_MAX。任何定義的區別都是無限的。除非這是你想要的行爲,否則這就是有限數和無窮大幾乎相等。 ULP的實施完全不合格。事實上,對於上述兩個數字,ULP甚至沒有很好的定義。

另一個例子是0x7F800000(或任何與此相關的數字,取決於您的maxULP)和0x7F800001(NaN)。與上面的例子不同,甚至沒有一個論點認爲他們應該被認爲是「幾乎相等」。

另一方面,您拒絕任何具有不同符號的對不夠接近,而事實上-FLT_MINFLT_MIN之間存在很多的低於正常水平,這可能被認爲是「幾乎相等」。例如,0x80000001和0x1因2ULP而不同,但如果在函數中將maxULP設置爲2,則它將返回false。

如果您可以排除非正常,無限度,NaN,然後​​處理雙倍你只需要替換uint32_tuint64_t如其他人的評論中所述。

+0

我發現[IEE 754-1985](https://en.wikipedia.org/wiki/IEEE_754-1985#Examples)的值,​​對於[IEE 754-2008](https://en.wikipedia) .ORG /維基/單precision_floating-point_format#單precision_examples)。雖然我會想象vC++使用後者,我應該忽略前一個,對吧? – Nikita

+0

@Byrk這不是關於兩個標準的區別。你無法處理IEEE浮點的特殊值。指數設置爲全1的數字,但顯着零爲無窮大,非零是NaN。指數設置爲零的數字是子正常數。正常數字在MSB中有一個隱含的1。 x86中的80位long double沒有隱式1,而其他一些平臺有不同的long double。這些是計算ULP時需要考慮的事情 –

+0

關於比較浮點數的話題,有人指出,當你關閉到零時,'float.h'中定義的'FLT_EPSILON'變得有用,這是否是一種簡單的方法處理例外情況,還是我必須對'NaN'等進行單獨檢查? – Nikita