四舍五入是日常开发中高频出现的操作,无论是金额计算、报表展示还是数据处理,都绕不开它。但在Python中,如果你直接使用内置的round()函数,很可能会遇到结果与预期不符的情况,比如round(2.675, 2)返回的不是2.68而是2.67。这篇文章就来彻底讲清楚Python中数字舍入的各种细节,让你不再被这些诡异的结果困扰。

一、为什么round函数的结果经常不符合预期
要理解round()的诡异行为,需要先弄清楚两个关键点:浮点数的存储误差和Python采用的舍入规则。
第一点是浮点数本身的精度问题。Python中的浮点数遵循IEEE 754标准,采用二进制存储。很多十进制小数在二进制中无法精确表示,比如2.675在计算机内部实际存储的值约等于2.67499999999999982236431605997495353221893310546875。既然这个数实际上比2.675小一点点,那么保留两位小数时舍入到2.67反而是“正确”的,只是与我们的十进制直觉不符。
第二点是Python 3的round()采用的是“银行家舍入”(banker's rounding),即ROUND_HALF_EVEN规则。当数字恰好处于中间值时,会舍入到最近的偶数,而不是我们习惯的“五入”。看下面的例子:
print(round(0.5)) # 输出 0,因为0是偶数 print(round(1.5)) # 输出 2,因为2是偶数 print(round(2.5)) # 输出 2,而不是3 print(round(3.5)) # 输出 4 print(round(2.675, 2)) # 输出 2.67,浮点误差叠加导致
从例子可以看出,round(0.5)和round(2.5)都向下取整到了偶数,这与我们从小学习的“四舍五入”规则并不一致。银行家舍入在统计学上有其优势——大量数据求和时误差可以相互抵消,所以在金融系统之外的场景其实是合理的设计。但如果你的业务需求就是传统的四舍五入,就不能依赖round()了。
另外要注意,round()在不指定第二个参数时返回的是整数(int类型),指定了小数位数则返回浮点数,这一点在类型敏感的场景需要留意。
二、用Decimal模块实现严格的四舍五入
当业务要求严格的“逢五进一”时,标准库中的decimal模块是最可靠的方案。Decimal采用十进制存储,不存在二进制浮点误差,并且支持指定多种舍入模式,其中ROUND_HALF_UP就是我们要的传统四舍五入。
使用时有一个非常关键的细节:构造Decimal对象必须传入字符串,而不是浮点数。如果传入浮点数,精度误差会原封不动地带过去,舍入结果照样出错。
from decimal import Decimal, ROUND_HALF_UP
# 错误方式:传入浮点数,精度已经丢失
print(Decimal(2.675)) # 输出 2.67499999999999982236431605997495353221893310546875
# 正确方式:传入字符串
num = Decimal('2.675')
result = num.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(result) # 输出 2.68
# 负数同样适用
print(Decimal('-2.675').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) # 输出 -2.68
quantize()方法的第一个参数指定目标精度,比如Decimal('0.01')表示保留两位小数,Decimal('0.0001')表示保留四位小数。ROUND_HALF_UP表示中间值时向上(远离零的方向)舍入,这正是我们习惯的四舍五入。
Decimal模块还提供了其他舍入模式,常用的包括ROUND_HALF_EVEN(银行家舍入)、ROUND_DOWN(向零舍入)、ROUND_CEILING(向正无穷舍入)、ROUND_FLOOR(向负无穷舍入)等,可以根据业务需要灵活选择。对于金融、财务类的计算,强烈建议全程使用Decimal,避免浮点误差累积。
如果只是偶尔需要对浮点数做四舍五入,也可以先用str()转换再构造Decimal,这是一个简单实用的技巧:
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(value, digits=0):
"""对浮点数执行传统四舍五入"""
factor = Decimal('1').scaleb(-digits) # 生成 1、0.1、0.01 这样的系数
return Decimal(str(value)).quantize(factor, rounding=ROUND_HALF_UP)
print(round_half_up(2.675, 2)) # 2.68
print(round_half_up(3.14159, 3)) # 3.142
print(round_half_up(-0.5)) # -1
三、字符串格式化也能保留小数位,但要注意行为差异
很多开发者习惯用format()或者f-string来处理小数位,比如'{:.2f}'.format(3.14159)或者f'{3.14159:.2f}'。这种方式在展示层面很好用,但要清楚它的舍入行为。
print('{:.2f}'.format(2.675)) # 输出 2.67
print(f'{2.675:.2f}') # 输出 2.67
print('%.2f' % 2.675) # 输出 2.67,旧的百分号格式化
print(f'{0.5:.0f}') # 输出 0,走的也是银行家舍入
print(f'{1.5:.0f}') # 输出 2
可以看到,格式化操作底层同样受浮点精度和舍入规则的影响,中间值时也是向偶数靠拢。因此格式化只适合“展示用途”,也就是确定最终的显示效果时使用。如果舍入后的数值还要参与后续计算,就不应该先格式化成字符串再转回数字,这样既绕弯又容易出错。
还需要区分两个容易混淆的概念:round()返回的是数值,而格式化返回的是字符串。在拼接日志、生成报表时格式化很方便,但在数据库存储、金额运算时应该保留数值类型。
四、常见问题解答汇总
问题1:怎么对小数点后一位进行四舍五入取整数?传统做法可以借助Decimal,也可以用一个经典技巧:对正数加0.5后向下取整,即int(x + 0.5)。负数则用int(x - 0.5)。不过更推荐统一使用round_half_up函数,逻辑清晰且不易出错。
问题2:numpy数组如何批量四舍五入?numpy提供了np.round(),但它同样采用银行家舍入。如果需要传统四舍五入,可以先乘以10的n次方,加0.5后取整再除回去,代码如下:
import numpy as np arr = np.array([0.5, 1.5, 2.675, 3.14159]) digits = 2 factor = 10 ** digits result = np.floor(arr * factor + 0.5) / factor print(result) # [0.5 1.5 2.68 3.14]
问题3:为什么round(5)之后结果还是原数?注意round()的第二个参数指的是保留的小数位数,对整数调用round(x)没有意义。如果你想做“取整到最近的10”或“最近的100”,需要借助round_half_up中对digits传负数的技巧,例如round_half_up(1234, -2)会先把数字转成科学计数法形式处理,或者直接写int(Decimal('1234').quantize(Decimal('1E2'), rounding=ROUND_HALF_UP))得到1200。
问题4:Python 2和Python 3的round有什么区别?Python 2的round()是传统的四舍五入(远离零),Python 3改成了银行家舍入。如果从旧代码迁移,一定要检查涉及中间值的舍入逻辑,必要时替换为Decimal方案。
总结一下:展示用途用格式化,一般场景用内置round(),金额和精度敏感场景一律用Decimal加ROUND_HALF_UP。理解了浮点误差和银行家舍入这两个核心原因,Python中的数字舍入问题就再也不会让你踩坑了。
Python四舍五入round函数Decimal修改时间:2026-09-05 23:12:59