c# - F# 在非确定性浮点计算方面是否受到相同的 C# 警告?

标签 c# f# floating-point language-lawyer non-deterministic

C# 浮点代码的结果可能导致不同的结果。

这个问题不是 关于为什么 0.1 + 0.2 != 0.3以及浮点机器数固有的不精确性。

这与具有相同目标体系结构(例如 x64)的相同 C# 代码可能会根据所使用的实际机器/处理器导致不同结果的事实相关联。

这个问题与这个问题直接相关:Is floating-point math consistent in C#? Can it be? ,其中讨论了 C# 问题。

供引用,this paragraph在 C# 规范中明确说明了这种风险:

Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an "extended" or "long double" floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type. Only at excessive cost in performance can such hardware architectures be made to perform floating-point operations with less precision, and rather than require an implementation to forfeit both performance and precision, C# allows a higher precision type to be used for all floating-point operations. Other than delivering more precise results, this rarely has any measurable effects



事实上,我们实际上经历了一个~ 1e-14仅使用 double 的算法中的数量级差异,并且我们担心这种差异会传播到使用该结果的其他迭代算法,等等,使得我们的结果不能始终如一地再现我们在我们领域(医学成像研究)的不同质量/法律要求。

C# 和 F# 共享相同的 IL 和公共(public)运行时,但是,据我了解,它可能更多是由编译器驱动的,这对于 F# 和 C# 是不同的。

我觉得还不够精明,无法理解问题的根源是否是双方共同的,或者如果 F# 有希望,我们是否应该跳入 F# 来帮助我们解决这个问题。

TL;博士

C# 语言规范中明确描述了这种不一致问题。我们没有在 F# 规范中找到等效项(但我们可能没有在正确的位置进行搜索)。

F# 在这方面是否有更多的一致性?

即,如果我们切换到 F#,是否可以保证在跨架构的浮点计算中获得更一致的结果?

最佳答案

简而言之; C# 和 F# 共享相同的运行时,因此以相同的方式进行浮点数计算,因此当涉及到浮点数计算时,您将在 F# 中看到与 C# 中相同的行为。
0.1 + 0.2 != 0.3的问题跨越大多数语言,因为它来自二进制浮点数的 IEEE 标准,其中 double是一个例子。在二进制浮点数中,0.1、0.2 等无法精确表示。这就是一些语言支持像 0x1.2p3 这样的十六进制浮点字面量的原因之一。可以精确地表示为二进制浮点数(0x1.2p3 等于 9 btw 在十进制数系统中)。

很多依赖 double 的软件内部像 Microsoft Excel 和 Google Sheet 使用各种作弊来使数字看起来不错,但通常在数字上不是正确的(我不是专家,我只是读了一点 Kahan)。

在 .NET 和许多其他语言中,通常有一个 decimal十进制浮点数的数据类型,确保 0.1 + 0.2 = 0.3是真的。但是,它不保证 1/3 + 1/3 = 2/31/3不能在十进制数系统中精确表示。因为没有硬件支持 decimal它们往往更慢,此外 .NET decimal不符合 IEEE 标准,这可能是也可能不是问题。

如果你有分数并且你有很多可用的时钟周期,你可以使用 BigInteger 实现一个“大理性”。在 F# 中。然而,分数迅速增长得非常大,它不能代表评论中提到的第 12 个根,因为根的结果通常是无理数(即不能表示为有理数)。

我想您可以象征性地保留整个计算并尝试尽可能长时间地保留精确值,然后非常仔细地计算最终数字。可能很难做到正确,而且很可能很慢。

我读过一点 Kahan (他共同设计了 8087 和 IEEE 浮点数标准),根据其中一篇论文,我读到了一种实用的方法来检测浮点数引起的舍入误差是计算三次。

一次是正常的舍入规则,然后是总是向下舍入,最后是总是向上舍入。如果最后数字相当接近,则计算可能是合理的。

根据 Kahan 的说法,像“棺材”之类的可爱想法(对于每个浮点运算产生一个范围而不是给出最小值/最大值的单个值)只是不起作用,因为它们过于悲观,你最终会得到无限大的范围。这当然符合我在执行此操作的 C++ boost 库中的经验,而且速度也很慢。

因此,当我过去使用 ERP 软件时,从我读到的 Kahan 建议我们应该使用小数来消除像 0.1 + 0.2 != 0.3 这样的“愚蠢”错误。但要意识到还有其他错误来源,但在计算、存储和能力水平上消除它们超出了我们的范围。

希望这可以帮助

PS。这是一个复杂的话题,我曾经在某个时候更改框架时遇到过回归错误。我深入研究了它,发现错误来自旧框架中的抖动使用旧式 x86 FPU 指令,而在新抖动中它依赖于 SSE/AVX 指令。切换到 SSE/AVX 有很多好处,但丢失的一件事是旧式 FPU 指令在内部使用 80 位浮点数,并且只有当浮点数离开 FPU 时,它们才四舍五入为 64 位,而 SSE/AVX 使用 64 位在内部,这意味着框架之间的结果不同。

关于c# - F# 在非确定性浮点计算方面是否受到相同的 C# 警告?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61915551/

相关文章:

c# - 为什么 KeyEvent 中的数字小键盘数字和常规数字不一样?

c - 如何正确实现 float 乘法(软件FP)

ruby - Ruby 中 float 的一致舍入

c# - 使用反射获取通用列表始终为空

c# - 从代码注释中引用 TFS 工作项

c# - 在控制台应用程序中随时捕获用户 key

f# - 如何在 C#/F# MVC4 中的 F# 文件中使用断点?

f# - F# 中的显式类型递归

F# 联合案例参数化

c - 如何从字节数组读取/写入浮点值?