浮动误差导致的不平等


15

至少在Java中,如果我编写以下代码:

float a = 1000.0F;
float b = 0.00004F;
float c = a + b + b;
float d = b + b + a;
boolean e = c == d;

价值将。我认为这是由于以下事实造成的:在精确表示数字的方式中,浮点数非常有限。但我不明白为什么只是改变的位置可能会导致这种不平等。Ëfalsea

我在第3行和第4行中将 s都减小为1,但是的值变为:betruË

float a = 1000.0F;
float b = 0.00004F;
float c = a + b;
float d = b + a;
boolean e = c == d;

第三和第四行到底发生了什么?为什么带有浮点数的加法运算不关联?

提前致谢。


16
如您的示例所示,浮点加法可交换的。但这不是关联的。
Yuval Filmus

1
我鼓励您查找基本定义。还要注意,编译器将解析为(左侧有关联)。r + s + t[R+s+Ť[R+s+Ť
Yuval Filmus

2
为了轻松了解为什么会这样,请考虑X一个非常大的数字和Y一个非常小的数字,例如X + Y = X。在这里,X + Y + -X将为零。但是X + -X + Y会的Y
大卫·史瓦兹


Answers:


20

在典型的浮点实现中,将产生单个运算的结果,就好像该运算是以无限精度执行的一样,然后四舍五入到最接近的浮点数。

比较和:以无限精度执行的每个运算的结果相同,因此,这些相同的无限精度结果将以相同的方式取整。换句话说,浮点加法是可交换的。b + 一种+bb+一种

取:是一个浮点数。对于二进制浮点数,也是一个浮点数(指数大一),因此被添加而没有舍入误差。然后将加到精确值。结果是精确值,四舍五入到最接近的浮点数。b 2 b b + b a b + b 2 b + ab+b+一种b2bb+b一种b+b2b+一种

取:加,将有一个舍入误差r,因此我们得到结果a + b + r。加b,结果是精确2 b + a + r,四舍五入到最接近的浮点数。a + b一种+b+b一种+b[R一种+b+[Rb2b+一种+[R

因此,在一种情况下,是四舍五入的。在另一情况下,2 b + a + r取整。2b+一种2b+一种+[R

PS。对于两个特定的数字b,两个计算是否给出相同的结果取决于数字以及计算a + b中的舍入误差,通常很难预测。使用单精度或双精度在原则上对问题没有影响,但是由于舍入误差不同,因此将存在a和b的值,其中单精度的结果相等,而双精度的结果不相等,反之亦然。精度会高很多,但是浮点运算中两个表达式在数学上是相同但不相同的问题仍然相同。一种b一种+b

PPS。在某些语言中,与实际语句相比,浮点算术可以更高的精度或更大的数字范围执行。在那种情况下,两个总和给出相同结果的可能性要大得多(但仍不能保证)。

PPPS。一条评论询问我们是否应该询问浮点数是否相等。绝对可以,如果您知道自己在做什么。例如,如果您对数组进行排序或实现一个集合,那么如果要使用“近似相等”的概念,就会陷入麻烦。在图形用户界面中,如果对象的大小已更改,则可能需要重新计算对象的大小-比较oldSize == newSize以避免重新计算,要知道在实践中您几乎永远不会拥有几乎相同的大小,并且程序是正确的即使有不必要的重新计算。


在这种特殊情况下,b转换为二进制时变为周期性,因此到处都有舍入误差。
安德烈Souza的莱莫斯

1
b此答案中的@AndréSouzaLemos 不是0.00004,而是转换和舍入后的结果。
阿列克谢·罗曼诺夫

“在典型的浮点实现中,产生的单个操作的结果就好像该操作是无限精确地执行的,然后四舍五入到最接近的浮点数。”-规范实际上规定了这一点,令我非常沮丧当我尝试根据逻辑门实际实现此功能时(模拟器只能处理64位总线)。
约翰·德沃夏克

天真的问题:测试浮点数相等是否有意义?为什么大多数编程语言都允许aa == b测试,其中两者都是浮点数?
curious_cat

维基百科的相关定义:“ 由于浮点运算的舍入,机器Epsilon给出了相对误差的上限。”
Blackhawk

5

计算机支持的二进制浮点格式基本上类似于人类使用的十进制科学计数法。

浮点数由符号,尾数(固定宽度)和指数(固定宽度)组成,如下所示:

+/-  1.0101010101 × 2^12345
sign   ^mantissa^     ^exp^

常规科学计数法具有类似的格式:

+/- 1.23456 × 10^99

如果我们以有限的精度用科学计数法进行算术运算,然后在每次操作后取整,那么我们将得到与二进制浮点数相同的不良影响。


为了说明这一点,假设我们在小数点后使用正好3位数字。

a = 99990 = 9.999 × 10^4
b =     3 = 3.000 × 10^0

(a + b)+ b

现在我们计算:

c = a + b
  = 99990 + 3      (exact)
  = 99993          (exact)
  = 9.9993 × 10^4  (exact)
  = 9.999 × 10^4.  (rounded to nearest)

当然,在下一步中:

d = c + b
  = 99990 + 3 = ...
  = 9.999 × 10^4.  (rounded to nearest)

因此(a + b)+ b = 9.999×10 4

(b + b)+ a

但是,如果我们以不同的顺序进行操作:

e = b + b
  = 3 + 3  (exact)
  = 6      (exact)
  = 6.000 × 10^0.  (rounded to nearest)

接下来我们计算:

f = e + a
  = 6 + 99990      (exact)
  = 99996          (exact)
  = 9.9996 × 10^4  (exact)
  = 1.000 × 10^5.  (rounded to nearest)

因此(b + b)+ a = 1.000×10 5,这与我们的其他答案不同。


5

Java使用IEEE 754二进制浮点表示法,该表示法将23个二进制数专用于尾数,将其标准化为从第一个有效数位开始(省略以节省空间)。

0.0000410=0.00000000000000101001111100010110101100010001110001101101000111 ...2=[1。]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0.0000410=1111101000.00000000000000101001111100010110101100010001110001101101000111 ...2=[1。]11110100000000000000000101001111100010110101100010001110001101101000111 ...2×29

红色部分为尾数,因为它们实际表示出来(在四舍五入之前)。

100010+0.0000410+0.00004100.0000410+0.0000410+100010


0

我们最近遇到了类似的四舍五入问题。上面提到的答案是正确的,但是技术性很强。

我发现以下内容可以很好地解释为什么存在舍入错误。 http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR:无法将二进制浮点准确地映射到十进制浮点。这会导致在数学运算过程中可能会加重误差。

使用十进制浮点数的示例:1/3 + 1/3 + 1/3通常将等于1。但是,在十进制中:0.333333 + 0.333333 + 0.333333永远不会精确等于1.000000

对二进制小数进行数学运算时,也会发生同样的情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.