Python 3.x 舍入行为

2024-11-28 08:37:00
admin
原创
140
摘要:问题描述:我刚刚重读了Python 3.0 中的新增功能,它指出:函数round()舍入策略和返回类型已更改。精确半数的情况现在被舍入为最接近的偶数结果,而不是远离零。(例如,round(2.5)现在返回 2 而不是 3。)以及以下文档round():对于支持 的内置类型round(),值将四舍五入为最接近的...

问题描述:

我刚刚重读了Python 3.0 中的新增功能,它指出:

函数round()舍入策略和返回类型已更改。精确半数的情况现在被舍入为最接近的偶数结果,而不是远离零。(例如,round(2.5)现在返回 2 而不是 3。)

以及以下文档round()

对于支持 的内置类型round(),值将四舍五入为最接近的 10 的 n 次方倍数;如果两个倍数同样接近,则向偶数方向舍入

因此,在Python 2(例如 v2.7.3)中我得到了预期的结果:

round(2.5)
3.0

round(3.5)
4.0

但是,现在在Python 3下(例如 v3.2.3):

round(2.5)
2

round(3.5)
4

这似乎违反直觉,与我对舍入的理解相反(并且注定会让人困惑)。英语不是我的母语,但直到我读到这篇文章,我才知道舍入是什么意思 :-/ 我确信在 Python 3 推出时一定对此进行了一些讨论,但我在搜索中找不到一个很好的理由。

  1. 有人知道为什么要将其改为这样吗?

  2. 是否有其他主流编程语言(例如C、C++、Java、Perl等)可以进行这种(对我来说不一致的)舍入?

我在这里遗漏了什么?

更新:@Li-aungYip 关于“银行家舍入”的评论给了我正确的搜索词/关键词,然后我发现了这个 SO 问题:为什么 .NET 使用银行家舍入作为默认设置?,所以我会仔细阅读。


解决方案 1:

Python 3 的方法(称为“四舍五入为偶数”或“银行家舍入”)现在被认为是标准的舍入方法,尽管一些语言实现尚未实现。

简单的“始终将 0.5 四舍五入”技术会导致略微偏向较高的数字。对于大量计算,这可能会很严重。Python 3.0 方法消除了这个问题。

常用的舍入方法不止一种。IEEE 754(浮点数学国际标准)定义了五种不同的舍入方法(Python 3.0 使用的方法是默认方法)。还有其他方法。

这种行为并不像它应该的那样广为人知。如果我没记错的话,AppleScript 是这种舍入方法的早期采用者。AppleScriptround中的命令提供了几个选项,但舍入为偶数是默认选项,就像 IEEE 754 中一样。显然,实现该命令的工程师round对所有“让它像我在学校学到的那样工作”的要求感到厌烦,因此他只实现了这个:round 2.5 rounding as taught in school是一个有效的 AppleScript 命令。:-)

解决方案 2:

您可以使用Decimal 模块控制 Py3000 中的舍入:

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')

解决方案 3:

只需在这里添加文档中的一个重要说明:

https://docs.python.org/dev/library/functions.html#round

笔记

round() 对浮点数的行为可能会令人惊讶:例如,round(2.675, 2) 给出的结果为 2.67,而不是预期的 2.68。这不是错误:这是由于大多数小数不能精确表示为浮点数所致。有关更多信息,请参阅浮点运算:问题和限制。

因此,在 Python 3.2 中得到以下结果不要感到惊讶:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)

解决方案 4:

Python 3.x 将 .5 值舍入为相邻的偶数

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

但是,如果需要的话,可以将小数舍入“改回”为始终将 .5 向上舍入:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int

解决方案 5:

我最近也遇到了这个问题。因此,我开发了一个 Python 3 模块,它有 2 个函数trueround()trueround_precision()可以解决这个问题,并提供我们在小学时习惯的相同舍入行为(不是银行家舍入)。这是模块。只需保存代码并将其复制或导入即可。

注意:trueround_precision 模块可以根据 decimal 模块中的 ROUND_CEILING、ROUND_DOWN、ROUND_FLOOR、ROUND_HALF_DOWN、ROUND_HALF_EVEN、ROUND_HALF_UP、ROUND_UP 和 ROUND_05UP 标志根据需要更改舍入行为(有关详细信息,请参阅该模块文档)。有关以下函数,请参阅文档字符串或使用help(trueround)help(trueround_precision)复制到解释器中以获取更多文档。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)
    
    example:
        
        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.
    
    number is the floating point number needed rounding
    
    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.
    
    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)
    
    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:
        
        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

解决方案 6:

Python 3 中的 Python 2 舍入行为。

小数点后第 15 位加 1,精度达 15 位。

round2=lambda x,y=None: round(x+1e-15,y)

175.57 不对。因为数字增加时应该将其添加到小数点后 13 位。改用十进制比重新发明轮子要好。

from decimal import Decimal, ROUND_HALF_UP
def round2(x, y=2):
    prec = Decimal(10) ** -y
    return float(Decimal(str(round(x,3))).quantize(prec, rounding=ROUND_HALF_UP))

未使用 y

解决方案 7:

一些案例:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

修复方法:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

如果想要更多小数,例如 4,则应添加 (+ 0.0000001)。

为我工作。

解决方案 8:

样品复制:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API:https ://docs.python.org/3/library/functions.html#round

状态:

返回四舍五入到小数点后 ndigits 精度的数字。如果 ndigits 被省略或为 None,则返回最接近其输入的整数。

对于支持 round() 的内置类型,值会被四舍五入为最接近的 10 的次方减 ndigits 的倍数;如果两个倍数同样接近,则四舍五入为偶数(例如,round(0.5) 和 round(-0.5) 均为 0,round(1.5) 为 2)。任何整数值对于 ndigits 都有效(正数、零或负数)。如果 ndigits 被省略或为 None,则返回值为整数。否则,返回值的类型与 number 相同。

对于一般的 Python 对象数字, round 委托给number.round

注意:round() 对浮点数的行为可能会令人惊讶:例如,round(2.675, 2) 给出的结果为 2.67,而不是预期的 2.68。这不是错误:这是由于大多数小数不能精确表示为浮点数所致。有关更多信息,请参阅浮点运算:问题和限制。

有了这种见解,你可以用一些数学来解决这个问题

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

现在您可以使用 my_round 而不是 round 来运行相同的测试。

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']

解决方案 9:

我建议为 DataFrame 工作的自定义函数:

def dfCustomRound(df, dec):
    d = 1 / 10 ** dec
    df = round(df, dec + 2)
    return (((df % (1 * d)) == 0.5 * d).astype(int) * 0.1 * d * np.sign(df) + df).round(dec)

解决方案 10:

没有小数,

def my_round(x: float, precision: int = 2):
    tmp = round(x, precision)
    if x == tmp + float('0.' + '0' * precision + '5'):
        return round(x + float('0.' + '0' * precision + '1'), precision)
    return tmp

解决方案 11:

# round module within numpy when decimal is X.5 will give desired (X+1)

import numpy as np
example_of_some_variable = 3.5
rounded_result_of_variable = np.round(example_of_some_variable,0)
print (rounded_result_of_variable)

解决方案 12:

试试这个代码:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

结果是:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

您可以在此处查看输出:
https://i.sstatic.net/QQUkS.png

解决方案 13:

您可以使用 math.ceil 模块控制舍入:

import math
print(math.ceil(2.5))
> 3
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1259  
  IPD(Integrated Product Development)流程管理作为一种先进的产品开发管理理念和方法,在提升企业创新能力方面发挥着至关重要的作用。它打破了传统产品开发过程中部门之间的壁垒,通过整合资源、优化流程,实现产品的快速、高效开发,为企业在激烈的市场竞争中赢得优势。IPD流程管理的核心概念IPD流程...
IPD流程中PDCP是什么意思   11  
  IPD(Integrated Product Development)流程管理作为一种先进的产品开发管理模式,旨在通过整合各种资源,实现产品的高效、高质量开发。在这一过程中,团队协作无疑是成功的关键。有效的团队协作能够打破部门壁垒,促进信息共享,提升决策效率,从而确保产品开发项目顺利推进。接下来,我们将深入探讨IPD流...
IPD培训课程   9  
  IPD(Integrated Product Development)研发管理体系作为一种先进的产品开发理念和方法,在众多企业中得到了广泛应用。它旨在打破部门壁垒,整合资源,实现产品开发的高效、协同与创新。在项目周期方面,IPD研发管理体系有着深远且多维度的影响,深入剖析这些影响,对于企业优化产品开发流程、提升市场竞争...
华为IPD流程   11  
  IPD(Integrated Product Development)流程管理是一种先进的产品开发管理模式,旨在通过整合企业的各种资源,实现产品的高效、高质量开发。它涵盖了从产品概念提出到产品退市的整个生命周期,对企业的发展具有至关重要的意义。接下来将详细阐述IPD流程管理的五个阶段及其重要性。概念阶段概念阶段是IPD...
IPD概念阶段   12  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用