0%

Python 四舍五入用 round() ?

网上一查很多资料都说 Python 内置的 round() 是用于四舍五入的函数,其实它包含着很大的坑,本文将会说明。

说明

先上几个正常的例子熟悉下用法:

1
2
3
4
5
6
7
# Python 3.7.0
>>> round(1.6)
2
>>> round(1.66, 1)
1.7
>>> round(1.666, 2)
1.67

再上几个打脸的例子:

1
2
3
4
5
# Python 3.7.0
>>> round(0.5)
0
>>> round(2.5)
2

纳尼?小数部分的 0.5 为啥被舍掉了,不应该“五入”进一吗?查阅官方文档可知, round() 函数有两个参数,依次是:

  • number: 整数或浮点数
  • ndigits(可选):整数。若被省略或为 None 则函数返回整数,否则函数返回值的类型与 number 相同。

函数的作用为:

Return number rounded to ndigits precision after the decimal point.

翻译过来不就是四舍五入的意思吗?仔细往下看文档,特殊场景下不是的:

if two multiples are equally close, rounding is done toward the even choice.

这句话书上的解释是:

“当某个值恰好等于两个整数间的一半时,取整操作会取到离该值最接近的那个偶数上”
——《Python Cookbook 中文版第 3 版》 P83

也就是说,判断进位的数恰好是 5 的情况,要看 5 的前一位是奇数还是偶数,如果是奇数,则进一,例如 round(1.5) 的结果是 2 ;如果是偶数,则不会进一,而是直接把 5 舍掉,例如 round(2.5) 的结果也是 2 。以上举的几个例子都是浮点数,不过仔细看文档和书上对 round() 函数第二个参数 ndigits 的说明,会发现:

“传递给 round() 的参数 ndigits 可以是负数,在这种情况下会相应地取整到十位、百位、千位等”
——《Python Cookbook 中文版第 3 版》 P83

也就是说,给第二个参数 ndigits 传入 -1, 会取整到十位,传入 -2,会取整到百位,以此类推。以取整到十位为例,示例如下:

1
2
3
4
5
# Python 3.7.0
>>> round(15, -1)
20
>>> round(25, -1)
20

如果你想:我对判断进位的数是 5 的情况特殊处理,还是可以达到四舍五入的目的的。那么请再看下面的示例:

1
2
3
# Python 3.7.0
>>> round(2.675, 2)
2.67

Excuse me??? 不是说 5 的前一位是奇数的话要进一么,怎么又不对了?难道是 Python 的 Bug ? Python 官方文档说这不算 Bug ,而是浮点数固有的精度丢失问题。我们都知道,计算机存储的任何数据其实都是二进制流,也就是一串 0 和 1。整数在计算机内是可以精确存储的,而大多数浮点数则不然。因为大多数浮点数若用二进制表示,会变得很长甚至无限长,计算机需要将其截断才能存储,这样就丢失了精度。就拿 2.675 来说,它要存储到计算机里,首先要转为二进制,结果为:10.1010110011001100(假设计算机截断小数点后十六位),如下图所示:

我们把计算机存储 2.675 的这个二进制串再转回十进制,就变成了:2.67498779296875 ,如下图所示:

所以, round(2.675, 2) 的结果就成了 2.67 。更详细的说明可以看参考文献 2

替代方案

既然 Python 四舍五入不可以用 round(),那有没有替代方案呢?
当然有, decimal 模块就是用于浮点数的精确运算的,其中就包括四舍五入。用法示例如下:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from decimal import *

# 使用当前上下文的一个副本,退出 with 语句时会恢复之前的上下文
with localcontext() as ctx:
ctx.prec = 3 # 设置精度(整数和小数部分加起来的位数)
ctx.rounding = ROUND_HALF_UP # 设置舍入方式为四舍五入
print(Decimal('2.675') + 0) # 后面 + 0 是因为精度和舍入仅在运算期间发挥作用

运行这段代码,输出结果为 2.68
关于 decimal 模块的详细用法和相关问题,可以看参考文献 3

总结

Python 四舍五入不可以用 round(),需要用到 decimal 模块。

参考文献

  1. round() 官方文档
  2. 官方文档对于浮点数精度丢失的说明
  3. 官方文档:decimal — 十进制定点和浮点运算
觉得文章有帮助,打赏1元鼓励一下作者