1. 让人迷惑的现象
让我们来做个简单的实验——把下面的代码转贴到地址栏中并按确定键:
javascript:alert(0.1 + 0.2)
结果返回: 0.30000000000000004, 的确如此,你可能会感动迷惑,为什么不是0.3? 为什么js不能像其他语言(如c或java)一样printf出正确的结果呢? 这是不是js的bug呢? 其实这些疑问在当时也迷惑着我。其实我们随意搜索下google就能知道答案,这是由于十进制到二进制的转换导致的精度问题!因为计算机执行的是二进制算术,当一个十进制数不能准确的转化着二进制数时,这种精度误差就无法避免。如果对这简单的原因描述还是迷惑的话,那就接着看文章后面的具体描述吧!
2. 认识javascript的浮点运算
学过js的同学都知道,js中的数字都是用浮点数表示的,并规定使用IEEE 754 标准的双精度浮点数表示。
2.1 IEEE 754 标准的浮点数简介(呵呵,这可是大学里学习的组成原因知识噢)
IEEE 754 规定了两种基本浮点格式:单精度和双精度。
IEEE单精度格式具有24 位有效数字精度(包含符号号),并总共占用32 位。
IEEE双精度格式具有53 位有效数字精度(包含符号号),并总共占用64 位。
说明: 基本浮点格式是固定格式,相对应的十进制有效数字分别为7位和17位。基本浮点格式对应的C/C++类型为float和double。
2.2 实验过程推导
有了以上的理论知识,我们可以模拟计算机对之前实验的运算做个简单的过程展示:
十进制0.1 => 二进制0.00011001100110011…(循环0011) =>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0 => 计算机存储为:0 00000000100 10011001100110011…11001 => 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001 而十进制0.2 => 二进制0.0011001100110011…(循环0011) =>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0 => 存储为:0 00000000011 10011001100110011…11001 因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011 那么两者相加得: 0.00011001100110011001100110011001100110011001100110011001 + 0.00110011001100110011001100110011001100110011001100110011 (确认??) = 0.01001100110011001100110011001100110011001100110011001100 转换成10进制之后得到:0.30000000000000004
3.解决方法
<script> //加法函数,用来得到精确的加法结果 //说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 //调用:accAdd(arg1,arg2) //返回值:arg1加上arg2的精确结果 function accAdd(arg1,arg2){ var r1,r2,m; try{r1=arg1.toString().split(".")[1].length;}catch(e){r1=0;} try{r2=arg2.toString().split(".")[1].length;}catch(e){r2=0;} m=Math.pow(10,Math.max(r1,r2)); return (arg1*m+arg2*m)/m; } //减法函数减数即为加数加上数的负数 function accSub(arg1,-arg2){ var r1,r2,m; try{r1=arg1.toString().split(".")[1].length;}catch(e){r1=0;} try{r2=arg2.toString().split(".")[1].length;}catch(e){r2=0;} m=Math.pow(10,Math.max(r1,r2)); return (arg1*m+arg2*m)/m; } //乘法函数,用来得到精确的乘法结果 //说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 //调用:accMul(arg1,arg2) //返回值:arg1乘以arg2的精确结果 function accMul(arg1,arg2) { var m=0,s1=arg1.toString(),s2=arg2.toString(); try{m+=s1.split(".")[1].length;}catch(e){} try{m+=s2.split(".")[1].length;}catch(e){} return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m); } //除法函数,用来得到精确的除法结果 //说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 //调用:accDiv(arg1,arg2) //返回值:arg1除以arg2的精确结果 function accDiv(arg1,arg2){ var t1=0,t2=0,r1,r2; try{t1=arg1.toString().split(".")[1].length;}catch(e){} try{t2=arg2.toString().split(".")[1].length;}catch(e){} with(Math){ r1=Number(arg1.toString().replace(".","")); r2=Number(arg2.toString().replace(".","")); return (r1/r2)*pow(10,t2-t1); } } </script>