🫰浮点运算产生的误差
如何将十进制转换为二进制
整数🌰
所以100
的二进制表示就是1100100
,大家也可以用windows
自带的程序员计算器多试几个例子
小数🌰
所以0.375的二进制表示为0.011
在看一个稍微特殊的例子:0.1的二进制应该如何表示呢?
可以看出是无限循环的,0.1
的二进制表示为000110011001100...
,于是误差就产生了。
IEEE754标准下下的9.1
二进制表示9为:
1001
二进制表示0.1为:
0001100110011001100...
9.1二进制转换后为
1001.0001100110011001100...
,写成科学表示法就是:1.0010001100110011001100… x 2³
指数位为3+127 = 130,二进制表示为
10000010
由于指数可以是负数,如果算出来的指数不足八位,需要在高位即左边补0至八位。
分数位为
0010001100110011001100…
,如果不够23位则在后面补0最终9.1按照
IEEE754
表示如下
检验下,再转回到十进制
正如所看到的,我们首先将 9.1 转换为 IEEE 754 标准,然后将 IEEE 754 值转换为十进制值,得到的却是9.10000038,这就是所谓的浮点误差。
0.1 和 0.2 这两个数值在二进制浮点数中并没有精确的表示,因此,当你将十进制转换成二进制,再将二进制转换成十进制时,就会损失精度。
所以当我们编写一个算法时,应当要十分注意这种浮点数带来的误差。(曾经在国内某个二线大厂的代码中也发现了这种错误😅)
浮点误差的解决方法
使用
double
而不是float
类型当添加浮点数的时候,先加最小的数
添加浮点数列表的时候,首先对他们进行排序
在 C++ 中的浮点数精度问题
首先,让我们从一段程序开始这个问题
这看似没什么问题,实则问题隐藏于冰山之下。现在,让我们来解开它!
再次尝试下面的程序:
可以看到,当我们输出小数后 23 位后就能发现这个精度缺失问题了。
第一个例子之所以能得到正确的答案是因为 std::cout 输出流对小数做了四舍五入操作,通过处理给出了正确答案。
看到这,你或许觉得这有什么问题,能得到正确答案不就行了?其实不然,现在让我们将 float a = 10.0;
换为 float a = 500000.0
,我们再次运行程序,看看能得到什么(使用默认输出位数)!
好家伙,懵了吧(500000.0 并不是绝对的,准确来说要确保 a 足够的大)!
很明显,这出现了问题!当我们输出小数后 23 位时你就会得到:
所以,即使你可以通过允许误差的方式来判断浮点数是否相等(大概类似于使用 a - b <= 1e-12
来近似代替 a - b == 0.0
这样的判别式),你也无法避免输出显示的问题。而这一切都源于浮点数的精度丢失问题。
解决 C++ 中的浮点数精度问题
Boost库
目前,我尝试的解决办法为使用 Boost 库中的 boost::multiprecision::cpp_dec_float_50
来代替 float
或 double
类型。
很明显,精度丢失问题在 Boost 库中得到了很好的解决。当然,Boost 库还有很多其他的关于浮点数操作的优化,而且基本都是基于源标准库的模式重写的,所以使用起来的非常顺手(注意命名空间即可)。
Kahan 求和
Kahan 求和 是一种补偿的求和算法。代码如下:
reference
Last updated