为什么四舍五入是trunc(x±0.49999997)而不是trunc(x±0.5)?

目录

float的不精确表示

±0.5的舍入方法

该方法的漏洞

0.4999997f舍入的结果错误

以±0.4999997f改进舍入方法

可以用0.49999996、0.49999998或者0.49999999替换0.49999997吗?


在做舍入函数研究时,发现函数中实现四舍五入的round函数大概采用的逻辑是trunc(x±0.4999997),而不是trunc(x±0.5),(trunc表示truncate,向0舍入)下面对这个数值进行研究说明。

float的不精确表示

我们都知道二进制在有限的位上表示浮点值并不是每个都精确的,就像在有限的位数上,十进制不能准确地表示1/3。二进制在实数上能表示的数据分布大概规律如下:

越接近0的值表示得越精确,越大的数表示得越不精确,这是前提。

±0.5的舍入方法

舍入函数中round实现数学上的四舍五入,被普遍知道的方法是将x±0.5(x为正则+,为负则-)后并向0舍入(向0舍入即将小数位置0),这种做法对计算机来说,比判断x是否大于等于【小于等于x的最大整数与大于等于x的最大整数的中值】这个判断要简单得多。

该方法的漏洞

因为浮点表示不精确的特性,当2^23 < |x|时,单精度float浮点的IEEE 754格式能表示的只有整数,即两个可表示的浮点之间间隔最小为1,并且间隔会随x的增大而增大。

在2^23 < |x| < 2^24时,间隔正好为1。

此时,假设有float值x=2^23+1.0,对其进行四舍五入的舍入预期值为x本身,但实际舍入会执行trunc(x+0.5),而x+0.5这个值并不能用float精确表示,系统会对其进行舍入后再表示,此处的舍入是中值向偶数舍入的就近舍入,x+0.5正好是2^23+1.0与2^23+2.0的中值,向偶舍入则会舍入为2^23+2.0,对2^23+2.0执行trunc依然是2^23+2.0,得到的结果与预期值不符。这种错误的结果会在2^23 < |x| < 2^24范围内所有的奇数值上发生(但对于更大绝对值的x则不会发生,因为最小间隔大于1,任何数字±0.5后达不到与上一个可精确表示数字的中值,所以x±0.5被实际表示为x,trunc(x)=x,结果正确)。

0.49999997f舍入的结果错误

如何处理这个范围内的错误结果呢?

让我们考虑一个特殊的值x=0.49999997f,对x执行round,发现结果=1.0而不是0.0,但x明显更接近1.0。

这个错误同样是因为浮点表示不精确引起的,IEEE 754 float以 0.4999999701976776123046875来不精确地表示0.49999997:

当这个值加上0.5时,数学上是0.9999999701976776123046875,但float同样不能精确地表示这个数,它能精确表示的下一个数是:

能精确表示的上一个数是1:

这个值正好是两者的中值:(0.9999999701976776123046875+1)/2=0.4999999701976776123046875

根据向偶舍入的就近舍入原则,系统以1.0来不精确地表示这个数:

所以就是trunc(1.0)结果为1.0。

你可能会想,为什么float能精确表示0.4999999701976776123046875却不能精确表示0.9999999701976776123046875呢?

因为上面提到的,越靠近0表达地越精确。

为什么0.9999999701976776123046875正好落在能精确表达的两个数的中间呢?

因为float能精确表示的数是有规律的,假设0.49999997附近的最小间隔为a,比0.49999997大的数间隔会慢慢变成2a,4a……,0.99999997附近的最小间隔就是2a,效果如下: 

所以对0.49999997执行四舍五入的结果为1.0。

±0.49999997f改进舍入方法

这个数字却可以用来避免±0.5的舍入方法的漏洞,因为当2^23 < |x| < 2^24时,x±0.49999997达不到与上一个可精确表示数的中值(即x±0.5),所以x±0.49999997会就近舍入到x,trunc(x)=x,结果正确,不会有±0.5的漏洞。同样,±0.49999997f也可以避免当x=0.49999997f时的错误。

可以用0.49999996、0.49999998或者0.49999999替换0.49999997吗?

首先,0.49999996与0.49999998在IEEE 754 float中的表示与0.49999997一样,所以在表示这一步就与0.49999997一样了,即可以用96与98替代97的。而0.49999999表示为0.5,显然会存在和±0.5的舍入方式一样的漏洞,所以不能用其替换。

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值