下面代码是将 0.1 累加 100 次,然后将结果输出到显示器上的 JavaScript语言程序。
首先把 0 赋值给变量i,然后在此基础上累加 100 次 0.1。
i+ = 0.1; 表示为现在的i值加 0.1。
for(k= 0;k< 100;k++){...} 表示将 {} 内包含的处理重复 100 次。
最后将累加 100 次 0.1 后的变量i的值输出到显示器上。
心算一下就能知道,0.1 累加 100 次后的结果是 10。但是,代码经过计算机程序运行后,显示器上显示的结果并不是 10。
程序没错,计算机也没有发生故障,当然,JavaScript 语言(包括其他编程语言)也没有什么问题。可为什么会出现这样的结果呢?
这时,如果考虑一下计算机处理小数的机制,就讲得通了。那么,计算机内部是如何处理小数的呢?
由于计算机内部所有的信息都是以二进制数的形式来处理的,因此在这一点上,整数和小数并无差别。不过,使用二进制数来表示整数和小数的方法却有很大的不同。
要知道计算机为何在计算时会出错,要先了解下计算机处理数据的过程,也就是cup如何进行数据运算的。下面通过二进制数转十进制数的例子简单感受下数字的转换过程:
把 1011.0011 这个有小数点的二进制数转换成十进制数。
小数点前面部分的转换只需将各数位数值和位权相乘,然后再将相乘的结果相加即可实现。那么,小数点后面的部分要如何进行转换呢?其实,它的处理和整数是一样的,将各数位的数值和位权相乘的结果相加即可。
下图就是将二进制数1011.0011转换为10进制数的过程,最后转换的结果为11.1875
二进制数小数点前面部分的位权,第 1 位是 2 的 0 次幂、第 2 位是 2 的 1 次幂 …… 以此类推。小数点后面部分的位权,第 1 位是 2 的-1 次幂、第 2 位是 2 的-2 次幂,以此类推。0 次幂前面的位的位权按照 1 次幂、2 次幂……的方式递增,0 次幂以后的位的位权按照-1 次幂、-2 次幂……的方式递减。
这一规律并不仅限于二进制数,在十进制数和十六进制数中也同样适用。既然二进制数的小数点后第 3 位是 2 的-3 次幂(0.125),第 4 位是 2 的-4 次幂(0.0625),那么小数点以后的 .0011 转换成十进制数就应该是 0.125+0.0625 = 0.1875。此外,由于整数部分的 1011 转换成十进制数是 11。因此,二进制数 1011.0011 转换成十进制数就是 11 + 0.1875 = 11.1875。
计算机运算出错的原因
计算机之所以会出现运算错误,是因为“有一些十进制数的小数无法转换成二进制数”。
例如,十进制数 0.1,就无法用二进制数正确表示,小数点后面即使有几百位也无法表示。
小数点后 4 位用二进制数表示时的数值范围为 0.0000~0.1111(因为二进制数只有0和1组成 )。因此,这里只能表示 0.5、0.25、0.125、0.0625 这四个二进制数小数点后面的位权组合而成(相加总和)的小数。将这些数值组合后能够表示的数值,即为下图中所示的无序的十进制数。
也就是说二进制数小数点后 4 位能够用二进制数表示的数值二进制数是连续的,十进制数是非连贯的。
十进制数 0 的下一位是 0.0625。因此,这中间的小数,就无法用小数点后 4 位数的二进制数来表示。
同样,0.0625 的下一位数一下子变成了 0.125。这时,如果增加二进制数小数点后面的位数,与其相对应的十进制数的个数也会增加,但不管增加多少位,2 的-x次幂怎么相加都无法得到 0.1 这个结果。
实际上,十进制数 0.1 转换成二进制后,会变成 0.00011001100…(1100 循环)这样的循环小数。这和无法用十进制数来表示 1/3 是一样的道理。1/3 就是 0.3333…,同样是循环小数。
因为无法正确表示的数值,最后都变成了近似值。计算机这个功能有限的机器设备,是无法处理无限循环的小数的。
因此,在遇到循环小数时,计算机就会根据变量数据类型所对应的长度将数值从中间截断或者四舍五入。我们知道,将 0.3333…这样的循环小数从中间截断会变成 0.333333,这时它的3倍是无法得出 1 的(结果是 0.999999),计算机运算出错的原因也是同样的道理。
遇到这样小数结果精度损失的问题,也是有解决方法的,下面提供两种方法:
解决方法一
使用文章中的方法,对0.1相加100次:
该算法的原理是先把两个数字的小数位数的最大长度取到,然后把每个数字都扩大10的n次方倍,这时候原来的小数都转为整数,再将两个整数相加,最后再将整数结果除以10的n次幂,得到最终结果。
解决方法二
还有一种处理方法就是利用编程语言提供的对数字四舍五入的相关方法进行处理: