python四舍五入(round精度不够,有时不能实现四舍五入)

python四舍五入(round精度不够,有时不能实现四舍五入)

码农世界 2024-05-28 后端 81 次浏览 0个评论

Python 所有文章传送门
【Python】所有文章传送门

目录

  • 简述 / 前言
  • 1. Python 实验
  • 2. 自定义函数
  • 3. 总结

    简述 / 前言

    最近心血来潮,刚复习到折半插入排序时,发现算法的mid(中间点)选择的公式是:(low + high)/2,那么当(low + high)出现奇数时怎么办呢,比如(low + high)/2 = 1.5,那么是取 1 还是 2 呢?于是我在 Python 中实验了一下,由于 Python 中浮点数运算结果还是浮点数,因此我用了内置函数 round(number, ndigits) 来实现小数转整数运算。

    1. Python 实验

    >>> round(1.5, 0)
    2.0
    

    看样子好像是四舍五入的取法,但是换了一个数字之后,就发现新大陆了!

    >>> round(2.5, 0)
    2.0
    

    这个运算发现round其实就是直接截取了整数部分,那么事实果真如此吗?文末会给出解答~

    2. 自定义函数

    第一反应就是官方的函数是不是有问题,于是找了几篇博客(参考的一个博客解答,但并没有完全解决)看看怎么回事,发现给出的解决方法都不能解决问题,实验如下:

    >>> from decimal import Decimal
    >>>
    >>> print(round(Decimal("1.5"),0))
    2
    >>> print(round(Decimal("2.5"),0))
    2
    >>> print(Decimal("1.5").quantize(Decimal("0.")))
    2
    >>> print(Decimal("2.5").quantize(Decimal("0.")))
    2
    

    于是一股脑的自己花了十几分钟写了一个函数来实现:

    def round_Pro(number, ndigits):
        """
        实现四舍五入
        :param number: 要四舍五入的数字
        :param ndigits: 要保留的位数
        :return: 以 float 形式输出
        """
        flag = ''       # 用于记录 number 的正负性
        if number < 0:
            flag = '-'
        number_abs = abs(number)    # 一律采用正数的四舍五入进行操作
        number_abs_str = str(number_abs)
        if '.' in number_abs_str:       # 如果给的数字是小数的话
            integer = int(number_abs_str.split('.')[0])     # 数字的整数部分
            decimal_str = number_abs_str.split('.')[1]      # 数字的小数部分
            decimal_cnt = len(decimal_str)      # 小数位数
            if ndigits >= decimal_cnt:      # 如果要保留的小数位数 >= 整个小数位数,则直接输出原数字
                return number
            else:       # 要保留的小数位数 < 整个小数位数,需要进行四舍五入
                judge_num = int(decimal_str[ndigits])   # 舍入位数字(根据此数字判断是否需要进位)
                if ndigits == 0:    # 只保留整数
                    decimal = 0     # 小数设为 0
                    if judge_num >= 5:  # 如果小数第一位 > 5,则整数进一位
                        integer += 1
                else:       # 保留指定小数位数
                    decimal = int(decimal_str[:ndigits])        # 要保留的小数(直接截断转为整数,便于后续直接计算进位)
                    decimal_cnt = len(decimal_str[:ndigits])    # 要保留的小数位数
                    if judge_num >= 5:      # 如果判断位(指定保留小数位数后一位的小数数字,比如:number=1.234,ndigits=2,那么判断位就是数字"4") > 5
                        decimal += 1        # 小数进一位
                        if len(str(decimal)) > decimal_cnt:     # 如果进位导致小数位最高位也进位了
                            integer += 1        # 整数部分也要进位
                            decimal = str(decimal)[1:]      # 小数部分去掉最高位(由于进位多出来的数据)
                decimal = str(decimal)
                return float(flag + str(integer) + '.' + decimal)
        else:       # 给的数字是一个整数,直接输出
            return number
    

    测试如下:

    print(round_Pro(2.9897, 5))
    print(round_Pro(2.4497, 1))
    print(round_Pro(2.4497, 0))
    print(round_Pro(2.5597, 0))
    print(round_Pro(2.5597, 10))
    print(round_Pro(0, 0))
    print(round_Pro(0, 10))
    print(round_Pro(-1.5, 0))
    print(round_Pro(-1.5, 1))
    print(round_Pro(-1.56, 1))
    print(round_Pro(-1.56, 2))
    print(round_Pro(-1.999, 2))
    

    输出如下:

    2.9897
    2.4
    2.0
    3.0
    2.5597
    0
    0
    -2.0
    -1.5
    -1.6
    -1.56
    -2.0
    

    写完之后发现我的逻辑其实有点复杂(先转为字符串进行分割,再分别转成数字进行计算),于是又找找看有没有其它的解决方法,因为我坚信我不可能是第一个遇到这种问题的人,于是很快发现了另一篇博客(能够解决此问题的一篇博客),完全解决了这个问题,即用到一个库:decimal,只保留整数就是 Decimal("1."),保留1位小数就是 Decimal(".1"),2位小数就是 Decimal(".01") 【Decimal(".89") 也是一样的,本质就是看你给的字符串中小数占了多少位而已】。

    >>> from decimal import Decimal
    >>> print(Decimal(1.5).quantize(Decimal("1."), rounding="ROUND_HALF_UP"))
    2
    >>> print(Decimal(2.5).quantize(Decimal("1."), rounding="ROUND_HALF_UP"))
    3
    

    3. 总结

    解决方法,使用第三方库函数,格式是:Decimal(要进行四舍五入的数字).quantize(Decimal("保留多少位小数"), rounding="ROUND_HALF_UP"):

    >>> from decimal import Decimal
    >>> num = 1.5
    >>> print(Decimal(num).quantize(Decimal("1."), rounding="ROUND_HALF_UP"))
    2
    

    那么回到文章开头提出的问题,round为什么不能进行四舍五入呢? 其实round本质就是四舍五入,但是由于我们的计算机存储数字的精度有问题,才导致了这种情况发生:

    >>> from decimal import Decimal
    >>> print(Decimal(1.535))
    1.5349999999999999200639422269887290894985198974609375
    >>> print(Decimal(1.725))
    1.725000000000000088817841970012523233890533447265625
    

    详见 python3 小数位的四舍五入(用两种方法解决round 遇5不进),文中给出了两种解决方法。

转载请注明来自码农世界,本文标题:《python四舍五入(round精度不够,有时不能实现四舍五入)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,81人围观)参与讨论

还没有评论,来说两句吧...

Top