什么是浮点/舍入误差的简单示例?

What is a simple example of floating point/rounding error?

提问人:MrDatabase 提问时间:10/30/2008 最后编辑:MrDatabase 更新时间:3/25/2022 访问量:49362

问:

我听说过使用浮点变量时的“错误”。现在我正在尝试解决这个难题,我想我得到了一些舍入/浮点错误。因此,我终于要弄清楚浮点误差的基础知识了。

浮点/舍入错误的简单示例是什么(最好在 C++ 中)?

编辑:例如,假设我有一个事件,其成功的概率为p。我做了这个事件 10 次(p 没有改变,所有试验都是独立的)。2 次试验成功的概率是多少?我将其编码为:

double p_2x_success = pow(1-p, (double)8) * pow(p, (double)2) * (double)choose(8, 2);

这是浮点误差的机会吗?

C++ 浮动精度

评论

0赞 Patrick 10/30/2008
我认为你真正需要的是:每个计算机科学家都应该知道的关于浮点运算的知识
0赞 Nicholas Wilson 5/4/2013
阅读: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
0赞 xmedeko 11/5/2013
请参阅简单的 Java 示例,在 C 中应该相同:stackoverflow.com/a/15790782/254109

答:

13赞 Matthew Schinckel 10/30/2008 #1

通常,浮点误差是指当一个数字不能存储在IEEE浮点表示中时。

存储整数时,最右边的位是 1,左边的每个位是 (2,4,8,...) 的两倍。很容易看出,它可以存储最多 2^n 的任何整数,其中 n 是位数。

浮点数的尾数(小数部分)以类似的方式存储,但从左向右移动,每个连续位都是前一个位值的一半。(它实际上比这要复杂一些,但现在可以)。

因此,像 0.5 (1/2) 这样的数字很容易存储,但并不是每个数字 <1 都可以通过添加固定数量的 1/2、1/4、1/8、...

一个非常简单的例子是 0.1,即 1/10。这可以用一个无穷级数来完成(我真的懒得计算),但是每当计算机存储 0.1 时,存储的就不完全是这个数字。

如果你可以访问 Unix 机器,很容易看到这一点:

Python 2.5.1 (r251:54863, Apr 15 2008, 22:57:26) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1
0.10000000000000001
>>> 

无论您使用哪种语言,您都需要非常小心地进行浮点数和双精度的相等性测试。

(至于你的例子,0.2 是另一个不能存储在 IEEE 二进制文件中的讨厌的数字,但只要你测试不等式,而不是等式,比如 p <= 0.2,那么你就会没问题。

7赞 SmacL 10/30/2008 #2

不久前,一个简单的 C 语言示例引起了我的注意:

double d = 0;
sscanf("90.1000", "%lf", &d);
printf("%0.4f", d);

这将打印90.0999

这是在将 DMS 中的角度转换为弧度的函数中。

为什么在上述情况下不起作用?

评论

3赞 Dan Moulding 10/7/2011
正如一位匿名用户所指出的,“f”转换说明符需要一个参数,而不是一个(然而,“f”的意思是——是的,这很令人困惑)。应使用“lf”修改后的转换说明符来使 .sscanffloatdoubledoubleprintfsscanfdouble
32赞 Motti 10/30/2008 #3
 for(double d = 0; d != 0.3; d += 0.1); // never terminates 
49赞 Agnius Vasiliauskas 4/17/2011 #4

图片胜过千言万语 - 尝试绘制方程式:
enter image  description here
您将得到这样的 XY 图(X 和 Y 为对数刻度)。
enter image description here
如果计算机可以表示 32 位浮点数而没有舍入误差,那么对于每一个浮点数,我们应该得到零。但是,由于浮点误差累积,误差会随着 k 值的增加而增加。
f(k)k

呵呵!

评论

9赞 Martin Thoma 8/13/2018
我可以在 CC0 许可下将此图像(重做,使其成为 SVG)添加到维基百科共享资源(参考这个想法)吗?
0赞 Agnius Vasiliauskas 11/10/2022
当然你可以使用它。
0赞 Agnius Vasiliauskas 11/29/2023
注意:对于想要在浏览器中获得类似累积误差效应的人来说,有一些警告 - 现代浏览器以某种方式减少了浮点错误,可能实现了双浮点运算或其他方式。无论如何,对于浏览器 Javascript 控制台,运行这样的代码,它将显示 1000 万个总和的人为放大效果:(iter = 10**7, Math.abs((iter*0.1)**2 - Array(iter).fill(0.1).reduce((x,a) => a+x)**2));
9赞 Rory O'Bryan 12/17/2014 #5

这里有一个抓住了我:

 round(256.49999) == 256
roundf(256.49999) == 257

双精度和浮点数具有不同的精度,因此第一个将表示为 ,第二个表示为 ,因此舍入方式不同256.49999000000003256.5

6赞 Samuel Li 11/7/2018 #6

我喜欢 Python 解释器的这个:

Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1+0.2
0.30000000000000004
>>>
2赞 formiaczek 8/14/2019 #7

超级简单(Python):

a = 10000000.1
b = 1/10
print(a - b == 10000000)
print ('a:{0:.20f}\nb:{1:.20f}'.format(a, b))

打印(取决于平台)如下:

False                                                                                                                                 
a:10000000.09999999962747097015                                                                                                       
b:0.10000000000000000555 

评论

1赞 Ben Voigt 5/11/2021
这是什么语言?这个问题被标记为 C++,其中此代码根本没有意义(在浮点中完全可表示,它是1/100.00000)
9赞 Danilo Pianini 5/20/2020 #8

这是我想到的最简单的,应该适用于多种语言,很简单:

0.2 + 0.1

以下是我想到的 REPL 的一些示例,但应该在任何符合 IEEE754 的语言上返回此结果。

>>> 0.2 + 0.1
0.30000000000000004

Kotlin

0.2 + 0.1
res0: kotlin.Double = 0.30000000000000004

斯卡拉

scala> 0.2 + 0.1
val res0: Double = 0.30000000000000004

爪哇岛

jshell> 0.2 + 0.1
$1 ==> 0.30000000000000004

红宝石

irb(main):001:0> 0.2 + 0.1
=> 0.30000000000000004

评论

1赞 x-yuri 5/11/2021
实际上,Lua和PHP返回.0.3
1赞 x-yuri 5/11/2021
还有Perl:)
0赞 x-yuri 5/11/2021 #9

我认为 Ruby 在其文档中有一个很好的例子:

sum = 0
10_000.times do
  sum = sum + 0.0001
end
print sum #=> 0.9999999999999062