RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1608067
Accepted
Alexander
Alexander
Asked:2025-03-04 04:06:58 +0000 UTC2025-03-04 04:06:58 +0000 UTC 2025-03-04 04:06:58 +0000 UTC

为什么 python 没有数学四舍五入的函数?

  • 772

对于普通的数学舍入,您必须使用第三方模块和 2 个函数:

from decimal import Decimal, ROUND_HALF_UP

d = Decimal(5.445).quantize(Decimal("1.00"), ROUND_HALF_UP)

为什么他们不向内置解释器添加一个仅用于数学舍入而不是银行家舍入的圆形类型函数?并且是否存在在性能和代码可读性方面更简单的舍入选项?

python
  • 2 2 个回答
  • 221 Views

2 个回答

  • Voted
  1. Best Answer
    Serge3leo
    2025-03-06T08:37:36Z2025-03-06T08:37:36Z

    为什么他们不向内置解释器添加一个仅用于数学舍入而不是银行家舍入的 round 类型函数?

    这个很难说,可能是因为常见的处理器(x86/ARM)和传统语言/OS(Fortran-2003/C17/C++/POSIX)一样,都支持4种浮点舍入:

    • FE_DOWNWARD(roundTowardNegative 或floor());
    • FE_TONEAREST (roundTiesToEven 或nearbyint(),默认舍入,rint());
    • FE_TOWARDZERO (roundTowardZero 或int());
    • FE_UPWARD (roundTowardPositive 或ceil());
    • 并且只有 Fortran-2018 和 C23 描述:FE_TONEARESTFROMZERO (roundTiesToAway 或round())。

    真的,这并不妨碍该函数最初round()在 C/C++/POSIX 中可用吗?嗯,是的,不是某个团队喜欢它rint(),但是有些人喜欢它,而且有时它是必要的。

    并且是否存在在性能和代码可读性方面更简单的舍入选项?

    我们还有什么其他选择?

    标准,适用于float(无支撑math.inf和math.nan):

    import math
    def math_roundTiesToAway(x):
        return math.trunc(x + math.copysign(0.5, x))
    

    对于数组和 NumPy 类型:

    import numpy as np
    def np_roundTiesToAway(x):
        return np.trunc(x + np.copysign(0.5, x))
    

    就可读性而言(即不需要了解数学):

    import ctypes
    import sys
    
    if sys.platform.startswith("darwin"):
        libc = ctypes.CDLL("libSystem.B.dylib")
    elif sys.platform.startswith('win'):
        libc = ctypes.cdll.msvcrt
    else:
        libc = ctypes.CDLL("libc.so.6")
    
    libc.round.argtypes = [ctypes.c_double]
    libc.round.restype = ctypes.c_double
    roundTiesToAway = libc.round
    

    但是它很长,并且float速度不是很快。但是,像np_roundTiesToAway(),它是和的朋友math.inf,math.nan一切都符合IEEE-754。

    Cython 有选项,它们速度更快,并且round()从 C(来自 libc)使用,即更可靠。

    x = 2.675
    a0 = np.double(x)
    a1000 = np.full(1000, a0)
    q = Decimal("1.")
    %timeit math_roundTiesToAway(x)
    %timeit float(math_roundTiesToAway(x))
    %timeit np_roundTiesToAway(a0)
    %timeit np_roundTiesToAway(a1000)
    %timeit roundTiesToAway(x)
    %timeit Decimal(x).quantize(q, ROUND_HALF_UP)
    
    99.6 ns ± 2.89 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    130 ns ± 11.7 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
    1.73 μs ± 22.4 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    3.81 μs ± 96.2 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    348 ns ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    868 ns ± 9.77 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    

    由于math_roundTiesToAway(x)( math.trunc(x)) 是类型int,因此添加了 的评估float(math_roundTiesToAway(x))。

    附言

    尽管四舍五入到整数也不错。然而,当涉及到四舍五入到小数点后 N 位时,问题陈述开始变得模棱两可。

    如果参数的类型为,那么就像标准 round() 函数float一样,由于参数的二进制表示精度有限,您可能会得到稍微意想不到的结果:

    十 x.hex() ≈x n 位数字 decimal_roundTiesToAway math_roundTiesToAway
    0.15 0x1.33333333333333p-3 ≈0.14999999999999999 1 0.1 0.2
    0.145 0x1.28f5c28f5c28fp-3 ≈0.14499999999999999 2 0.14 0.14
    2.675 0x1.56666666666666p+1 ≈2.6749999999999998 2 2.67 2.68
    2.0115 0x1.0178d4fdf3b64p+1 ≈2.0114999999999998 3 2.011 2.011
    0.0000005 0x1.0c6f7a0b5ed8dp-21 ≈4.99999999999999998e-7 6 0.000000 1e-06

    如果应用的目的roundTiesToAway()纯粹是计算/数学的,例如,最低有效数字的均匀分布,或将 0 出现的概率减半,那么这不是问题。一切都严格形式上正确,所有目标都已实现。

    由于对于该类型来说Decimal,10 的幂的乘法是精确的,因此这样的惊喜还有很多,几乎所有的惊喜都是如此。操作中的舍入误差math.fma(x, q, math.copysign(0.5, x))会稍微降低此类意外发生的可能性,但并不能完全消除。

    但是,如果目标是获得一个漂亮的结果或者满足其他一些要求,那么问题的陈述可以进行修改,即如果半最低位数字的边界位于[x - 𝛿, x + 𝛿]𝛿大于 0 但小于的范围内,则向上舍入math.ulp(x)(即,如果数字i..i,f..f50..0等于x机器精度,则应应用小数舍入规则)。

    import decimal
    
    _drtta_q0 = decimal.Decimal("1")
    
    def decimal_roundTiesToAway(x, ndigits=0, pretty=False,
                                pretty_delta_x = float.fromhex('0x1.8p-1')  # 0.75ulp
                               ):
        """
        decimal_roundTiesToAway(x, n) - округлённое до n-цифр машинное представление x
        str(decimal_roundTiesToAway(x, n, pretty=True)) - округлённое до n-цифр str(x)
        """
        dx = decimal.Decimal(x)
        if not ndigits:
            return dx.quantize(_drtta_q0, decimal.ROUND_HALF_UP)
        # assert math.ulp(x)/math.fabs(dx - dx.next_toward(0)) >= 2, \
        #     "Default context precision too small"
        q = decimal.Decimal(f"1e{-ndigits}")
        if not pretty or isinstance(x, str) or isinstance(x, decimal.Decimal):
            return dx.quantize(q, decimal.ROUND_HALF_UP)
        return (dx + 
                decimal.Decimal(math.copysign(pretty_delta_x*math.ulp(x), x))
               ).quantize(q, decimal.ROUND_HALF_UP)
    
    import math
    
    try:
        from math import fma as lc_fma  # Python 3.13 и выше
    except ImportError:
        import ctypes
        import sys
    
        if sys.platform.startswith("darwin"):
            _libc = ctypes.CDLL("libSystem.B.dylib")
        elif sys.platform.startswith('win'):
            _libc = ctypes.cdll.msvcrt
        else:
            _libc = ctypes.CDLL("libc.so.6")
            
        _libc.fma.argtypes = [ctypes.c_double, ctypes.c_double, ctypes.c_double]
        _libc.fma.restype = ctypes.c_double
        lc_fma = _libc.fma
    
    def math_roundTiesToAway(x, ndigits=0, pretty=False,
                             pretty_delta_x = float.fromhex('0x1.p-1')  # 0.5ulp
                            ):
        """
        math_roundTiesToAway(x, n) - округлённое до n-цифр машинное представление x
        str(math_roundTiesToAway(x, n, pretty=True)) - округлённое до n-цифр str(x)
        """
        if not ndigits:
            return math.trunc(x + math.copysign(0.5, x))
        if ndigits < 0:
            q = float(10**-ndigits)
            return math.trunc(x/q + math.copysign(0.5, x))*q
        q = float(10**ndigits)
        if not pretty:
            return math.trunc(lc_fma(x, q, math.copysign(0.5, x)))/q
        xl = math.copysign(pretty_delta_x*math.ulp(x), x)
        h = x + xl
        l = xl - (h - x)
        x, xl = h, l
        # assert x == x + xl  and  abs(xl) < math.ulp(x)
        ql = float(10**ndigits - int(q))
        # assert q == (q + ql)  and  abs(ql) < math.ulp(q)
        h = x*q
        l = lc_fma(x, q, -h)
        l += ql*x + xl*q 
        xq = h + l
        xql = l - (xq - h)
        # assert xq == (xq + xql)  and  abs(xql) < math.ulp(xq)
        a05 = math.copysign(0.5, x)
        h = xq + a05
        t = h - xq
        l = (xq - (h - t)) + (a05 - t)
        l += xql
        xqa05 = h + l
        xqa05l = l - (xqa05 - h)
        # assert xqa05 == (xqa05 + xqa05l)  and  abs(xqa05l) < math.ulp(xqa05)
        txqa05 = math.trunc(xqa05)
        if txqa05 == xqa05:
            txqa05 += math.floor(xqa05l) if 0 < xqa05 else math.ceil(xqa05l)
        return txqa05/q
    
    print(f"{decimal_roundTiesToAway(2.0115, 3)=}")
    %timeit decimal_roundTiesToAway(2.0115, 3)
    print(f"{math_roundTiesToAway(2.0115, 3)=}")
    %timeit math_roundTiesToAway(2.0115, 3)
    
    decimal_roundTiesToAway(2.0115, 3)=Decimal('2.011')
    1.47 μs ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    math_roundTiesToAway(2.0115, 3)=2.011
    267 ns ± 2.42 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    
    print(f"{decimal_roundTiesToAway(2.0115, 3, pretty=True)=}")
    %timeit decimal_roundTiesToAway(2.0115, 3, pretty=True)
    print(f"{math_roundTiesToAway(2.0115, 3, pretty=True)=}")
    %timeit math_roundTiesToAway(2.0115, 3, pretty=True)
    
    decimal_roundTiesToAway(2.0115, 3, pretty=True)=Decimal('2.012')
    2.42 μs ± 74.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
    math_roundTiesToAway(2.0115, 3, pretty=True)=2.012
    835 ns ± 13 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
    
    • 8
  2. Stanislav Volodarskiy
    2025-03-08T18:00:06Z2025-03-08T18:00:06Z

    因为你不能只靠类型来解决float。不管你在函数内部写什么,这都是行不通的:

    def perfect_round(x: float) -> float:
        ...
    

    我们设置任务:需要一个将实数四舍五入到小数点后两位的函数。如果第三位数字正好是5,我们就从零开始舍入。必须在图书馆最少参与的情况下解决这个问题。

    首先要决定的是如何表示参数和结果的类型。float——这是自然的答案。

    问题已经在于类型的选择。我们从中获取代码Decimal并使其适应任务:

    from decimal import Decimal, ROUND_HALF_UP
    
    
    def perfect_round(x):
        return float(Decimal(x).quantize(Decimal('1.00'), ROUND_HALF_UP))
    
    
    print(perfect_round(5.465))
    

    出了点问题。完美的舍入并不完美:5.465被向下舍入,但应该向上舍入为5.47:

    $ python perfect_round.py
    5.46
    

    因为5.465无法用float精确的格式表示。最接近的值是5.4649999999999999857891452847979962825775146484375。由于这个分数严格小于5.465,所以必须向下舍入。

    第二个较小的问题是我们也无法准确地想象结果float。在打印件上您会看到一个整数5.46,在计算机内存中则是5.45999999999999996447286321199499070644378662109375。

    我们甚至还没有开始编程,但就已经遇到了问题:很难/不可能float将四舍五入到两位数并按照用户的预期运行。

    既然问题是由选择引起的,float那你就需要回头去改变你的选择。请记住,我们想要尽可能最简单的解决方案。 Python 标准类型中只剩下一种支持小数的简单类型。这些是线条。也就是说,你需要写:

    def perfect_round(x: str) -> str:
        ...
    

    现在你明白为什么标准库中没有舍入函数了吗?

    • 1

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5