Python3的四舍五入round()函数坑爹?不,更科学

问题描述

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凑成双数,高位为双数则不进位。

这让我想起了大二时候的大物实验,第一节讲的计数方法,其中印象最深刻的就是“四舍六入五成双”,它的作用是让统计数据更公平,降低舍入的误差

具体查阅:

  1. 浮点算术:争议和限制
  2. What Every Computer Scientist Should Know About Floating-Point Arithmetic




进一步阅读

由于有浮点精度表示错误,在追求精度的情况下应使用 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'>

对比

方法耗时(运算一百万次)易读性
使用 decimal1.99 s
使用 math1.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




操作建议

  1. 放到后面再四舍五入
  2. 遵守当地货币规定
  3. 纠结的话就用ROUND_HALF_EVEN




浮点数判等

math.isclose(a, b)




参考文献

  1. Python2 在线工具
  2. Python3 在线工具
  3. 内置函数 — Python文档
  4. How to Round Numbers in Python
  5. math - how to round to higher 10’s place in python
  6. How to round float 0.5 up to 1.0, while still rounding 0.45 to 0.0, as the usual school rounding
  7. Python快速计算函数耗时timeit
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XerCis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值