用过Python这门编程语言的应该都会发现,当我们输入0.1+0.2时,打印出来的却不是0.3,而是0.000004。
那么这是为什么呢?其实不仅是Python,很多编程语言例如C、Java、JavaScript等等都有这种特性
Java:
原理很简单,所有数的运算在计算机中都是进行二进制计算的,十进制小数也是一样,十进制小数在进行运算时首先将十进制数转换成二进制数之后再进行运算,运算结果又转换成十进制并显示出来。
所有浮点数在进行运算时都需要遵循IEEE 754
规范,IEEE 754
浮点数表示方法规定浮点数的两种转化方式:
float
):1
位符号位+8
位指数位+23
位尾数位double
):1
位符号位+11
位符号位+52
位尾数位其中符号位用来表示正负,指数位限制数的范围,尾数位限制数的精度。这也就是说,当采用float
类型表示浮点数时,能表示二进制的范围是+(8位指数).(23位小数)~-(8位指数).(23位小数),所以在把十进制小数0.1转化成二进制小数时得到的是一个超出能表示二进制小数最大尾数的小数并且是无限小数,但因为表示的范围有限,仅保留前23位小数,也就导致了精度缺失。不过在一些编程语言中,为了更好的表示十进制小数,默认采用的是将十进制小数保留16位。
十进制小数转换成二进制的方法,如果需要把十进制小数转换成二进制,一般的方法是先将整数转化成二进制数得到二进制小数的整数部分,再将十进制小数的小数部分乘以2取整数部分直至尾数为0,得到的整数顺序排列即为该十进制小数的二进制小数部分。例如十进制数0.5=0.+floor(0.5*2)=二进制数0.1
由于bin
函数不能将十进制小数转换成二进制,所以我在Python中根据十进制小数转换二进制规则来设计一个函数dec_to_binf
,函数非常简单
from math import floor def dec_to_binf(dec_num, precision): result = "0." for i in range(precision): # 取整数部分 integer = floor(dec_num * 2) # 取小数部分,为了防止运算过程中精度再次缺失陷入死循环,用round保留12位小数,避免像0.000004的情况出现 dec_num = round(dec_num, 12) * 2 - integer result = result + str(integer) return result
因为在二进制数中有尾数精度缺失,不能直接表示成二进制数,所以我让它们最后都返回字符串类型。传入参数dec_num
为1以内的十进制小数,precision
为保留的二进制精确度。
一切准备就绪,让我们回到主题,当0.1+0.2时计算机内部情况到底是怎样的。
程序如下:
所有二进制数均保留80位小数,转化后结果为
十进制数0.1=二进制数0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001
十进制数0.2=二进制数0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
可以发现它们是一个无限循环小数,都超过了double
类型所能表示的最大尾数位数52位,后面我更据IEEE 754规范将它们保留52位小数(遵循逢1进1,遇0舍弃)
利用二进制数的加法运算可以算出 十进制数0.1 + 十进制数0.2 = 二进制数0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101
再设计一个函数将二进制小数转换成十进制数,函数也很简单,原理是根据各位上的权来计算十进制数,比如二进制数0.1011=(1*2^-1)+(0*2^-2)+(1*2^-3)+(1*2^-4)
。函数如下:
def binf_to_dec(binf): sum_dec = 0 # 仅计算0.后面尾数,偏移量为2 for i in range(len(binf)-2): # 根据位上的权和系数来计算每一位的结果 sum_dec = sum_dec + float(binf[i+2]) * 2**-(i+1) return sum_dec
传入参数binf是一个字符型二进制数,将前面计算的和传入运行如下:
可以看到结果和直接计算0.1+0.2的结果一致。而真正出现问题的步骤在将0.1和0.2的二进制循环小数保留52位尾数,这里出现了精度丢失。那么我们可以得出结论了,0.1+0.2≠0.3其实是由于进制转换过程中出现了精度丢失。
无论在何种计算机中,都遵循IEEE 754标准,都会出现这种浮点数运算精度丢失的情况,而最好的解决办法就是避免使用浮点数来进行运算,尤其是对精度要求及其严格的军工、航天等领域。最后欢迎来访我的个人网站https://angie.js.cn/
文章来自https://www.angie.js.cn/index.php/archives/43.html