美文网首页
Python进阶之路(三)之float奇舍偶入?五成双?

Python进阶之路(三)之float奇舍偶入?五成双?

作者: EchoPython | 来源:发表于2019-07-08 19:06 被阅读0次

    引言,

    默认float 类型储存双精度(double) 浮点数,可表达16到17个小数点.

    从实现方式看,浮点数以二进制储存十进制的近似值.这可能导致执行结果和编码的预期效果不符合,造成一定量的缺陷,所以对精度有严格要求的场合,应该选择固定精度类型.

    1.关于精度问题


    一般可以通过float.hex 方法输入实际储存值的十六进制格式字符串,以检查执行结果为什么不同.
    还可以使用该方式实现浮点值的精确传递,避免精度丢失


    • bin() 转二进制
    • int() 转10进制
    • oct() 转8进制
    • hex() 转16进制
    In [1]: 0.1 * 3                                                                                                                                                                              
    Out[1]: 0.30000000000000004
    
    In [2]: (0.1 * 3).hex()       # 显然两个储存的不同                                                                                                                                                                 
    Out[2]: '0x1.3333333333334p-2'
    
    In [3]: (0.3).hex()        #       转换为16进制                                                                                                                                                             
    Out[3]: '0x1.3333333333333p-2'
    
    
    In [4]: s = (1/3).hex()                                                                                                                                                                      
    
    In [5]: s                                                                                                                                                                                    
    Out[5]: '0x1.5555555555555p-2'  
    
    In [6]: float.fromhex(s)        # 返回浮点数                                                                                                                                                            
    Out[6]: 0.3333333333333333
     
    

    对于简单的比较操作,可以尝试将浮点数限制在有效的固定精度内,但是考虑到round算法实现问题,更准确的做法是使用decimal.Decimal类型

    In [13]: round(0.1 * 3, 2) == round(0.3, 2)        # 避免不确定行,左右都是使用了固定精度                                                                                                                                           
    Out[13]: True
    
    In [14]: round(0.1, 2) * 3 == round(0.3, 2)        # 将round 返回值作为操作数,导致精度再度丢失                                                                                                                                          
    Out[14]: False
    

    不同类型的数字之间,可以直接进行加减和比较运算的.

    In [15]: 1.1 + 2                                                                                                                                                                             
    Out[15]: 3.1
    
    In [16]: 1.1 < 2                                                                                                                                                                             
    Out[16]: True
    
    In [17]: 1.1 == 1.100                                                                                                                                                                        
    Out[17]: True
    

    2.转换

    将整数或者字符串转换为浮点数很简单,且能自动处理字符串内的符号以及空白符问题,只超过有效精确度的时候,结果和字符串内容存在差异.

    In [18]: float(100)                # 正常                                                                                                                                                          
    Out[18]: 100.0
    
    In [19]: float("-100.123")           #符号                                                                                                                                                        
    Out[19]: -100.123
    
    In [20]: float(" \t   100.213123 \n")      # 空白符                                                                                                                                                  
    Out[20]: 100.213123
    
    In [21]: float("1.1234e2")           # 科学计算法                                                                                                                                                        
    Out[21]: 112.34
    

    差异部分

    In [22]: float("0.1234567890123456789")                                                                                                                                                      
    Out[22]: 0.12345678901234568               # 显示不完全
    

    返回来,将浮点数转换为整数的时候,有多重不同的方案可以供我们选择.可以直接减掉小数部分,或者分别往大小两个方向取整数.

    In [23]: int (2.6)                                                                                                                                                                           
    Out[23]: 2
    
    In [24]: from math import trunc,floor,ceil                                                                                                                                                   
    
    In [25]: trunc(3.2),trunc(-3.2)            # 截断小数部分                                                                                                                                                  
    Out[25]: (3, -3)
    
    In [26]: floor(3.2),floor(-3.2)             # 向小数方向取最近整数                                                                                                                                           
    Out[26]: (3, -4)
    
    In [27]: ceil(3.2),ceil(-3.2)                # 往大数字方向取整数                                                                                                                                                
    Out[27]: (4, -3)
    
    在这里插入图片描述

    3.十进制浮点数

    与float 基于硬件的二进制浮点数类型相比,decimal.Decimal 是十进制实现,最高可以提供28位有效精度,其准确表达十进制数和运算,不存在二进制近似值的问题.

    In [1]: 1.1+2.2                                                                                                                                                                              
    Out[1]: 3.3000000000000003          # 结果只是与3.3相似
    
    In [2]: (0.1 + 0.1 + 0.1 - 0.3) == 0                   # 与预期结果不符                                                                                                                                      
    Out[2]: False
    
    In [3]: from decimal import Decimal        
                                                                                                                                                      
    # 使用Decimal之后
    In [4]: Decimal("1.1") + Decimal("2.2")                                                                                                                                                      
    Out[4]: Decimal('3.3')
    
    In [5]: Decimal("0.1") + Decimal("0.1") + Decimal("0.1")  - Decimal("0.3")  == 0                                                                                                             
    Out[5]: True
    

    在创建Decimal 实例时,应该传入一个准确数值,比如整数或者字符串等,如果是float类型的话,那么再构建之前,其中精度就已经丢失了.

    In [6]: Decimal(0.1)                                                                                                                                                                         
    Out[6]: Decimal('0.1000000000000000055511151231257827021181583404541015625') # 精度已经丢失
    
    In [7]: Decimal("0.1")               # 只有在传入字符串或者整数的时候,精度才不会丢失.                                                                                                                                                        
    Out[7]: Decimal('0.1')
    
    

    需要的时候,可以通过上下文修改Decimal默认的28位精度

    In [8]: from decimal import Decimal,getcontext                                                                                                                                               
    
    In [9]: getcontext()                                                                                                                                                                         
    Out[9]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[FloatOperation], traps=[InvalidOperation, DivisionByZero, Overflow])
    
    In [10]: getcontext().prec = 2                                                                                                                                                               
    
    In [11]: Decimal(1) / Decimal(7)                                                                                                                                                             
    Out[11]: Decimal('0.14')
    
    In [12]: Decimal(1) / Decimal(3)                                                                                                                                                             
    Out[12]: Decimal('0.33')
    

    更高阶一点的 用localcontext 限制一定的区域中的精度

                                                                                                                                                                                                 
    In [14]: from decimal import localcontext  
    In [15]: with localcontext() as ctx: 
        ...:     ctx.prec = 2 
        ...:     print(getcontext().prec) 
        ...:     print(Decimal(1) / Decimal(7)) 
        ...:                                                                                                                                                                                     
    2
    0.14
    

    除非有特别的需求,不然不要使用Decimal代替float,要知道其运算速度也会慢很多


    4.奇舍偶入(并不是)

    同样因为近似值和精度问题,造成float运行'四舍五入' (round) 的时候操作存在不确定性,其结果会导致一些不易察觉的陷阱

    In [18]: round(0.3)                                                                                                                                                                          
    Out[18]: 0
    
    In [19]: round(0.5)         # 这里应该是1才是正确的                                                                                                                                                                 
    Out[19]: 0
    
    In [20]: round(1.5)                                                                                                                                                                          
    Out[20]: 2
    
    

    按照round算法规则,按照林近数字距离远近来考虑是否进位,因此,四舍六入就是确定的,相关问题都一种在两边都是5的时候是否进位


    按照以0.4为例子,其舍入后的相邻数字是0和1,从距离上看0自然是距离0.4更近一些
    对于5 还要考虑后面是否还有小数位,如果有,那么左右距离就不可能是相等的,这自然需要进位


    In [21]: round(0.5)            # 与0,1距离相等,暂时不确定                                                                                                                                                              
    Out[21]: 0
    
    In [22]: round(0.5000001)        # 哪怕是0.5后面的小数部分再小,那么他也是接近1的                                                                                                                                                         
    Out[22]: 1
    
    In [23]: round(1.25,1)                                                                                                                                                                       
    Out[23]: 1.2
    
    In [24]: round(1.2500000001,1)                                                                                                                                                               
    Out[24]: 1.3
    

    剩下的,要看返回整数还是浮点数.如果是整数,就去邻近的偶数.

    In [25]: round(0.5)                 #  0 --> 0.5 --> 1                                                                                                                                                         
    Out[25]: 0
    
    In [26]: round(1.5)                 #  1 --> 1.5 --> 2                                                                                                                                                         
    Out[26]: 2
    
    In [27]: round(2.5)                #  2 --> 2.5 --> 3                                                                                                                                                            
    Out[27]: 2
    

    不同版本,规则存在差异,比如2.7中,round(2.5)返回值是3.0
    从这点来看,我们应该谨慎对待此类行为差异,并且严格测试其造成的影响


    如果依旧返回浮点数,事情就变得有点莫名其妙了,有些文章宣城 "奇舍偶入",或者"五成双",也就是看5前一位小数的奇偶性来判断是否进位,但是事情并非如此

    In [28]: round(1.25,1)         # 偶舍                                                                                                                                                              
    Out[28]: 1.2
    
    In [29]: round(1.245,2)        # 偶入                                                                                                                                                              
    Out[29]: 1.25
    
    In [30]: round(2.675,2)         # 奇 舍                                                                                                                                                             
    Out[30]: 2.67
    
    In [31]: round(2.375,2)         # 奇 入                                                                                                                                                          
    Out[31]: 2.38
    

    对此官方文档宣城这并不是错误,而是属于事出有因,对此我们可以改用Decimal,按照需求选取可控的进位方案.

    In [35]: from decimal import Decimal,ROUND_HALF_UP                                                                                                                                           
    
    In [35]: def roundx(x,n): 
        ...:     return Decimal(x).quantize(Decimal(n), ROUND_HALF_UP)  # 严格按照四舍五入进行
    
    In [36]: roundx("1.24",".1")                                                                                                                                                                 
    Out[36]: Decimal('1.2')
    
    In [37]: roundx("1.26",".1")                                                                                                                                                                 
    Out[37]: Decimal('1.3')
    
    In [38]: roundx("1.245",".1")                                                                                                                                                                
    Out[38]: Decimal('1.2')
    
    In [39]: roundx("1.675",".1")                                                                                                                                                                
    Out[39]: Decimal('1.7')
    
    In [40]: roundx("1.375",".1")                                                                                                                                                                
    Out[40]: Decimal('1.4')
    
    
    

    最后嘛 :

    小编整理的python程序员资料裙echo'(895797751,欢迎自取)'
    

    相关文章

      网友评论

          本文标题:Python进阶之路(三)之float奇舍偶入?五成双?

          本文链接:https://www.haomeiwen.com/subject/drnahctx.html