Python 3.x 舍入行为

2024-11-28 08:37:00
admin
原创
193
摘要:问题描述:我刚刚重读了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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3970  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2740  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   79  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   87  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   74  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用