R语言`%%`运算`3.1 %% 0.1`结果异常,是否符合预期?
为什么R中
3.1 %% 0.1返回0.1? 这本质还是二进制浮点数的精度限制导致的,但和你遇到的12345.2 %% 0.1返回极小值的情况逻辑不同,核心原因是:你预期的十进制数(3.1、0.1)在二进制浮点数中无法精确存储,实际参与计算的是它们的近似值,进而影响了模运算的结果。
1. 十进制小数的二进制存储偏差
计算机用双精度二进制浮点数(IEEE 754标准)存储小数,而很多十进制小数(比如0.1、3.1)转换成二进制是无限循环的,只能用近似值存储:
- 用R打印3.1的精确存储值:
可见3.1实际存储的值略小于十进制的3.1。sprintf("%.20f", 3.1) # 输出:"3.09999999999999964473" - 打印0.1的精确存储值:
0.1实际存储的值略大于十进制的0.1。sprintf("%.20f", 0.1) # 输出:"0.10000000000000000555"
2. 模运算的计算逻辑
R的%%运算符遵循模运算的标准定义:
a %% b = a - b * floor(a / b)
其中floor()是向下取整函数。
计算3.1 %% 0.1时:
- 先算
3.1 / 0.1的实际值:用上面的近似值计算,得到3.09999999999999964473 / 0.10000000000000000555 ≈ 30.999999999999996。 - 对这个结果向下取整,
floor(30.999999999999996)得到30(而非你预期的31)。 - 代入模运算公式:
3.1 - 0.1 * 30 ≈ 3.0999999999999996 - 3.0000000000000004 ≈ 0.0999999999999992。 - 由于R的显示精度限制,这个接近0.1的极小值会被显示为
0.1。
3. 为什么乘以10后num %% 1结果为0?
当你把这些数值乘以10后,比如3.1 * 10,实际存储的值是30.999999999999996,这个值非常接近整数31。此时计算num %% 1,得到的是一个极接近0的极小值,R会因显示精度限制将其展示为0;如果你对结果做四舍五入处理,也会直接得到0。本质上是把小数精度问题转化为了接近整数的问题,最终表现为模1结果为0。
4. Python的math.fmod为什么有相同行为?
Python的math.fmod和R的%%都基于IEEE 754双精度浮点数实现,遵循相同的模运算规则,因此遇到同样的浮点数精度问题时,会产生一致的结果。
内容的提问来源于stack exchange,提问作者rosscova
相关产品推荐
相关产品推荐

