這樣的討論的例子還有: here, here, here, here 和 here.(它們都是值得一讀的,從其他人的錯誤中學習要比從自己當中少痛苦一點)
我們將通過展示另外一個簡單的例子來開始我們的解答。
for (double r=0.0; r<1e22; r+=1.0) printf(".");
這個程式將會列印多少個點呢?這次很清楚,不是嗎?這次的終止條件不再是等於測試。這個迴圈將會在10^22遍曆後停止。或者。。是嗎?
真糟糕,這又一次的成了死迴圈。為什麼會這樣呢?因為當r這個值變得大了,這個變數的精度並不能夠足夠大的儲存所有r的十進位下的數。最後一些會丟失掉。因此當我們在這麼大的一個數後加1,結果又被近似到原來的數了。
練習:試著判斷一下我們這個迴圈裡r所能達到的最大值。檢查你的答案,如果你的判斷出錯了,找出它的原因。
在作出觀察之後,我們將顯示為什麼運算式fabs(a-b)<eps教程ilon(使用固定值epsilon,推薦在1e-7到1e-9)對於比較雙精確度數並不是理想的。
考慮值123456123456.1234588623046875和123456123456.1234741210937500。它們都沒什麼特別之處,僅僅是兩個double可以在不進行近似的情況下就能儲存的兩個值。他們直接的差大約在2e-5.
現知讓我們看看這兩個值的位形式:
first: 01000010 00111100 10111110 10001110 11110010 01000000 00011111 10011011
second: 01000010 00111100 10111110 10001110 11110010 01000000 00011111 10011100
是這樣的,沒錯。這是在double中可以被儲存的兩個連續的值。任何使用近似得到的錯誤都能夠使得其中對一個變成另外一個(或者超過)。但是他們仍然是不一樣的,因此我們原始的測試“相等”不能夠工作。
我們真正需要的是容忍一些小的精度誤差。正如我們所看到的,double能夠近似儲存最多15個10進位數位數。通過近似積聚的精度錯誤,最後一些數字將會丟失。但是我們究竟應該怎樣容忍這些誤差呢?
我們將不使用固定常數ε,而使用一個與比較的數數量級相關的值。更確切點說,如果x是一個雙精確度數,那麼x*1e-10是一個比x小10倍數量級的數。它的最高有效位對應於x的第11位最高有效位。這使得它能夠很好的滿足我們的需要。
換句話說,一個更好的方式來比較a,b兩個雙精確度數是否“相等”就是檢查a是否在b*(1-1e-10)和b*(1+1e-10)之間。(小心,如果b是負數的時候,這兩個數中的第一個將會更大!)
看到用這樣的比較方式的問題了嗎?試著比較1e-1072和-1e1072.這兩個數都機會相等並且等於0,但是我們的測試在處理這種情況時會失敗。這就是為什麼我們既需要做第一個測試(測試絕對誤差)和第二個測試(測試相對誤差)。
這就是tc中用來檢查你的傳回值是否正確的方法。現在你知道原因了。
有更好的比較函數(參見其中的一篇參考文章),但是更重要的是要知道在實際中你經常僅僅使用絕對誤差測試而僥倖成功。為什嗎?
因為包含在計算當中的數字都是在限定的範圍之內。例如,如果我們需要比較的最大數只是9947,那麼你知道一個double能夠在十進位小數點後儲存另外的11個數字。因此我們在進行絕對誤差測試時使用epsilon=1e-8,我們運行最後的3個數字丟掉。
這個方法的優點很明確:檢查絕對誤差要比上面的進階測試簡單。
elections (a div2 easy with a success rate of only 57.58%)
archimedes
sortestimate (the binary search is quite tricky to get right if you don't understand precision issues)
perforatedsheet (beware, huge rounding errors possible)
watchtower
packingshapes