为什么在C#中添加多个双打时的顺序影响圆角
-
22-08-2019 - |
题
考虑下面的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的设计博士学位,但我只是想我会问。
修改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和数学之间的区别是,+是简写“添加然后轮”,而不是只添加。
其他提示
C#编译器没有做任何事情。在CPU是
如果你有一个在一个CPU寄存器,和您然后添加B,存储在该寄存器中的结果是A + B,近似为使用浮动精度
如果你再加入C,错误增加了。这个错误添加不是过渡操作,从而最终的差。
请参阅的经典论文(什么每台计算机科学家应该知道浮点算术)关于这个问题的。这种东西是与浮点运算会发生什么。它需要一个计算机科学家告诉你,1/3 + 1/3 + 1/3'STRONG> is'nt 等于1 ...
浮点运算的顺序很重要。没有直接回答你的问题,但你要时刻注意比较浮点数。它通常以包括公差:
double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
...
}
这可能感兴趣的:什么每台计算机科学家应该知道关于浮点点算术
为什么这些错误是不依赖于为了可以用不同的例子来说明的相同。
让我们说,低于10个号码,它可以存储所有的数字,所以它能够存储1,2,3,依此类推直到并包括10,但10之后,它只能存储,由于每第二数目到的精确的内部损失,换言之,它只能存储10,12,14,等等。
现在,与例子,你就会明白为什么下面会产生不同的结果:
1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10
浮点数的问题是,它们不能准确地表示,并且误差不总是以同样的方式,所以为了将无关紧要。
例如,3.00000000003 3.00000000003 +可能最终会被(末尾通知未6)6.00000000005,但3.00000000003 2.99999999997 +可能最终会被6.00000000001,和与该:
step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002
但是,改变这个顺序:
step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004
因此,这将关系。
现在,当然,你可能会在幸运上面的例子中相互抵消,在第一将由.xxx1摆动,其它的下降.xxx1,让您在这两个.xxx3,但没有保证。
,因为中间结果不同,可以实际上不使用相同的值:
double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;
由于双打不能代表十进制值正是你得到不同的结果。