2017-06-26 50 views
7

下面的C程序在我的Mac和Linux上產生不同的結果。我驚訝,因爲我認爲的libm實施以某種方式標準化Mac和Linux上exp函數的結果略有不同

#include<math.h> 
#include<stdio.h> 

int main() 
{ 
    double x18=-6.899495205106946e+01; 
    double x19 = exp(-x18); 
    printf("x19  = %.15e\n", x19); 
    printf("x19 hex = %llx\n", *((unsigned long long *)(&x19))); 
} 

在Mac上的輸出是

x19  = 9.207186811339878e+29 
x19 hex = 46273e0149095886 

,並在Linux上

x19  = 9.207186811339876e+29 
x19 hex = 46273e0149095885 

兩個不使用任何優化彙編標誌如下:

gcc -lm .... 

我知道我永遠不應該比較漂浮在一起的漂浮物。

在調試過程中出現了這個問題,令人遺憾的是使用這種計算證明的算法在數值上不穩定,這種細微的差異導致最終結果出現明顯的偏差。但這是一個不同的問題。

我只是驚訝,因爲exp等基本操作不規範,我可以期待通過IEEE 754

規定的基本代數運算是否有關於精密任何假設我可以依靠的不同實現libm爲不同的機器或不同的版本?


由於討論下面我用mpmath計算值比機器精度更高,我得到兩個數字,結果9.2071868113398768244,所以我的兩個結果的最後數字已經是錯誤的。在Linux上的結果可以通過舍入這個值來解釋,如果計算機使用四捨五入,Mac的結果也是關閉的。

+1

那麼......標準操作列在https://en.wikipedia.org/wiki/IEEE_754-1985#Standard_operations。你在問什麼? –

+0

對不起,增加了額外的句子。 – rocksportrocker

+1

順便說一句,您可以使用'%a'格式說明符在十六進制中打印「精確」浮點值。 –

回答

2

的C99規格狀態(其他版本應該是相似的):

J.3實現定義的行爲

1一致的實現需要 記錄其選擇行爲的在每個本小節列出的 區域。以下是實現定義:

...

J.3.6浮點

- 浮點運算的庫函數和 在<math.h><complex.h>返回的準確性 浮點結果(5.2.4.2.2)。

含義GNU libm和BSD libm可以自由設置不同級別的準確性。 可能發生的事情是,OSX上的BSD實現向最近的單元(最後一個單元)ULP捨去,GNU實現截斷爲下一個ULP。

+1

而IEEE-754標準只需要對加法,減法,乘法,除法和平方根進行正確舍入(誤差<= 0.5 ULP)。其他操作不需要正確舍入。這兩個結果在1 ULP以內,所以在這種情況下,誤差<= 1 ULP。 – casevh

+0

我使用'mpmath'指出錯誤在mac上超過1 ULP時,用更精確的結果擴展了我的問題。 – rocksportrocker

+0

@rocksportrocker請參閱下面的答案。它有更多的細節。 – casevh

1

IEEE-754行爲在二進制級別指定。使用Linux,我可以得到相同的Python值本地math庫,mpmath和MPFR(通過gmpy2)。但是,在三種方法之間轉換爲小數。

>>> import mpmath, gmpy2 
>>> import mpmath, gmpy2, math 
>>> x18=68.99495205106946 
>>> x19=math.exp(x18) 
>>> mp18=mpmath.mpf("68.99495205106946") 
>>> mp19=mpmath.exp(mp18) 
>>> gp18=gmpy2.mpfr("68.99495205106946") 
>>> gp19=gmpy2.exp(gp18) 
>>> x18 == mp18 
True 
>>> x18 == gp18 
True 
>>> x19 == mp19 
True 
>>> x19 == gp19 
True 
>>> print(x18, mp18, gp18) 
68.99495205106946 68.9949520510695 68.994952051069461 
>>> print(x19, mp19, gp19) 
9.207186811339876e+29 9.20718681133988e+29 9.2071868113398761e+29 

轉換爲Python的任意精度整數形式後,所有三個結果也顯示爲精確。

>>> hex(int(x19)) 
'0xb9f00a484ac42800000000000' 
>>> hex(int(mp19)) 
'0xb9f00a484ac42800000000000' 
>>> hex(int(gp19)) 
'0xb9f00a484ac42800000000000' 

所以(至少一個)Linux的數學庫,mpmathgmpy2.mpfr同意。

聲明:我保留gmpy2並在過去貢獻mpmath

+0

值得注意的是,IEEE-754不是C標準(或C++)的一部分,也不是Linux所要求的(鑑於OSX可以合法運行的受限硬件可能安全地說它的實現將使用IEEE- 754)。 – dlasalle

+0

@dlasalle確實如此。雖然IEEE-754不是必需的,但GNU libc會嘗試遵守IEEE-754-2008標準,因此許多Linux系統將符合標準。但這不是一個安全的假設。如果您的應用程序依賴於可重複的結果,包括超越函數,那麼您應該使用單獨的庫,例如MPFR。 – casevh

相關問題