引言,

默认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)

python floatS python float双精度_字符串

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')