为什么更改总和顺序会返回不同的结果?

本文翻译自:Why does changing the sum order returns a different result?

Why does changing the sum order returns a different result? 为什么更改总和顺序会返回不同的结果?

23.53 + 5.88 + 17.64 = 47.05 23.53 + 5.88 + 17.64 = 47.05

23.53 + 17.64 + 5.88 = 47.050000000000004 23.53 + 17.64 + 5.88 = 47.050000000000004

Both Java and JavaScript return the same results. JavaJavaScript都返回相同的结果。

I understand that, due to the way floating point numbers are represented in binary, some rational numbers ( like 1/3 - 0.333333... ) cannot be represented precisely. 我理解,由于浮点数用二进制表示的方式,一些有理数( 如1/3 - 0.333333 ...... )无法精确表示。

Why does simply changing the order of the elements affect the result? 为什么简单地改变元素的顺序会影响结果?


#1楼

参考:https://stackoom.com/question/1LAAD/为什么更改总和顺序会返回不同的结果


#2楼

I believe it has to do with the order of evaulation. 我认为这与评估的顺序有关。 While the sum is naturally the same in a math world, in the binary world instead of A + B + C = D, it's 虽然数学世界中的总和自然是相同的,但在二进制世界中,而不是A + B + C = D,它就是

A + B = E
E + C = D(1)

So there's that secondary step where floating point numbers can get off. 所以浮动点数可以下降的第二步。

When you change the order, 当您更改订单时

A + C = F
F + B = D(2)

#3楼

Maybe this question is stupid, but why does simply changing the order of the elements affects the result? 也许这个问题很愚蠢,但为什么简单地改变元素的顺序会影响结果呢?

It will change the points at which the values are rounded, based on their magnitude. 它将根据其大小更改值舍入的点。 As an example of the kind of thing that we're seeing, let's pretend that instead of binary floating point, we were using a decimal floating point type with 4 significant digits, where each addition is performed at "infinite" precision and then rounded to the nearest representable number. 作为我们所看到的那种事物的一个例子,让我们假设我们使用的是带有4位有效数字的十进制浮点类型,而不是二进制浮点数,其中每次加法都以“无限”精度执行,然后舍入到最近的可表示数字。 Here are two sums: 这是两个总和:

1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
                = 1.000 + 0.6667 (no rounding needed!)
                = 1.667 (where 1.6667 is rounded to 1.667)

2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
                = 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
                = 1.666 (where 1.6663 is rounded to 1.666)

We don't even need non-integers for this to be a problem: 我们甚至不需要非整数来解决这个问题:

10000 + 1 - 10000 = (10000 + 1) - 10000
                  = 10000 - 10000 (where 10001 is rounded to 10000)
                  = 0

10000 - 10000 + 1 = (10000 - 10000) + 1
                  = 0 + 1
                  = 1

This demonstrates possibly more clearly that the important part is that we have a limited number of significant digits - not a limited number of decimal places . 这可能更清楚地表明,重要的部分是我们有限数量的有效数字 - 不是有限的小数位数 If we could always keep the same number of decimal places, then with addition and subtraction at least, we'd be fine (so long as the values didn't overflow). 如果我们总能保持相同的小数位数,那么至少加上和减法,我们就没事了(只要值没有溢出)。 The problem is that when you get to bigger numbers, smaller information is lost - the 10001 being rounded to 10000 in this case. 问题是当你得到更大的数字时,更小的信息会丢失 - 在这种情况下,10001被舍入到10000。 (This is an example of the problem that Eric Lippert noted in his answer .) (这是Eric Lippert在答案中提到的问题的一个例子。)

It's important to note that the values on the first line of the right hand side are the same in all cases - so although it's important to understand that your decimal numbers (23.53, 5.88, 17.64) won't be represented exactly as double values, that's only a problem because of the problems shown above. 重要的是要注意右侧第一行的值在所有情况下都是相同的 - 所以尽管理解您的十进制数(23.53,5.88,17.64)不能完全表示为double精度值很重要,由于上面显示的问题,这只是一个问题。


#4楼

Floating point numbers are represented using the IEEE 754 format, which provides a specific size of bits for the mantissa (significand). 浮点数使用IEEE 754格式表示,该格式为尾数(有效数字)提供特定的位大小。 Unfortunately this gives you a specific number of 'fractional building blocks' to play with, and certain fractional values cannot be represented precisely. 不幸的是,这会为您提供特定数量的“分数构建块”,并且某些小数值无法精确表示。

What is happening in your case is that in the second case, the addition is probably running into some precision issue because of the order the additions are evaluated. 在您的情况下发生的情况是,在第二种情况下,由于评估添加的顺序,添加可能会遇到一些精确问题。 I haven't calculated the values, but it could be for example that 23.53 + 17.64 cannot be precisely represented, while 23.53 + 5.88 can. 我没有计算出这些值,但可能是23.53 + 17.64不能精确表示,而23.53 + 5.88可以。

Unfortunately it is a known problem that you just have to deal with. 不幸的是,这是一个已知的问题,你只需要处理。


#5楼

This actually covers much more than just Java and Javascript, and would likely affect any programming language using floats or doubles. 这实际上涵盖的不仅仅是Java和Javascript,并且可能会影响使用浮点数或双精度的任何编程语言。

In memory, floating points use a special format along the lines of IEEE 754 (the converter provides much better explanation than I can). 在内存中,浮点使用IEEE 754的特殊格式(转换器提供了比我更好的解释)。

Anyways, here's the float converter. 无论如何,这是浮动转换器。

http://www.h-schmidt.net/FloatConverter/ http://www.h-schmidt.net/FloatConverter/

The thing about the order of operations is the "fineness" of the operation. 关于操作顺序的事情是操作的“精细度”。

Your first line yields 29.41 from the first two values, which gives us 2^4 as the exponent. 你的第一行从前两个值得到29.41,这给了我们2 ^ 4作为指数。

Your second line yields 41.17 which gives us 2^5 as the exponent. 你的第二行产生41.17,它给出了2 ^ 5作为指数。

We're losing a significant figure by increasing the exponent, which is likely to change the outcome. 我们通过增加指数而失去一个重要的数字,这可能会改变结果。

Try ticking the last bit on the far right on and off for 41.17 and you can see that something as "insignificant" as 1/2^23 of the exponent would be enough to cause this floating point difference. 尝试勾选最右边的最后一位开启和关闭41.17,你可以看到像指数的1/2 ^ 23那样“无关紧要”的东西足以引起这个浮点差异。

Edit: For those of you who remember significant figures, this would fall under that category. 编辑:对于那些记得重要人物的人来说,这属于那个类别。 10^4 + 4999 with a significant figure of 1 is going to be 10^4. 具有显着数字1的10 ^ 4 + 4999将是10 ^ 4。 In this case, the significant figure is much smaller, but we can see the results with the .00000000004 attached to it. 在这种情况下,有效数字要小得多,但我们可以看到附加了.00000000004的结果。


#6楼

Here's what's going on in binary. 这是二进制文件中发生的事情。 As we know, some floating-point values cannot be represented exactly in binary, even if they can be represented exactly in decimal. 众所周知,某些浮点值无法用二进制精确表示,即使它们可以用十进制精确表示。 These 3 numbers are just examples of that fact. 这3个数字只是这个事实的例子。

With this program I output the hexadecimal representations of each number and the results of each addition. 使用此程序,我输出每个数字的十六进制表示和每个加法的结果。

public class Main{
   public static void main(String args[]) {
      double x = 23.53;   // Inexact representation
      double y = 5.88;    // Inexact representation
      double z = 17.64;   // Inexact representation
      double s = 47.05;   // What math tells us the sum should be; still inexact

      printValueAndInHex(x);
      printValueAndInHex(y);
      printValueAndInHex(z);
      printValueAndInHex(s);

      System.out.println("--------");

      double t1 = x + y;
      printValueAndInHex(t1);
      t1 = t1 + z;
      printValueAndInHex(t1);

      System.out.println("--------");

      double t2 = x + z;
      printValueAndInHex(t2);
      t2 = t2 + y;
      printValueAndInHex(t2);
   }

   private static void printValueAndInHex(double d)
   {
      System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
   }
}

The printValueAndInHex method is just a hex-printer helper. printValueAndInHex方法只是一个十六进制打印机助手。

The output is as follows: 输出如下:

403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004

The first 4 numbers are x , y , z , and s 's hexadecimal representations. 前4个数字是xyzs的十六进制表示。 In IEEE floating point representation, bits 2-12 represent the binary exponent , that is, the scale of the number. 在IEEE浮点表示中,位2-12表示二进制指数 ,即数字的比例。 (The first bit is the sign bit, and the remaining bits for the mantissa .) The exponent represented is actually the binary number minus 1023. (第一位是符号位, 尾数的其余位。)表示的指数实际上是二进制数减去1023。

The exponents for the first 4 numbers are extracted: 提取前4个数字的指数:

    sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5

First set of additions 第一组添加

The second number ( y ) is of smaller magnitude. 第二个数字( y )的幅度较小。 When adding these two numbers to get x + y , the last 2 bits of the second number ( 01 ) are shifted out of range and do not figure into the calculation. 当添加这两个数字以获得x + y ,第二个数字( 01 )的最后2位移出范围并且不计入计算。

The second addition adds x + y and z and adds two numbers of the same scale. 第二个加法添加x + yz并添加两个相同比例的数字。

Second set of additions 第二组补充

Here, x + z occurs first. 这里,首先出现x + z They are of the same scale, but they yield a number that is higher up in scale: 它们具有相同的比例,但它们产生的数字在规模上更高:

404 => 0|100 0000 0100| => 1028 - 1023 = 5

The second addition adds x + z and y , and now 3 bits are dropped from y to add the numbers ( 101 ). 第二个加法添加x + zy ,现在从y中删除3位以添加数字( 101 )。 Here, there must be a round upwards, because the result is the next floating point number up: 4047866666666666 for the first set of additions vs. 4047866666666667 for the second set of additions. 这里,必须有一个圆形向上,因为结果是下一个浮点数起来: 4047866666666666对于首套增加的对4047866666666667第二组补充的。 That error is significant enough to show in the printout of the total. 该错误足以显示在总数的打印输出中。

In conclusion, be careful when performing mathematical operations on IEEE numbers. 总之,在对IEEE数字执行数学运算时要小心。 Some representations are inexact, and they become even more inexact when the scales are different. 一些表示是不精确的,当尺度不同时,它们变得更加不精确。 Add and subtract numbers of similar scale if you can. 如果可以,添加和减去相似比例的数字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值