2012-06-05 24 views
10

一些比較C代碼和F#我試圖來取代它,我觀察到有最終結果有所不同。C雙打與.NET雙打不同嗎?

工作備份代碼,我發現即使在有些差異 - 儘管很小。

代碼從讀取文件中的數據開始。而第一個數字則不同。例如,在F#(更容易腳本):

let a = 71.9497985840 
printfn "%.20f" a 

我得到預期的(對我來說)輸出71.94979858400000000000

但在C:

a = 71.9497985840; 
fprintf (stderr, "%.20f\n", a); 

打印出71.94979858400000700000

在這情況下7從何而來?

區別只是很小,但它困擾我,因爲我不知道爲什麼。 (它也困擾我,因爲它使得我更難追查我的兩個版本的代碼在哪裏發散)

+0

你確定這兩個值是雙重的嗎?你確定錯誤不在打印? – Euphoric

+1

.net中的浮點運算沒有明確定義的精確結果。這只是一個近似值。編譯器和JITer的意想不到的更低的數字可能會發生變化,只是用'printfn'來觀察它們會影響它們。 – CodesInChaos

+0

使用調試器或其他東西來查看變量的確切位模式。如果您使用相同的浮點格式和變量的F#和C實現都具有完全相同的位模式,則差異在於打印或編譯器如何解析浮點文字。無論哪種情況,由於浮點數只是近似值,所以可能不需要擔心,因爲它們之間的差異很小。 –

回答

7

這是打印時的差異。該值轉換爲IEEE754 double產生

Prelude Text.FShow.RealFloat> FD 71.9497985840 
71.94979858400000694018672220408916473388671875 

但表示71.949798584足以從它的鄰居區分開的數量。 C,當要求以20位精度來打印小數點轉換正確地舍入到的數字的期望數量的值之後,顯然F#使用最短唯一地確定表示與焊盤它以0的期望數量,只是象Haskell確實。

+0

因此,如果我對這些數字列表執行多項操作,這些差異是否會開始變得顯着?我正在努力研究如何測試,看看是否是這種情況... – Benjol

+0

這取決於。我認爲.NET爲JITter提供了相當大的餘地,因此結果不一定會在.NET代碼的不同運行中重現。如果C實現不聲稱遵循IEC 60559,它也有很大的餘地。問題在於,C和.NET是否對具有相同輸入的相同函數產生相同的結果。如果他們這樣做,你只會觀察到字符串表示的差異,它只出現在確定'double'值所需的部分之後,沒有任何東西可以累積。如果他們在結果中不存在1或2個ULP的差異... –

+0

可以積累。但是除非你做了很多操作(或者一些不良操作),否則這些差異將會很小。 –

3

這只是不同的舍入。的數字是相同的(根據CPython的,至少):

>>> '%.44f' % 71.94979858400000000000 
'71.94979858400000694018672220408916473388671875' 
>>> '%.44f' % 71.94979858400000700000 
'71.94979858400000694018672220408916473388671875' 
0

其他答案充分解釋的問題(雙精度和舍入)的源極。

如果你的數字一般都是中等大小,小數精度非常重要(比計算速度還快),那麼也許考慮使用.NET decimal格式。這給你28-29精確的小數位精度,不會出現像double這樣的小數二進制舍入誤差。限制是範圍較小(沒有大的指數!)。

http://msdn.microsoft.com/en-us/library/364x0z75%28v=vs.100%29.aspx

3

它是是差,即轉換雙爲字符串的方法中的.NET System.Double.ToString()方法。您可以通過下載SSCLI20中提供的CLR源來查看相關的代碼。該轉換是通過CLR/SRC/VM/comnumber.cpp,COMNumber :: FormatDouble()函數來完成。這看起來like this,在代碼中的註釋是最描述發生了什麼事情的:

//In order to give numbers that are both friendly to display and round-trippable, 
//we parse the number using 15 digits and then determine if it round trips to the same 
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits 
//and display that. 

C運行時庫不具有該功能。

+0

感謝Hans,這是一個相當確定的答案。我肯定會在我通過我的算法進行累積錯誤時,現在我只需要嘗試跟蹤確切的位置和原因......目前無法解決,如果它是這種浮點事物,或者我的純粹錯誤碼。 – Benjol

0

任何人都在這個問題上陷入困境的進一步信息。

使用找到的代碼位here,我已經確認(我相信)聲明底層二進制表示(至少對於這個特定的數字)是相同的。

下面是代碼示例 - 注意'乘以零'以消除負零 - 當轉換爲長時很醜陋。

//(C# this time) 
var d = 71.9497985840; //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
var longval = System.BitConverter.DoubleToInt64Bits(d); // = 4634763433907061836 

在C:

double d; 
long long a; 
d = 71.9497985840;  //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
a = *(long long*)&d; //= 4634763433907061836 

更新 - 我也跟着通過,並發現了矩陣求逆過程中引入的差異,因爲每個系統叫了一個不同的庫,實現反轉一種不同的方式...