簡體   English   中英

強制浮點數在 .NET 中具有確定性?

[英]Coercing floating-point to be deterministic in .NET?

我已經閱讀了很多關於 .NET 中浮點確定性的內容,即確保具有相同輸入的相同代碼將在不同機器上給出相同結果。 由於 .NET 缺少像 Java 的 fpstrict 和 MSVC 的 fp:strict 這樣的選項,因此共識似乎是使用純托管代碼無法解決這個問題。 C# 游戲 AI Wars 已決定改用定點數學,但這是一個麻煩的解決方案。

主要問題似乎是 CLR 允許中間結果存在於精度高於類型的本機精度的 FPU 寄存器中,從而導致不可預測的精度更高的結果。 CLR 工程師 David Notario 的一篇 MSDN 文章解釋了以下內容:

請注意,根據當前的規范,它仍然是提供“可預測性”的語言選擇。 該語言可能會在每個 FP 操作之后插入 conv.r4 或 conv.r8 指令以獲得“可預測”的行為。 顯然,這真的很昂貴,不同的語言有不同的妥協。 例如,C# 什么都不做,如果要縮小范圍,則必須手動插入 (float) 和 (double) 強制轉換。

這表明可以通過為每個計算結果為浮點的表達式和子表達式插入顯式強制轉換來實現浮點確定性。 人們可能會圍繞 float 編寫一個包裝器類型來自動執行此任務。 這將是一個簡單而理想的解決方案!

然而,其他評論表明它並不那么簡單。 Eric Lippert 最近表示(強調我的):

在運行時的某些版本中,顯式轉換為 float 會給出與不這樣做不同的結果。 當您顯式轉換為浮點數時,C# 編譯器會提示運行時說“如果您碰巧正在使用此優化,請將其從超高精度模式中移除”。

這個運行時的“提示”是什么? C# 規范是否規定顯式轉換為 float 會導致在 IL 中插入 conv.r4? CLR 規范是否規定 conv.r4 指令會導致值縮小到其本機大小? 只有當這兩個都為真時,我們才能依賴顯式強制轉換來提供浮點“可預測性”,正如 David Notario 所解釋的那樣。

最后,即使我們確實可以將所有中間結果強制轉換為類型的本機大小,這是否足以保證跨機器的可重復性,或者是否還有其他因素,如 FPU/SSE 運行時設置?

這個運行時的“提示”是什么?

正如您推測的那樣,編譯器會跟蹤源代碼中是否實際存在向雙精度或浮點數的轉換,如果存在,它總是插入適當的轉換操作碼。

C# 規范是否規定顯式轉換為 float 會導致在 IL 中插入 conv.r4?

不,但我向您保證,編譯器測試用例中有單元測試可以確保它確實如此。 雖然規范沒有要求它,但您可以依賴這種行為。

該規范的唯一評論是,任何浮點運算都可能以比運行時所要求的精度更高的精度完成,並且這可以使您的結果出乎意料地更加准確。 見第 4.1.6 節。

CLR 規范是否規定 conv.r4 指令會導致值縮小到其本機大小?

是的,在第 I 部分的第 12.1.3 節中,我注意到您可以自行查找,而不是要求互聯網為您查找。 這些規范在網絡上是免費的。

您沒有問過但可能應該問的問題:

除了強制轉換之外,還有什么操作可以將浮點數從高精度模式中截斷嗎?

是的。 分配給double[]float[]數組的靜態字段、實例字段或元素會被截斷。

一致的截斷是否足以保證跨機器的可重復性?

不。我鼓勵您閱讀第 12.1.3 節,其中有很多關於非正規數和 NaN 的有趣內容。

最后,另一個你沒有問但可能應該問的問題:

我如何保證可重復的算術?

使用整數。

8087 浮點單元芯片設計是英特爾的十億美元錯誤。 這個想法在紙面上看起來不錯,給它一個 8 寄存器堆棧,以擴展精度,80 位存儲值。 這樣您就可以編寫中間值不太可能丟失有效數字的計算。

然而,野獸是不可能優化的。 將 FPU 堆棧中的值存儲回內存是很昂貴的。 因此,將它們保留在 FPU 內是一個強大的優化目標。 不可避免的是,如果計算足夠深,只有 8 個寄存器將需要回寫。 它也被實現為堆棧,而不是可自由尋址的寄存器,因此也需要可能產生回寫的體操。 不可避免地,寫回會將值從 80 位截斷回 64 位,從而失去精度。

因此,結果是非優化代碼不會產生與優化代碼相同的結果。 當中間值最終需要寫回時,對計算的微小更改可能會對結果產生很大的影響。 /fp:strict 選項是一個黑客,它強制代碼生成器發出回寫以保持值一致,但不可避免地會損失大量性能。

這是一塊完整的岩石和堅硬的地方。 對於 x86 抖動,他們只是沒有嘗試解決問題。

英特爾在設計 SSE 指令集時沒有犯同樣的錯誤。 XMM 寄存器可自由尋址且不存儲額外位。 如果您想要一致的結果,那么使用 AnyCPU 目標和 64 位操作系統進行編譯是快速的解決方案。 x64 抖動使用 SSE 而不是 FPU 指令進行浮點數學運算。 盡管這增加了計算可以產生不同結果的第三種方式。 如果計算是錯誤的,因為它丟失了太多有效數字,那么它將始終是錯誤的。 這確實有點像溴化物,但通常僅就程序員而言。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM