Python 3.x 舍入行为

2024-11-28 08:37:00
admin
原创
6
摘要:问题描述:我刚刚重读了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
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   642  
  引言在当今快速变化的科技市场中,企业要想保持竞争力,就必须具备高效的产品开发流程。小米作为一家以创新驱动的科技公司,其集成产品开发(IPD)流程在业界颇受关注。其中,技术路线图规划作为IPD流程的核心环节,对于确保产品技术领先、满足市场需求以及实现长期战略目标至关重要。本文将深入探讨小米IPD流程中的技术路线图规划,分...
华为IPD是什么   0  
  在当今快速变化的商业环境中,项目管理的高效执行是企业成功的关键。为了应对日益复杂的产品开发挑战,企业纷纷寻求将产品开发流程(Product Development Process, PDCP)与集成产品开发(Integrated Product Development, IPD)流程相结合的策略,以实现更高效、更协同的...
IPD管理   0  
  在当今竞争激烈的市场环境中,提高客户满意度是企业持续发展和成功的关键。为了实现这一目标,企业需要不断优化其产品开发和管理流程。IPD(Integrated Product Development,集成产品开发)流程图作为一种高效的项目管理工具,能够帮助企业实现跨部门协作、优化资源配置,并最终提升客户满意度。本文将深入探...
IPD流程是谁发明的   0  
  在项目管理领域,集成产品开发(IPD, Integrated Product Development)流程被视为提升项目成功率的关键框架。IPD通过其系统化的方法,将产品开发过程中的各个阶段紧密连接,确保从概念到市场的每一步都经过深思熟虑和高效执行。本文将深入探讨IPD流程的六个核心阶段如何深刻影响项目成功,并为项目管...
IPD流程中CDCP   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用