【问题标题】:Why does this double arithmetic give two different answers on a single machine? [duplicate]为什么这种双重算术在一台机器上会给出两个不同的答案? [复制]
【发布时间】:2018-02-14 10:29:32
【问题描述】:

在我正在开发的一些软件的深处有一行代码......

double DataNoise = StatsStuff.MeanofSquares() - average * average;

示例数字:

StatsStuff.MeanofSquares() = 1.9739125181231402E-13  
average = -4.3328988592605794E-07
DataNoise = 9.6511265664977283E-15 //(State1)  
DataNoise = 9.6511265664977204E-15 //(State2)

如果我反复从 GUI 重新启动分析,该计算的结果迟早会发生变化,有时在第一次重新运行分析时,但它通常会在切换到不同的答案之前给出一些一致的结果(开关前的次数是显着变化的)。一旦软件切换到返回第二个值,它就永远不会返回到返回第一个值。

我正在使用 C# 和 Visual Studio,在带有 i5 4570 的 Windows 7 机器上进行测试,如果这对任何人都有帮助的话。

我在 Debug 和 Release 版本中都发现了这个问题。

每次启动分析时,都会在分析方法中重新创建所有分析对象,因此不应有任何持久性。

我已经记录了计算中的值,它们没有改变;我也使用BitConverter.GetBytes() 来检查数字是否相同。

我已经在网上看到过下面的问题和许多其他类似的文章,但它们都与两台不同机器之间的差异有关。
Why does this floating point calculation give different results...

how-deterministic-is-floating-point-inaccuracy 中的答案似乎表明我应该能够从单个机器和指令集中获得确定性行为,但我没有。

任何帮助解释为什么会发生这种情况和/或如何确保一致的结果将不胜感激。

调试中的一些额外字节值:
输入:
平均:48、51、51、18、221、19、157、190
MeanOfSquares: 205, 250, 200, 243, 196, 199, 75, 61

输出:
数据噪声(状态 1):192、220、244、228、126、187、5、61
DataNoise(状态 2):187、220、244、228、126、187、5、61

【问题讨论】:

  • 在使用 IEEE-754 算术时,数字中的低有效位会有微小的差异 - 但结果应该在相同硬件上给定相同输入的情况下是确定性的、环境等。您绝对确定StatsStuff.MeanOfSquares() 正在返回相同 值(System.Double 值的所有 64 位)?
  • 不幸的是它在软件的深处。到目前为止,我还无法在主软件之外复制效果。
  • @Dai:你的说法是绝对错误的。 C# 语言不保证即使在同一进程中,相同的计算也会连续两次给出相同的结果。浮点计算在 C# 中不是确定性的,因为 抖动可以自行决定是否使用高精度寄存器。因此,可以根据寄存器大小以至少 64 位精度或更高的精度进行计算。这会改变计算结果。
  • @Dai:不幸的是,浮点运算在 C# 中不是确定性的,但是如果您想抱怨这种不幸的情况,请向 Intel 抱怨。他们为我们提供了确定性计算速度较慢且准确性较低的芯片组,从而激励语言开发人员将非确定性纳入浮点计算。

标签: c# floating-point


【解决方案1】:

根据C# specification,关于double的操作,“该操作至少使用双倍范围和精度……”

这意味着StatsStuff.MeanofSquares()可以用扩展精度计算,这个扩展精度结果可以直接在StatsStuff.MeanofSquares() - average * average中使用。如果StatsStuff.MeanofSquares() 的计算产生的结果在扩展精度上略有不同,则在仅打印 17 位数字时可能看不到差异。

一个重要的线索是,显示的两个结果正是通过使用显示的输入值计算得到的结果@第二个结果的算术。这表明正在使用不同的指令来评估这两个结果。具体来说:

  • m 成为最接近1.9739125181231402E-13 的double 值。
  • a 成为最接近-4.3328988592605794E-07 的double 值。
  • 您显示的第一个结果 9.6511265664977283E-15 等于在 double 中计算 a*a 的结果,从 double 中的 m 中减去乘积,并将结果转换为 17 位十进制数字。李>
  • 您显示的第二个结果 9.6511265664977204E-15 等于使用精确数学计算 m-a*a 然后四舍五入到 double(与 C 的 fma(a, -a, m) 一样)并将结果转换为 17 位十进制数字的结果。它还等于在long double 中计算a*a 的结果,从m 中减去乘积,将结果四舍五入为double,并将其转换为17 位十进制数字。

执行这些不同操作的唯一方法是通过不同的指令。所以这表明double DataNoise = StatsStuff.MeanofSquares() 在不同的时间被编译成不同的指令。一种可能性是该语句在源代码中出现多次。另一个是编译器内联包含它的函数,因此在不同的上下文中编译不同。

由于该问题未提供可重复的示例或有关声明 double DataNoise = StatsStuff.MeanofSquares() - average * average 上下文的任何信息,因此不可能有明确的答案。

虽然这些数字与分离与融合和双与长双模式相匹配,这强烈表明不同的指令用于不同的结果,但仍有可能使用一个指令序列来计算 @987654346 @ 但表达式的输入值会有所不同,可能是 StatsStuff.MeanofSquares() 以额外的精度计算的,这在您打印的有限数字中不可见。 如果您的软件是多线程的,它可能会将问题划分为子问题并与多个线程并行执行它们。当这些线程返回结果时,它可能会组合结果以生成StatsStuff.MeanofSquares() 的最终结果。由于线程可能在不同的运行中以不同的顺序完成,因此结果可能以不同的顺序组合。这意味着在操作中使用了不同的数据,因此结果可能会有所不同。 (例如,在两位十进制中,加 21 + 4.9 + 90 得到 26 + 90(21 + 4.9 正好是 25.9,所以四舍五入得到 26)然后是 120,但加上 21 + 90 + 4.9 得到 110( 111 轮到 110)然后 110(110+4.9 轮到 110)。

另一种可能是软件存在错误,导致它使用未初始化的数据,而这些数据会影响结果。

如果问题在于以不同方式评估表达式,则可能的解决方法是将中间结果分配给临时变量:

double t0 = average*average;
double t1 = StatsStuff.MeanofSquares();
double Mean = t1 - t0;

我猜想这样的赋值将导致每个表达式四舍五入为double。我在 C# 规范中没有看到明确的声明,但这是 C 中的规则,C# 编译器也可以这样做。如果是这样,这可能会大大降低观察到不同最终结果的频率,但可能不会完全消除它们。

(由于 C# 不符合您的目的,您应该让 Microsoft 知道,并且您应该寻找其他语言和其他编译器。)

【讨论】:

  • 我正在用 C# 编写并使用 VisualStudio。关于多线程,即使是这种情况,如果输入相同,我是否应该不能期望计算 double - double * double 在单个线程上完成并返回一致的结果?跨度>
  • @rbren:已更新。您显示的数字中有一种模式强烈表明您显示的两个结果来自评估表达式 StatsStuff.MeanofSquares() - average * average 的不同指令实例。
  • 感谢您的详细解答!我没有在多个线程上运行它(尽管我认为我应该朝那个方向前进),但是您提供的有关相对精度水平的细节与 Eric 的 cmets 一致。
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 1970-01-01
  • 2023-03-11
  • 2014-02-24
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2022-08-18
相关资源
最近更新 更多