Python 3.x 舍入行为

Posted

技术标签:

【中文标题】Python 3.x 舍入行为【英文标题】:Python 3.x rounding behavior 【发布时间】:2012-06-05 06:10:30 【问题描述】:

我只是在重读What’s New In Python 3.0,它说:

round() 函数舍入策略和返回类型已更改。 确切的中途情况现在四舍五入到最接近的偶数结果 远离零。 (例如,round(2.5) 现在返回 2 而不是 3.)

和 round 的文档:

对于支持 round() 的内置类型,值被四舍五入到 最接近 10 次幂减 n 的倍数;如果两个倍数是 同样接近,向偶数选择进行舍入

所以,在 v2.7.3 下:

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

正如我所料。但是,现在在 v3.2.3 下:

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

这似乎违反直觉,与我的理解相反 四舍五入(并且一定会绊倒人)。英语不是我的母语,但 在我读到这篇文章之前,我以为我知道四舍五入是什么意思:-/我确定 在引入 v3 时,一定有一些讨论 这个,但我无法在搜索中找到一个很好的理由。

    有没有人知道为什么会变成这样? 是否有任何其他主流编程语言(例如,C、C++、Java、Perl, ..)进行这种(在我看来不一致)舍入?

我在这里错过了什么?

更新:@Li-aungYip 对“银行家的四舍五入”的评论给了我正确的搜索词/关键字来搜索,我发现了这个 SO 问题:Why does .NET use banker's rounding as default?,所以我会仔细阅读。

【问题讨论】:

不是不一致的。它说...如果介于两者之间,则四舍五入到最接近的偶数。 我没有时间查这个,但我相信这被称为“银行家的四舍五入”。我相信这在金融行业很常见。 @sberry 好吧,是的,它的行为与它自己的描述是一致的。因此,如果它说“四舍五入”是其价值的两倍并且做到了,它也是一致的 :) .. 但它似乎与通常的舍入 含义 相悖。所以我正在寻找更好的理解。 相关:***.com/questions/10093783/… 请注意:银行四舍五入在金融领域并不常见。这就是我在 70 年代在小学时就被教导要四舍五入的方法:-) 【参考方案1】:

Python 3 的方式(称为“round half to even”或“banker's rounding”)现在被认为是标准的舍入方法,尽管一些语言实现还没有出现。

简单的“always round 0.5 up”技术会稍微偏向较大的数字。通过大量计算,这可能很重要。 Python 3.0 方法消除了这个问题。

常用的舍入方法不止一种。浮点数学的国际标准 IEEE 754 定义了five different rounding methods(Python 3.0 使用的是默认值)。还有there are others。

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

【讨论】:

我不知道这种“这些天几乎普遍采用的默认标准舍入方法”,您(或其他任何人)是否知道 C/C++/Java/Perl 或任何其他“主流" 语言实现舍入的方式相同? Ruby 做到了。 Microsoft 的 .NET 语言可以做到这一点。不过,Java 似乎没有。我无法为每一种可能的语言找到它,但我想它在最近设计的语言中最常见。我想 C 和 C++ 已经足够老了,以至于他们没有。 ruby 为 2.5.round 返回 3 我添加了一些关于 AppleScript 对此的处理,因为我喜欢实现“旧”行为的讽刺方式。 @kindall 自 1985 年(当 IEEE 754-1985 发布时)以来,此方法一直是 IEEE 默认舍入模式。它也是 C 中的默认舍入模式,至少从 C89 开始(因此也在 C++ 中),然而,因为 C99(以及在此之前具有零星支持的 C++11)“round() " 功能已经可用,它使用从零开始的关系。内部浮点舍入和 rint() 系列函数仍然遵循舍入模式设置,默认舍入到偶数。【参考方案2】:

您可以使用Decimal module 控制在 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')

【讨论】:

谢谢 .. 我不熟悉这个模块。知道如何获得 Python v 2.x 的行为吗?您展示的示例似乎没有这样做。只是好奇这是否可能。 @Levon:常量ROUND_HALF_UP 与 Python 2.X 的旧行为相同。 您还可以为 Decimal 模块设置一个上下文,它会为您隐式执行此操作。请参阅setcontext() 函数。 这正是我今天要找的。在 Python 3.4.3 中按预期工作。另外值得注意的是,如果您想四舍五入到最接近的 100,例如为了钱,您可以通过将 quantize(decimal.Decimal('1') 更改为 quantize(decimal.Decimal('0.00') 来控制它的舍入量。 只要ndigits 是肯定的,此解决方案就可以替代round(number, ndigits),但令人讨厌的是,您不能用它来替代round(5, -1) 之类的东西。【参考方案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)

【讨论】:

我看到了。我的第一反应是:谁在使用无法表示 "2.67x" 的所有排列的 16 位 CPU?说分数不能用浮点数表示在这里似乎是替罪羊:没有任何现代 CPU 是不准确的,用任何语言(Python 除外?) @Adam:我认为你误会了。用于存储浮点数的二进制格式(IEEE 754 binary64)不能准确表示2.675:计算机可以得到的最接近的是2.67499999999999982236431605997495353221893310546875。这非常接近,但它不完全等于2.675:它稍微更接近2.67而不是2.68。所以round 函数做了正确的事情,并将其四舍五入到更接近的点后两位值,即2.67。这与 Python 无关,与二进制浮点有关。 这不是“正确的事情”,因为它被赋予了源代码常量:),但我明白你的意思。 @Adam:我之前在 JS 中遇到过同样的怪癖,所以它不是特定于语言的。【参考方案4】:

Python 3.x 将 0.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

但是,如果需要,可以将小数舍入“向后”更改为始终向上舍入 0.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 模块可以根据需要根据小数模块中的 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)

【讨论】:

你能解释一下这个公式背后的直觉吗? 据我了解,无法准确表示的分数最多有 15 个 9,然后是不精确。例如,2.6752.67499999999999982236431605997495353221893310546875。添加 1e-15 将使其超过 2.675 并使其正确四舍五入。如果分数已经超过代码常量,添加 1e-15 不会改变舍入。 不错的技巧也适用于3.46//0.01==345,但(3.46+1E-15)//0.01==346 需要 在某些情况下这会阻止正确舍入吗?我的意思是除了真实数字恰好是 x.xxx9999999999999 的情况之外,在这种情况下,您无法确定 9s 是停止还是继续,因为这是普通 float64 的最大精度,实际上略高于 float64,具体取决于哪个方向您正在转换 bi-dec-bi 或 dec-bi-dec 以及您需要在哪个数字系统中保持准确性。 (所有假设没有具有真实分数或任意精度的外部确认计算。)【参考方案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)。

为我工作。

【讨论】:

这是唯一对我有用的解决方案,感谢发帖。每个人似乎都打算向上/向下舍入 0.5,所以我无法处理多小数舍入问题。【参考方案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,则返回值为整数。 否则返回值与数字类型相同。

对于一般的 Python 对象编号,将委托四舍五入到 数。圆形

注意对于浮点数的 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】:

在学校教授的 Python 3.x 中进行舍入的最简单方法是使用辅助变量:

n = 0.1 
round(2.5 + n)

这些将是 2.0 到 3.0 系列的结果(以 0.1 步):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3

【讨论】:

【参考方案10】:

试试这个代码:

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 

Ooutput 你可以在这里查看: https://i.stack.imgur.com/QQUkS.png

【讨论】:

【参考方案11】:

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

import math
print(math.ceil(2.5))
> 3

【讨论】:

这将始终返回不带小数部分的数字,这不是四舍五入。 ceil(2.5) = 2, ceil(2.99) = 2 在python3+中,如果number参数为正数或负数,ceil函数返回上限值。 输入[14]:math.ceil(2.99)输出[14]:3 是的,很抱歉我错了。 Ceil() 返回上限值,而 floor() 返回我正在谈论的那个值。但是,在我看来,这仍然不是舍入行为(这两个函数)

以上是关于Python 3.x 舍入行为的主要内容,如果未能解决你的问题,请参考以下文章

Powershell:将分数转换为整数 - 令人惊讶的舍入行为

java struts2 奇怪的序列化行为(自行舍入大数字)

BigDecimal的精度舍入模式详解

浮点除法的软件实现,舍入问题

BigDecimal的舍入模式

ldexp 应该正确舍入