【Python】python的精度控制
python精度问题(未完待续)
想起来以前有次做客户的收益计算(一群电力客户,给他们分配不同的电量,计算不同分配方法的收益),跟同事一起,同事提供收益计算方法,我负责将计算方法包装起来,做成web服务,然后用它来为客户服务(挣客户的钱😸)。然鹅,并没有顺利挣到。代码已经无从考究,但是原因还记得很轻粗,我们在控制变量的精度的时候,用的是python的round
,打算只保留两位小数(分配给别人的电量单位较大(万千瓦时),所以精确到两位即可,也没有客户愿意听你从电量池里边分走几十度电),然鹅精度总是在算着算着的时候,被忽略掉的后边小数就冒出来了,经过加减乘除,然后进位了!!!!影响了结果的可靠性。
这些冒出来的小数,一直在我印象里就是鬼魅般的存在!!!断人财路,印象深刻,所以这里好好整理下python的精度问题。
来个人场景,稳定复现
x = 0.0 #importantfor i in range(10):
x+=0.01
print(x)
'''
0.01
0.02
0.03
0.04
0.05
0.060000000000000005
0.07
0.08
0.09
0.09999999999999
'''
所以round~round~几下,精度就丢失了!
要理解这个,还需要提及到二进制是怎么保存小数的原理了
二进制保存小数的原理
难受劝退!
十进制是怎么表示小数的呢?
# 直观 相当于拆分成什么十分位、百分位、千分位来表示125.456 = 1*10^2+2*10^1+5*10^0+4*10^-1+5*10^-2+6*10^-3
# 指数
1.25456E2 = (1+2*10^-1+5*10^-2+4*10^-3+5*10^-4+6*10^-5)*10^2
同理看一下二进制:
# 二进制 = 十进制 不要在意为什么二进制有个小点0.1 = 1*2^-1(0.5)
0.01 = 1*2^-2(0.25)
0.001 = 1*2^-3(0.125)
0.0001 = 1*2^-4 (0.0625)
0.00001 = 1*2^-5 (0.03125)
# 其实任意一个数上边这样表示的二进制数据,我们都可以转换成十进制数,比如
10001.101 = 1*2^4+1*2^0+1*2^-1+1*2^-3
# 上边这个更容易理解为, 看看左边末尾的2^4,是不是很像我们的十进制提取出10的倍数的表示
1.0001101*2^4 = ( 1+1*2^-4+1*2^-5+1*2^-7 ) * 2^4
可以看到二进制是可以表示很小的数的,也可以是很大的数。那么大大小小的数加起来,就可以表示任意大小的十进制数了。虽然不一定完全相等,但是只要我小的数足够多,加起来,近似还是可以的。
所以得出结论:任意一个十进制的数值都可以表示成或者近似表示成(1+12^-n+...12^-m)*2^k
<font color="red">有没有一种拉格朗日表达式的味道</font>
这也解释了为什么二进制是不能精确表示1/10,因为无论加多少阶,1/10都不能被上述样式精确表示出来。有些时候也会出现能够除尽的算式,计算机中却不能除尽。
为什么说双精度浮点数有15位十进制精度,是不是这个原因
经典例子:
#!/bin/pythona = 3240.0
b = 8.0
d = (a*(b/100))/(1+(b/100))
print(d)
'''
239.99999999999997
'''
- float有4个字节32为,首位表示符号,接下来8位表示阶数K,剩下23表示二进制的小数部分;
- double有8个字节,64位,首位表示符号,11位表示阶数k,剩下表示小数部分;(python默认的小数类型)
回到python的精度问题
1/10 用2的指数来表示,确实是表示不完的,没有一个二进制整数倍表达式,所以会有开篇引用的例子里,出现很多个二进制数。然后回到round函数,(0舍1入的规则),是不是感觉到了一丝丝坑意。
在python中,一个小数,是可以看到他的二进制表示的,错了,不是二进制,而是16进制,不过差不多
a = 1.2a.hex()
"""
0x1.3333333333333p+0
"""
b = 0.00000041
b.hex()
"""
0x1.b83bf11ce33aap-22
"""
# 0x 开头代表 后边的数据,是一个16进制数
# p-4表示 * 2^ -4,或者可以简单理解为小数点 左移 4 位, (左移变小, 注意这里还是16进制,不是二进制)
而为了回应人们在某些状况下对这个精度问题难以忍受的心情,Python 提供了另一种数字类型——Decimal 。他并不是内建的,因此使用它的时候需要 import decimal 模块,并使用 decimal.Decimal() 来存储精确的数字。这里需要注意的是:使用非整数参数时要记得传入一个字符串而不是浮点数,否则在作为参数的时候,这个值可能就已经是不精确的了
然后为了更直观地表现,人们又开始用无限小数的形式表示有理数(分数)。而其中从某一位开始后面全是 0 的特殊情况,被称为有限小数(没错,无限小数才是本体)。但因为很多时候我们并不需要无限长的小数位,我们会将有理数保存到某一位小数便截止了。后面多余小数的舍入方式便是“四舍五入”,这种方式较直接截断(round_floor)的误差更小。在二进制中,它表现为“0 舍 1 入”。当我们舍入到某一位以后,我们就可以说该数精确到了那一位。如果仔细体会每一位数字的含义就会发现,在以求得有限小数位下尽可能精确的值为目的情况下,直接截断的舍入方式其实毫无意义,得到的那最后一位小数也并不精确。例如,将 0.06 舍入成 0.1 是精确到小数点后一位,而把它舍入成 0.0 就不算。因此,不论是在双精度浮点数保留 52 位有效数字的时候,还是从双精度浮点数转换回十进制小数并保留若干位有效数字的时候,<font color="red">对于最后一位有效数字,都是需要舍入的</font>。
这就是为什么最后经常变得原因,想想0的最终存储时的值是0.00000000000000000001这种情况,是不是一下就直观了很多。
下图是一个(0,1)之间的数轴,上面用二进制分割,下面用十进制分割。比如二进制的 0.1011 这个数,从小数点后一位一位的来看每个数字的意义:开头的 1 代表真值位于 0.1 的右侧,接下来的 0 代表真值位于 0.11 的左侧,再接下来的 1 代表真值位于 0.101 的右侧,最后的 1 代表真值位于 0.1011 的右侧(包含正好落在 0.1011 上这种情况)。使用 4 位二进制小数表示的 16 个不同的值,除去 0,剩下的 15 个数字正好可以平均分布在(0,1)这个区间上,而十进制只能平均分布 9 个数字。显然 4 位二进制小数较于 1 位十进制小数将此区间划分的更细,即精度更高
以上是 【Python】python的精度控制 的全部内容, 来源链接: utcz.com/a/94896.html