2012-01-09 67 views
49

爲什麼此代碼在.NET 4中打印False?看起來一些意想不到的行爲是由顯式演員造成的。將結果投入浮動返回float的結果更改結果

我想要一個超出「浮點數不準確」或「不這樣做」的答案。

float a(float x, float y) 
{ 
    return (x * y); 
} 

float b(float x, float y) 
{ 
    return (float)(x * y); 
} 

void Main() 
{ 
    Console.WriteLine(a(10f, 1f/10f) == b(10f, 1f/10f)); 
} 

PS:此代碼來自單元測試,而不是發佈代碼。代碼是故意寫的。我懷疑它最終會失敗,但我想知道究竟何時以及爲什麼。答案證明了這種技術的有效性,因爲它提供的理解超出了對浮點確定性的通常理解。這就是以這種方式編寫代碼的要點。慎重探索。

PPS:單元測試路過在.net 3.5,但現在升級到.NET 4

+9

根據定義,浮點變量不是100%準確的。他們是近似數字。 http://msdn.microsoft.com/en-us/library/b1e65aza.aspx請參閱此帖以及:http://stackoverflow.com/questions/618535/what-is-the-difference-between-decimal-float -and-double-in-c 不能保證它們在運行時的任何*版本中都是相同的。 – David 2012-01-09 21:42:28

+0

另一個有用的鏈接:http://msdn.microsoft.com/en-us/library/ms187912.aspx – David 2012-01-09 21:45:53

+4

儘管如此,這是一個有趣的問題。在功能上,人們會期望這些方法A和B執行相同的操作,即使使用浮點近似。我有興趣聽到解釋。 – 2012-01-09 21:45:58

回答

76

大衛的評論是正確的,但不夠強後失敗。不能保證在相同的程序中做兩次計算將產生相同的結果。

C#的規範是在這一點上非常清楚:


浮點運算可以用比的結果類型的操作更高的精度來進行。例如,某些硬件體系結構支持比double類型更寬的範圍和精度的「extended」或「long double」浮點類型,並隱式執行使用此更高精度類型的所有浮點運算。只有在性能成本過高的情況下,這種硬件體系結構才能以更低的精度執行浮點運算,而不是要求實現放棄性能和精度,C#允許更高精度的類型用於所有浮點運算。除了提供更精確的結果外,這很少有任何可衡量的影響。然而,在表達式x * y/z中,乘法產生的結果超出了雙倍範圍,但隨後的除法將臨時結果重新帶回雙倍範圍,因此表達式以更高範圍格式進行評估的事實可能會導致將產生有限的結果而不是無窮大。


C#編譯器,抖動和運行時都具有廣闊的lattitude給你更準確的結果比規範要求,在任何時候,在一個突發奇想 - 它們不是必需的選擇一貫這樣做,事實上他們不這樣做。

如果你不喜歡,那麼不要使用二進制浮點數;要麼使用小數或任意精度的理由。

我不明白爲什麼鑄造漂浮在返回浮法方法使得它確實

良好的出發點的差異。

您的示例程序演示了小的更改會導致大的影響。您注意到在某些版本的運行時中,明確地轉換爲浮動會產生不同於不這樣做的結果。當您明確強制轉換爲浮點時,C#編譯器會提示運行時說「如果您恰好使用此優化,請將其從超高精度模式中取出」。正如規格說明,這有潛在的性能成本。

這樣做恰好輪到「正確答案」只是一個幸福的事故;得到正確的答案是因爲在這種情況下,失去了精確度,因此在正確的方向上丟失了。

.net 4與.NET 4有何不同?

你問3.5和4.0運行時間有什麼區別;差別很明顯,在4.0中,抖動選擇在您的特定情況下選擇更高的精度,並且3.5抖動選擇不會。這並不意味着這種情況在3.5中是不可能的;它已經可以在運行時的每個版本和C#編譯器的每個版本中使用。你碰巧碰到一個案例,在你的機器上,他們的細節有所不同。但抖動有始終允許進行此優化,並始終如此。

在編譯時計算常量浮點數時,C#編譯器也完全有權選擇進行類似的優化。根據編譯器運行時狀態的細節,常量中兩個看起來相同的計算可能會有不同的結果。更一般地說,你期望浮點數應該具有實數的代數性質,這與現實完全不符;他們沒有這些代數性質。浮點運算甚至不是聯想;他們當然不會像你期望的那樣服從乘法反演的定律。浮點數只是實數算術的近似值;近似值足夠接近,例如模擬物理系統或計算摘要統計量或某些此類事物。

+6

不幸的是,我已經知道浮點數是不準確的。我不知道的是關於鑄造和優化的一切。如果我知道答案,提出正確的問題會更容易。 – 2012-01-09 22:45:37

+2

我不喜歡將'不準確'一詞應用於浮點數字。 '(float)1'是100%準確的。加上小整數和乘以2的冪乘以正常結果也是100%準確的。這個例子中的差異不是'不準確'計算的結果,但是(正如Eric解釋的那樣)是語言和運行時自由選擇某些實現細節的自由。 – 2012-01-10 00:21:37

+1

@JeffreySax:不管你想怎麼稱呼它,這都是一回事。而這正是很多人錯誤地或不完全將這個問題歸因於此的原因。問題的關鍵是要去「其他的東西」。它編輯了這個問題和答案。我希望沒有人提到浮點'準確性'的基礎知識,因爲這個問題(正如Eric解釋的那樣)比通常所知的浮點「準確性」更復雜。這就是爲什麼僅僅關於浮點'精度'的評論仍然被提升。 – 2012-01-10 03:26:44

0

我現在沒有Microsoft編譯器,Mono也沒有這樣的效果。 據我所知GCC 4.3+ uses gmp and mpfr to calculate some stuff in compile time。 C#編譯器可以對同一個程序集中的非虛擬,靜態或私有方法執行相同的操作。顯式強制轉換可能會干擾這種優化(但我沒有看到爲什麼它不能有相同的行爲)。即它可以通過計算常量表達式來計算某個級別(對於b()它可能是例如直到演員)。

海灣合作委員會也有優化,促進操作更高的精度,如果這是有道理的。

所以我會考慮優化作爲潛在的原因。但是對於他們兩個人來說,我沒有理由爲什麼做明確的結果轉換可能會有一些額外的含義,比如「接近標準」。