考虑以下C#代码:
double result1 = 1.0 + 1.1 + 1.2; double result2 = 1.2 + 1.0 + 1.1; if (result1 == result2) { ... }
result1应该始终等于result2对吗?问题是,事实并非如此.result1是3.3,result2是3.3000000000000003.唯一的区别是常数的顺序.
我知道双打的实现方式可能会出现舍入问题.我知道如果我需要绝对精度,我可以使用小数.或者我可以在if语句中使用Math.Round().我只是一个想要了解C#编译器正在做什么的书呆子.有人能告诉我吗?
编辑:
感谢所有到目前为止建议阅读浮点运算和/或谈论CPU如何处理双精度的固有不准确性.但我觉得我的问题的主旨仍然没有答案.因为没有正确地说出来,这是我的错.我这样说吧:
分解上面的代码,我希望发生以下操作:
double r1 = 1.1 + 1.2; double r2 = 1.0 + r1 double r3 = 1.0 + 1.1 double r4 = 1.2 + r3
假设上述每个加法都有一个舍入误差(编号为e1..e4).因此r1包含舍入误差e1,r2包括舍入误差e1 + e2,r3包含e3,r4包含e3 + e4.
现在,我不知道舍入错误究竟是如何发生的,但我希望e1 + e2等于e3 + e4.显然它没有,但这对我来说似乎有些不对劲.另一件事是,当我运行上面的代码时,我没有得到任何舍入错误.这就是让我觉得C#编译器做的奇怪而不是CPU的原因.
我知道我问了很多,也许任何人都可以给出的最佳答案是去做CPU设计的PHD,但我只是想我会问.
编辑2
从原始代码示例中查看IL,很明显,编译器不是CPU正在执行此操作:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] float64 result1, [1] float64 result2) L_0000: nop L_0001: ldc.r8 3.3 L_000a: stloc.0 L_000b: ldc.r8 3.3000000000000003 L_0014: stloc.1 L_0015: ret }
编译器正在为我添加数字!
我原以为e1 + e2等于e3 + e4.
这与期待并不完全不同
floor( 5/3 ) + floor( 2/3 + 1 )
平等
floor( 5/3 + 2/3 ) + floor( 1 )
除非你在发言前乘以2 ^ 53.
使用12位精度浮点和截断值:
1.0 = 1.00000000000 1.1 = 1.00011001100 1.2 = 1.00110011001 1.0 + 1.1 = 10.00011001100 // extended during sum r1 = 1.0 + 1.1 = 10.0001100110 // truncated to 12 bit r1 + 1.2 = 11.01001100101 // extended during sum r2 = r1 + 1.2 = 11.0100110010 // truncated to 12 bit 1.1 + 1.2 = 10.01001100110 // extended during sum r3 = 1.1 + 1.2 = 10.0100110011 // truncated to 12 bit r3 + 1.0 = 11.01001100110 // extended during sum r4 = r3 + 1.0 = 11.0100110011 // truncated to 12 bit
因此,更改操作/截断的顺序会导致错误发生变化,并且r4!= r2.如果在此系统中添加1.1和1.2,则最后一位会携带,因此在截断时不会丢失.如果将1.0添加到1.1,则1.1的最后一位丢失,因此结果不一样.
在一个排序中,舍入(通过截断)移除尾随1
.
在另一个排序中,舍入0
两次都会删除尾随.
一个不等于零; 所以错误不一样.
双精度具有更多的精度,C#可能使用舍入而不是截断,但希望这个简单的模型能够显示不同的错误可能会发生在相同值的不同排序中.
fp和数学之间的区别在于+是'add then round'的简写,而不仅仅是添加.
c#编译器没有做任何事情.CPU是.
如果在CPU寄存器中有A,然后添加B,则存储在该寄存器中的结果为A + B,近似于使用的浮点精度
如果然后添加C,则错误加起来.此错误添加不是传递操作,因此是最终的差异.