29

我知道浮点计算存在准确性问题,并且有很多问题可以解释原因。我的问题是,如果我两次运行相同的计算,我是否可以始终依靠它来产生相同的结果?哪些因素可能会影响这一点?

  • 计算之间的时间?
  • CPU的当前状态?
  • 不同的硬件?
  • 语言/平台/操作系统?
  • 太阳耀斑?

我有一个简单的物理模拟,并想记录会话,以便可以重播。如果可以依赖计算,那么我应该只需要记录初始状态以及任何用户输入,并且我应该总是能够准确地重现最终状态。如果计算不准确,开始时的错误可能会在模拟结束时产生巨大影响。

我目前在 Silverlight 工作,但很想知道这个问题是否可以得到普遍回答。

更新: 最初的答案表明是的,但显然这并不完全明确,正如所选答案的评论中所讨论的那样。看来我将不得不做一些测试,看看会发生什么。

4

10 回答 10

24

据我了解,只要您处理相同的指令集和编译器,并且您运行的任何处理器都严格遵守相关标准(即 IEEE754),您就可以保证获得相同的结果。也就是说,除非您正在处理一个特别混乱的系统,否则运行之间的任何计算偏差都不太可能导致错误行为。

我知道的具体问题:

  1. 某些操作系统允许您以破坏兼容性的方式设置浮点处理器的模式。

  2. 浮点中间结果通常在寄存器中使用 80 位精度,但在内存中仅使用 64 位。如果以更改函数内的寄存器溢出的方式重新编译程序,则与其他版本相比,它可能会返回不同的结果。大多数平台都会为您提供一种强制将所有结果截断为内存精度的方法。

  3. 标准库函数可能会因版本而异。我收集到在 gcc 3 vs 4 中有一些不常见的例子。

  4. IEEE 本身允许一些二进制表示不同......特别是 NaN 值,但我不记得细节了。

于 2008-11-30T08:51:32.353 回答
20

简短的回答是,根据IEEE 浮点标准,FP 计算是完全确定的,但这并不意味着它们在机器、编译器、操作系统等之间完全可重现。

这些问题的详细答案以及更多问题可以在可能是关于浮点的最佳参考资料中找到,David Goldberg 的What Every Computer Scientist Should Know About Floating Point Arithmetic。跳至 IEEE 标准部分了解关键细节。

简要回答您的要点:

  • 计算之间的时间和 CPU 的状态与此无关。

  • 硬件会影响事物(例如,某些 GPU 不符合 IEEE 浮点)。

  • 语言、平台和操作系统也会影响事物。有关此问题的更好描述,请参阅 Jason Watkins 的回答。如果您使用的是 Java,请查看 Kahan关于 Java 的浮点数不足的咆哮

  • 太阳耀斑可能很重要,希望不经常发生。我不会太担心,因为如果它们确实很重要,那么其他一切都会搞砸。我会将其与担心EMP 归为一类。

最后,如果您在相同的初始输入上执行相同的浮点计算序列,那么事情应该完全可以重播。确切的顺序可能会根据您的编译器/操作系统/标准库而改变,因此您可能会遇到一些小错误。

通常在浮点中遇到问题的地方是,如果您有一个数值不稳定的方法,并且您从 FP 输入开始,这些输入大致相同但不完全相同。如果您的方法稳定,您应该能够在一定的公差范围内保证重现性。如果您想了解更多细节,请查看上面链接的 Goldberg 的 FP 文章或获取有关数值分析的介绍性文本。

于 2008-11-30T08:33:04.713 回答
8

我认为您的困惑在于浮点周围的不准确性类型。大多数语言都实现了IEEE 浮点标准该标准阐述了如何使用浮点/双精度中的各个位来生成数字。通常一个浮点数由一个四个字节和一个双八字节组成。

两个浮点数之间的数学运算每次都将具有相同的值(如标准中指定的那样)。

不准确来自精确。考虑一个 int 与一个 float。两者通常占用相同数量的字节 (4)。然而,每个数字可以存储的最大值却大相径庭。

  • int:大约 20 亿
  • 浮动:3.40282347E38(有点大)

区别在中间。int,可以表示 0 到大约 20 亿之间的每个数字。浮动但是不能。它可以表示 0 到 3.40282347E38 之间的 20 亿个值。但这留下了一系列无法​​表示的值。如果数学方程达到这些值之一,则必须将其四舍五入为可表示的值,因此被认为是“不准确的”。您对不准确的定义可能会有所不同:)。

于 2008-11-30T08:38:49.493 回答
4

此外,虽然Goldberg是一个很好的参考资料,但原文也是错误的:IEEE754 并不保证可移植。鉴于此陈述基于略读文本的频率,我无法强调这一点。该文档的更高版本包括专门讨论此问题的部分

许多程序员可能没有意识到,即使是仅使用 IEEE 标准规定的数字格式和操作的程序也可以在不同的系统上计算出不同的结果。事实上,该标准的作者打算允许不同的实现获得不同的结果。

于 2008-11-30T09:01:17.170 回答
2

对不起,但我不禁认为每个人都没有抓住重点。

如果不准确性对您正在做的事情很重要,那么您应该寻找不同的算法。

你说如果计算不准确,那么一开始的错误可能会在模拟结束时产生巨大的影响。

我的朋友不是模拟的。如果由于舍入和精度导致的微小差异,您得到的结果大不相同,那么很可能没有任何结果具有任何有效性。仅仅因为您可以重复结果并不能使其更有效。

在包括测量或非整数计算在内的任何重要的现实世界问题上,引入小错误以测试算法的稳定性始终是一个好主意。

于 2008-11-30T09:12:07.973 回答
2

C++ FAQ 中的这个答案可能描述得最好:

http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

不仅不同的体系结构或编译器可能会给您带来麻烦,浮点数在同一个程序中的行为也已经很奇怪了。正如常见问题解答指出的那样,如果y == x是真的,那仍然可能意味着这cos(y) == cos(x)将是错误的。这是因为 x86 CPU 使用 80 位计算值,而该值以 64 位存储在内存中,因此您最终将截断的 64 位值与完整的 80 位值进行比较。

计算仍然是确定性的,因为每次运行相同的编译二进制文件都会给您相同的结果,但是当您稍微调整源代码时,优化标志或使用不同的编译器编译它所有的赌注都没有了可以发生。

实际上,我并没有那么糟糕,我可以在 32 位 Linux 上使用不同版本的 GCC 逐位重现简单的浮点指向数学,但是当我切换到 64 位 Linux 时,结果不再相同。在 32 位上创建的演示录音不能在 64 位上运行,反之亦然,但在同一架构上运行时可以正常工作。

于 2009-06-17T16:46:39.180 回答
2

由于您的问题被标记为 C#,因此值得强调 .NET 上面临的问题:

  1. 浮点数学不是关联的——也就是说,(a + b) + c不能保证等于a + (b + c)
  2. 不同的编译器将以不同的方式优化您的代码,这可能涉及重新排序算术运算。
  3. 在 .NET 中,CLR 的 JIT 编译器将即时编译您的代码,因此编译取决于运行时机器上的 .NET 版本。

这意味着,您不应依赖 .NET 应用程序在不同版本的 .NET CLR 上运行时产生相同的浮点计算结果。

例如,在您的情况下,如果您记录模拟的初始状态和输入,然后安装更新 CLR 的服务包,您的模拟可能不会在您下次运行时重放相同。

请参阅 Shawn Hargreaves 的博客文章浮点数学是确定性的吗?有关 .NET 的进一步讨论。

于 2011-04-19T06:57:21.750 回答
1

嗯。由于 OP 要求使用 C#:

C# 字节码 JIT 是确定性的,还是在不同的运行之间生成不同的代码?我不知道,但我不会相信Jit。

我可以想到 JIT 具有一些服务质量特性并决定在优化上花费更少时间的场景,因为 CPU 在其他地方进行大量的数字运算(想想背景 DVD 编码)?这可能会导致细微的差异,并可能在以后导致巨大的差异。

此外,如果 JIT 本身得到改进(可能作为服务包的一部分),生成的代码肯定会发生变化。80 位内部精度问题已经被提及。

于 2008-11-30T13:41:51.563 回答
0

这不是您问题的完整答案,但这里有一个示例证明 C# 中的双重计算是不确定的。我不知道为什么,但看似无关的代码显然会影响下游双重计算的结果。

  1. 在 Visual Studio 版本 12.0.40629.00 Update 5 中创建一个新的 WPF 应用程序,并接受所有默认选项。
  2. 将 MainWindow.xaml.cs 的内容替换为:

    using System;
    using System.Windows;
    
    namespace WpfApplication1
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                Content = FooConverter.Convert(new Point(950, 500), new Point(850, 500));
            }
        }
    
        public static class FooConverter
        {
            public static string Convert(Point curIPJos, Point oppIJPos)
            {
                var ij = " Insulated Joint";
                var deltaX = oppIJPos.X - curIPJos.X;
                var deltaY = oppIJPos.Y - curIPJos.Y;
                var teta = Math.Atan2(deltaY, deltaX);
                string result;
                if (-Math.PI / 4 <= teta && teta <= Math.PI / 4)
                    result = "Left" + ij;
                else if (Math.PI / 4 < teta && teta <= Math.PI * 3 / 4)
                    result = "Top" + ij;
                else if (Math.PI * 3 / 4 < teta && teta <= Math.PI || -Math.PI <= teta && teta <= -Math.PI * 3 / 4)
                    result = "Right" + ij;
                else
                    result = "Bottom" + ij;
                return result;
            }
        }
    }
    
  3. 将构建配置设置为“发布”并构建,但不要在 Visual Studio 中运行。

  4. 双击构建的 exe 运行它。
  5. 请注意,窗口显示“底部绝缘接头”。
  6. 现在在“字符串结果”之前添加这一行:

    string debug = teta.ToString();
    
  7. 重复步骤 3 和 4。

  8. 请注意,窗口显示“Right Insulated Joint”。

这种行为在同事的机器上得到了证实。请注意,如果以下任何一项为真,则窗口始终显示“Right Insulated Joint”:exe 从 Visual Studio 中运行,exe 是使用 Debug 配置构建的,或者在项目属性中未选中“Prefer 32-bit”。

很难弄清楚发生了什么,因为任何观察过程的尝试似乎都会改变结果。

于 2016-03-04T00:27:31.140 回答
-4

很少有 FPU 符合 IEEE 标准(尽管他们声称)。所以在不同的硬件上运行相同的程序确实会给你不同的结果。结果很可能出现在极端情况下,作为在软件中使用 FPU 的一部分,您应该避免这些情况。

IEEE 错误通常在软件中修补,您确定您今天运行的操作系统包含来自制造商的适当陷阱和补丁吗?在操作系统更新之前或之后呢?是否删除了所有错误并添加了错误修复?C 编译器是否与所有这些同步,C 编译器是否生成正确的代码?

对此进行测试可能是徒劳的。在您交付产品之前,您不会看到问题。

遵守 FP 规则 1:永远不要使用 if(something==something) 比较。IMO 的第二条规则与 ascii 到 fp 或 fp 到 ascii(printf、scanf 等)有关。那里有比硬件更多的准确性和错误问题。

随着每一代新硬件(密度)的出现,来自太阳的影响更加明显。我们已经在行星表面上遇到了 SEU 的问题,因此独立于浮点计算,您会遇到问题(很少有供应商会费心去关心,所以预计新硬件会更频繁地崩溃)。

通过消耗大量逻辑,fpu 可能会非常快(单个时钟周期)。不比整数 alu 慢。不要将此与现代 fpus 与 alus 一样简单混淆,fpus 很昂贵。(alus 同样消耗更多的乘法和除法逻辑以将其降低到一个时钟周期,但它几乎没有 fpu 大)。

遵守上面的简单规则,多研究浮点数,了解随之而来的缺陷和陷阱。您可能需要定期检查无穷大或 nans。您的问题更有可能出现在编译器和操作系统中而不是硬件中(通常不仅仅是 fp 数学)。如今,现代硬件(和软件)根据定义充满了错误,因此请尽量减少错误,而不是运行软件的错误。

于 2008-11-30T15:59:46.497 回答