2012-01-10 61 views
14

我在找到why a test case is failing舍入差

有問題的測試可以降低到這樣做(4.0/9.0) ** (1.0/2.6),舍入該6位數字和檢查對一個已知值(字符串):

#include<stdio.h> 
#include<math.h> 
int main(){ 
    printf("%.06f\n", powf(4.0/9.0, (1.0/2.6))); 
} 

如果我編譯和GCC 4.1.2在Linux上運行,我得到:

0.732057 

Python的同意,一樣Wolfram|Alpha

$ python2.7 -c 'print "%.06f" % (4.0/9.0)**(1/2.6)' 
0.732057 

不過,我得到以下結果上GCC 4.4.0在Linux和4.2.1 OS X:

0.732058 

一個double行爲相同(雖然我沒有廣泛測試此)

我不知道如何進一步縮小這個..這是一個gcc迴歸?舍入算法的改變?我做一些愚蠢的事情?

編輯:打印結果12位,在第7位的位數是4比5,這解釋了舍入的差異,而不是值差:

GCC 4.1.2:

0.732057452202 

GCC 4.4.0:

0.732057511806 

下面是兩個版本gcc -S輸出:https://gist.github.com/1588729

+0

要刪除'printf()'的行爲與變量不同,請打印或檢查包含該值的內存位,以便刪除故事中的「轉換爲字符串」部分。 – unwind 2012-01-10 12:17:51

+3

這兩者之間的區別恰恰是32位處理器的[機器epsilon](http://en.wikipedia.org/wiki/Machine_epsilon)。此外gcc4.4的結果更接近表達式的實際值。 – 2012-01-10 12:44:05

回答

8

最近的gcc版本能夠使用mfpr編譯時間浮點計算。我的猜測是,你最近的gcc做到了這一點,並在編譯時使用更高的精度。這是由至少C99標準允許和結果

在C99

6.3.1.8/2

浮動操作數的值(我沒有在,如果它被修改其他一眼看去)的浮動表達式可以是 ,其以比該類型所要求的更高的精度和範圍表示;類型不是 由此而改變。

編輯:你的gcc -S結果證實了這一點。我還沒有檢查的計算,但老之一具有

movss 1053092943, %xmm1 
movss 1055100473, %xmm0 
call powf 

調用函數powf與預計算值4/9.0和1/2.6,然後打印推廣後的結果(其恆定內容代存儲器之後)加倍,而新的只打印浮動0x3f3b681f晉升爲雙。

+0

有趣的是,這聽起來像是原因!你能詳細說明反彙編中的內容嗎? (一件事 - 舊gcc4.1.2可能用於mfpr - 它的結果與其他來源相匹配,包括Daniel的答案) – dbr 2012-01-10 13:57:07

+0

@dbr,請參閱更新。它是否滿足您的需求? – AProgrammer 2012-01-10 14:18:15

3

這裏有很多玩家。 Gcc很可能只是將計算轉發給您的浮點處理器;你可以檢查反彙編。

您可以檢查用二進制表示法(來自同一wolfram/alpha)二進制結果:

float q=powf(4.0/9.0, (1.0/2.6)); 
unsigned long long hex=*reinterpret_cast<unsigned long long*>(&q); 
unsigned long long reference=0x1f683b3f; 
assert(hex==reference); 

而且printf是一個可能的罪魁禍首:這個數字的十進制表示可能是問題了。你可以嘗試寫printf("%0.06f", 0.73205748);來測試。

1

只需打印更多(全部)有效數字,您應該能區分舍入不同的格式和提供不同答案的數學。

如果在未發生舍入時看起​​來相同,則printf("%0.6f"只是四捨五入。


OK,一箇舊的Linux + Python環境我有手,我得到:

Python 2.4.3 (#1, Jun 11 2009, 14:09:37) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> (4.0/9.0)**(1.0/2.6) 
0.7320574847636997 

這又是不同的。

也許問一個問題會更簡單一些,有多少有意義的數字對這個單元測試真的很重要?

+0

已更新答案,打印爲12位數值 - 第7位數值在4和5之間變化,因此printf總是舍入爲 – dbr 2012-01-10 12:08:54

+0

您的結果將循環到gcc 4.1.2結果'round(0.7320574847636997,6)== 0.732057 '。單元測試理想情況下精確到6位數,因爲默認情況下,這個數字是以LUT格式存儲的,但是我會看看我是否可以將其減少到5 – dbr 2012-01-10 14:09:43

4

我覺得老版本的gcc使用了double。這樣做在Haskell計算和打印結果全精度,我得到

Prelude Text.FShow.RealFloat> FD ((4/9) ** (1/2.6)) 
0.73205748476369969512944635425810702145099639892578125 
Prelude Text.FShow.RealFloat> FF ((4/9) ** (1/2.6)) 
0.732057511806488037109375 

所以double結果與生產什麼GCC-4.1.2和float結果什麼GCC-4.4.0不同意。結果gcc-4.5.1在這裏產生float resp。 double與Haskell結果一致。

作爲程序員引用,編譯器被允許使用更高的精度,舊的gcc做到了,新的顯然沒有。

+0

這很好地說明了區別 - 謝謝! – dbr 2012-01-10 14:03:46

+1

+1因此,對於較新的編譯器,這是一個功能,而不是一個錯誤? – 2012-01-11 03:02:35