0.1 ,计算机会想“没前缀,是十进制,我要转成二进制存”;你写 0b0.00011 ,它才会想“有 0b ,是二进制,我直接按二进制存”。 计算机是以二进制来存储数据的,而像 0.1 、 0.2 这样的十进制小数,在转换为二进制时是无限循环小数。由于计算机的存储位数有限,只能对其进行近似存储,这就导致了计算时的精度误差。
当浮点数的近似值与十进制小数的差异非常小时,会自动以更简洁的十进制形式显示,而不是输出其底层存储的完整(带误差的)二进制近似值。例子a = 0.1 :底层存储的是 0.1 的二进制近似值(0.000110011001100110011
实际是 0.10000000000000000555... )。
二进制近似值(0.0001100110011...) → 这个二进制值转成十进制后是 0.10000000000000000555... → 它和目标十进制 0.1 差异极小 → Python 直接显示 0.1。
0.1 + 0.2 之所以会显示长串的误差( 0.30000000000000004 ),是因为这两个浮点数的近似值相加后,误差的表现形式无法被 Python 的显示优化“掩盖”,所以只能输出其真实的存储结果。
round() 用的其实是“银行家舍入”(四舍六入五成双),不是严格意义的四舍五入
银行家舍入(也叫“四舍六入五成双”),不是我们数学课上学的那种“四舍五入”。
四舍五入:逢五就进位,2.5 → 3,3.5 → 4
银行家舍入:当数字正好卡在中间(比如 2.5 正好在 2 和 3 正中间)时,向偶数那边靠
round(2.5) # 2 → 2和3中间,2是偶数,所以取2
round(3.5) # 4 → 3和4中间,4是偶数,所以取4
round(4.5) # 4 → 4和5中间,4
2.5 在计算机中是可以被精确存储的。一个十进制小数能否被二进制精确表示,取决于它能否写成 1/2^n的和。
2.5 的二进制转换:
整数部分 2 → 二进制 10
小数部分 0.5 → 二进制 0.1 (因为 0.5 = 1/2 = 2^(-1)
所以,2.5 的二进制表示是 10.1。这是一个有限的二进制小数。
在 IEEE 754 双精度浮点数标准中,2.5 可以被毫无误差地存储为:
1.01 ×2 ^1 尾数部分正好存得下,没有截断,没有舍入误差。因此,内存里的 2.5 就是数学意义上的四舍五入。
为什么round(2.675, 2) 返回2.67而不是 2.68
2.675 的二进制转换:
整数部分 2 → 10
小数部分 0.675 → 尝试用 1/2, 1/4, 1/8...1/2,1/4,1/8... 拼凑:
0.675×2=1.35 → 取 1
0.35×2=0.7 → 取 0
0.7×2=1.4 → 取 1
0.4×2=0.8 → 取 0
0.8×2=1.6 → 取 1
0.6×2=1.2 → 取 1
0.2×2=0.4 → 取 0
0.4×2=0.8 → 取 0 ... 开始循环了!
0.675 的二进制是 0.1010110011001100...(1100 无限循环)。
2.675 在二进制里无法精确表示,实际存的是一个比它略小的数,大约是这个:2.6749999999999998 ,round() 拿到的是这个“略小”的数去舍入,自然就往下走了。你可以自己验证:
repr(2.675)
#'2.67499999999999982236431605997495353221893310546875'
总结
round(2.5) 返回2:是因为2.5 存得准,且符合银行家舍入规则(五成双,2是偶数)。
round(2.675,2)返回2.67:主要是因
为 2.675 存不准,实际值略小于
2.675,导致它甚至没资格参与“五成
双”的判断,直接被当作“小于5”处理了。
哪些小数能精确存储?
只要小数部分的分母是2的幂次(如0.5,
0.25,0.125,0.75等),就能精确存储。像
0.1, 0.2, 0.3, 0.675 这种分母含有5或其他质因数的,通常都无法精确存储。
想要真正的“四舍五入”怎么办
1. 格式化输出(适合展示)
f'{2.675:.2f}' # '2.68'(返回字符串,一般符合直觉)
2. 用 decimal 模块(适合金额计算)
from decimal import Decimal, ROUND_HALF_UP
Decimal('2.675').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# Decimal('2.68')
注意:Decimal 后面要传字符串,传 2.675 这个浮点数本身又会把误差带进去。
简单记:普通场景用 round() 没问题;涉及金额、分数这类不能有偏差的计算,用 decimal 模块才稳。