问题描述
Python2 中,round()
的结果就是我们所理解的四舍五入,round(1.5)=2,round(2.5)=3。
Python3 中,round()
有较大改动,round(1.5)=2,而round(2.5)仍然等于2,只有round(2.6)才等于3,这是为什么呢?
解决方案
原来 Python2 的 round()
是四舍五入,而 Python3 的 round()
为四舍六入五成双,即高位为单数则进1凑成双数,高位为双数则不进位。
这让我想起了大二时候的大物实验,第一节讲的计数方法,其中印象最深刻的就是“四舍六入五成双”,它的作用是让统计数据更公平,降低舍入的误差。
具体查阅:
进一步阅读
由于有浮点精度表示错误,在追求精度的情况下应使用 Decimal,务必用字符串进行实例化
from decimal import Decimal
print(0.29 * 100) # 28.999999999999996
print(Decimal(0.29) * 100) # 28.99999999999999800159855567
print(Decimal('0.29') * 100) # 29.00
print(Decimal('1.5093').quantize(Decimal('1.00'))) # 1.51
强制四舍五入
使用 decimal
from decimal import Decimal, ROUND_HALF_UP
def round(number, ndigits=None):
"""强制四舍五入"""
exp = Decimal('1.{}'.format(ndigits * '0')) if ndigits else Decimal('1')
return type(number)(Decimal(number).quantize(exp, ROUND_HALF_UP))
print(round(4.115, 2), type(round(4.115, 2)))
print(round(4.116, 2), type(round(4.116, 2)))
print(round(4.125, 2), type(round(4.125, 2)))
print(round(4.126, 2), type(round(4.126, 2)))
print(round(2.5), type(round(2.5)))
print(round(3.5), type(round(3.5)))
print(round(5), type(round(5)))
print(round(6), type(round(6)))
# 4.12 <class 'float'>
# 4.12 <class 'float'>
# 4.13 <class 'float'>
# 4.13 <class 'float'>
# 3.0 <class 'float'>
# 4.0 <class 'float'>
# 5 <class 'int'>
# 6 <class 'int'>
使用 math
import math
def round(number, ndigits=0):
"""强制四舍五入"""
exp = number * 10 ** ndigits
if abs(exp) - abs(math.floor(exp)) < 0.5:
return type(number)(math.floor(exp) / 10 ** ndigits)
return type(number)(math.ceil(exp) / 10 ** ndigits)
print(round(4.115, 2), type(round(4.115, 2)))
print(round(4.116, 2), type(round(4.116, 2)))
print(round(4.125, 2), type(round(4.125, 2)))
print(round(4.126, 2), type(round(4.126, 2)))
print(round(2.5), type(round(2.5)))
print(round(3.5), type(round(3.5)))
print(round(5), type(round(5)))
print(round(6), type(round(6)))
# 4.12 <class 'float'>
# 4.12 <class 'float'>
# 4.13 <class 'float'>
# 4.13 <class 'float'>
# 3.0 <class 'float'>
# 4.0 <class 'float'>
# 5 <class 'int'>
# 6 <class 'int'>
对比
方法 | 耗时(运算一百万次) | 易读性 |
---|---|---|
使用 decimal | 1.99 s | 高 |
使用 math | 1.21 s | 低 |
四舍五入策略
查看舍入模式,默认为 decimal.ROUND_HALF_EVEN
from decimal import getcontext
print(getcontext())
# Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
切换舍入模式
from decimal import *
print(Decimal('1.5093').quantize(Decimal('1.00'), rounding=ROUND_DOWN)) # 临时切换
getcontext().rounding = ROUND_DOWN # 全局切换
舍入模式 | 功能 |
---|---|
decimal.ROUND_CEILING | 向上取整 |
decimal.ROUND_FLOOR | 向下取整 |
decimal.ROUND_DOWN | 截断 |
decimal.ROUND_UP | 舍入到零的反方向 |
decimal.ROUND_HALF_UP | 舍入到最接近的数,同样接近则舍入到零的反方向(四舍五入) |
decimal.ROUND_HALF_DOWN | 舍入到最接近的数,同样接近则舍入方向为零 |
decimal.ROUND_HALF_EVEN | 舍入到最接近的数,同样接近则舍入到最接近的偶数 |
decimal.ROUND_05UP | 四舍五入,趋近于零 |
import math
from decimal import *
# 向上取整
def round_ceiling(n, decimals=0):
multiplier = 10 ** decimals
return math.ceil(n * multiplier) / multiplier
getcontext().rounding = ROUND_CEILING
print('ROUND_CEILING')
print(Decimal('1.32').quantize(Decimal('1.0'))) # 1.4
print(Decimal('-1.32').quantize(Decimal('1.0'))) # -1.3
print(round_ceiling(1.32, 1.0)) # 1.4
print(round_ceiling(-1.32, 1.0)) # -1.3
# 向下取整
def round_floor(n, decimals=0):
multiplier = 10 ** decimals
return math.floor(n * multiplier) / multiplier
getcontext().rounding = ROUND_FLOOR
print('ROUND_FLOOR')
print(Decimal('1.32').quantize(Decimal('1.0'))) # 1.3
print(Decimal('-1.32').quantize(Decimal('1.0'))) # -1.4
print(round_floor(1.32, 1.0)) # 1.4
print(round_floor(-1.32, 1.0)) # -1.3
# 截断
def round_down(n, decimals=0):
multiplier = 10 ** decimals
return int(n * multiplier) / multiplier
getcontext().rounding = ROUND_DOWN
print('ROUND_DOWN')
print(Decimal('1.32').quantize(Decimal('1.0'))) # 1.3
print(Decimal('-1.32').quantize(Decimal('1.0'))) # -1.3
print(round_down(1.32, 1.0)) # 1.3
print(round_down(-1.32, 1.0)) # -1.3
# 将所有值从零进行舍入
getcontext().rounding = ROUND_UP
print('ROUND_UP')
print(Decimal('1.32').quantize(Decimal('1.0'))) # 1.4
print(Decimal('-1.32').quantize(Decimal('1.0'))) # -1.4
# 四舍五入到最接近的数字,并通过从0舍入来打破平局
def round_half_up(n, decimals=0):
def _round_half_up(n, decimals=0):
multiplier = 10 ** decimals
return math.floor(n * multiplier + 0.5) / multiplier
rounded_abs = _round_half_up(abs(n), decimals)
return math.copysign(rounded_abs, n)
getcontext().rounding = ROUND_HALF_UP
print('ROUND_HALF_UP')
print(Decimal('1.35').quantize(Decimal('1.0'))) # 1.4
print(Decimal('-1.35').quantize(Decimal('1.0'))) # -1.4
print(round_half_up(1.35, 1.0)) # 1.4
print(round_half_up(-1.35, 1.0)) # -1.4
# 舍入到最接近的数,同样接近则舍入方向为零
getcontext().rounding = ROUND_HALF_DOWN
print('ROUND_HALF_DOWN')
print(Decimal('1.35').quantize(Decimal('1.0'))) # 1.3
print(Decimal('-1.35').quantize(Decimal('1.0'))) # -1.3
# 舍入到最接近的数,同样接近则舍入到最接近的偶数
getcontext().rounding = ROUND_HALF_EVEN # 默认
print('ROUND_HALF_EVEN')
print(Decimal('15.255').quantize(Decimal('1'))) # 15
print(Decimal('15.255').quantize(Decimal('1.0'))) # 15.3
print(Decimal('15.255').quantize(Decimal('1.00'))) # 15.26
# 四舍五入,趋近于零
getcontext().rounding = ROUND_05UP # 四舍五入为0或5则取反方向的值
print('ROUND_05UP')
print(Decimal('1.38').quantize(Decimal('1.0'))) # 1.3
print(Decimal('1.35').quantize(Decimal('1.0'))) # 1.3
print(Decimal('-1.35').quantize(Decimal('1.0'))) # -1.3
print(Decimal('1.49').quantize(Decimal('1.0'))) # 1.4
print(Decimal('1.51').quantize(Decimal('1.0'))) # 1.6
常用函数
函数 | 功能 | 例子 |
---|---|---|
round() | 四舍六入五成双 | round(1.5) == 2 round(2.5) == 2 |
math.ceil() | 向上取整 | math.ceil(1.5) == 2 math.ceil(2) == 2 |
math.floor() | 向下取整 | math.floor(2.5) == 2 math.floor(2) == 2 |
向上取整到十位
from decimal import Decimal, ROUND_UP
def ceil_to_tens_decimal(x):
"""向上取整到十位
>>> ceil_to_tens_decimal(10.2)
Decimal('20')
>>> ceil_to_tens_decimal(10.0)
Decimal('10')
>>> ceil_to_tens_decimal(16.7)
Decimal('20')
>>> ceil_to_tens_decimal(94.9)
Decimal('100')
"""
return (Decimal(x) / 10).quantize(1, rounding=ROUND_UP) * 10
操作建议
- 放到后面再四舍五入
- 遵守当地货币规定
- 纠结的话就用ROUND_HALF_EVEN