Windows .NET更新后,最后一位数字的四舍五入方式发生了变化。

15

在Windows更新后,一些计算出来的值在最后一位数字上发生了变化,例如从-0.0776529085243926变为了-0.0776529085243925。该变化总是减少了一个数字,并且影响了偶数和奇数。这似乎与KB4486153有关,因为还原此更新会将值更改回先前的值。

在使用Visual Studio进行调试并悬停在变量上时,就可以看到这种变化。该值稍后会被写入输出文件,并且其中的值也会发生更改(而无需运行调试器)。

最小可重现示例

var output = -0.07765290852439255;
Trace.WriteLine(output); // This printout changes with the update.

var dictionary = new Dictionary<int, double>();
dictionary[0] = output; // Hover over dictionary to see the change in debug mode

背景

计算得出的值来自于

output[date] = input[date] / input[previousDate] - 1;

忽略浮点运算中的精度损失,我可以在立即窗口执行计算并得到结果 -0.07765290852439255,升级前后都是这个值。

然而,当我将鼠标悬停在 output 变量上时,我看到:

{[2011-01-12 00:00:00, -0.0776529085243926]} 升级前
{[2011-01-12 00:00:00, -0.0776529085243925]} 升级后

并且这种差异也传播到了输出文件中。

看起来计算出的值在更新前后都相同,但其表示方式舍入不同。

输入的值为:

{[2011-01-11 00:00:00, 0.983561000400506]} 
{[2011-01-12 00:00:00, 0.907184628008246]}

目标框架已设置为.NET Framework 4.6.1

问题

在保持更新的同时,有没有什么我可以做来恢复以前的行为?

我知道浮点数计算中存在精度损失,但是为什么这种变化会发生在更新之后,我们如何确保未来的更新不改变值的表示方式?

KB4486153是针对Microsoft .NET Framework 4.8的更新,请参见https://support.microsoft.com/en-us/help/4486153/microsoft-net-framework-4-8-on-windows-10-version-1709-windows-10-vers


@BenVoigt 计算出的值没有改变,我关心的是它的字符串表示形式,因为最终会写入输出文件。 - Jaroslav K
你确定这是Windows版本吗?微处理器之间存在差异(有些存在漏洞),因此在不同的PC上可能会出现差异。PC厂商还生成了补丁来纠正错误。因此,在PC上安装错误的补丁可能会导致差异。此外,为调试和发布编译有时也是不同的。 - jdweng
@jdweng 我怀疑是某个特定的更新,因为还原它也会还原这种行为。 - Jaroslav K
1
我认为你的最小示例是 double v = -0.07765290852439255; Trace.WriteLine(v);,不是吗? - Ben Voigt
1
实现稳定性的最佳方法是让二进制转十进制的转换得到正确舍入。其它任何转换都是随意的,且容易发生随意变化。 - aka.nice
显示剩余5条评论
1个回答

4

OP正在遇到浮点数计算中的常见问题之一。在新软件中,人们是想要一致的答案还是最好的答案?(升级更好)


以下信息可以帮助推进这个问题。

var output = -0.07765290852439255;

使用常见的二进制64位编码1,由于二进制64位编码的二进制特性,输出具有精确值。

-0.077652908524392 54987160666132695041596889495849609375

以下显示前一个和后一个可能的 double 类型的十六进制和十进制 FP 值。
                      -0.077652908524392 5
-0x1.3e10f9e8d3217p-4 -0.077652908524392 53599381885351249366067349910736083984375
-0x1.3e10f9e8d3218p-4 -0.077652908524392 54987160666132695041596889495849609375
                      -0.077652908524392 55
-0x1.3e10f9e8d3219p-4 -0.077652908524392 56374939446914140717126429080963134765625
                      -0.077652908524392 6

将精确编码为-0.077652908524392 5498...的值-0.077652908524392 55舍入到小数点前一位得到的最佳结果是-0.077652908524392 5。经过升级后,代码输出了更好的答案-至少在这个特定的情况下。

我认为这不仅仅是一个舍入的变化,而是对文本的更好转换。

有什么方法可以保持更新的同时保留以前的行为吗?

也许有,但看起来更新提供了更好的结果。

我们如何确保未来的更新不会改变值的表示

使用十六进制浮点输出(如C中的"%a")是实现不要改变表示的方法之一,但非十进制输出可能不太熟悉。


1 使用其他编码时,精确值可能比-0.077652908524392 5更接近于-0.077652908524392 6


在我看来,那些调试器应该打印最少的小数位数,以便四舍五入后与任何像样的 REPL 相同。每两个不同的浮点数应该有不同的打印签名,否则输出就不完全有用... - aka.nice
@aka.nice同意需要足够的小数位数。使用Printf宽度说明符来保持浮点值的精度可能会有所帮助。 - chux - Reinstate Monica
1
谢谢!对我来说,“解决方案”就是接受新的行为。 - Jaroslav K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接