JS番外:解决JS浮点数精度问题
经谢师兄启发,最近在进行JS浮点数计算精度的问题,算是有了些进展,下面是我的笔记。(Markdown编辑),代码看着难受可以直接上我这个网址上看:
http://note.youdao.com/noteshare?id=db00952021368923eb3e6aa237fbec09
来首音乐呗
正文开始!
问题来源
关于这个问题,最常见,大家讨论的最火的一个热点是0.1+0.2的问题,运行代码就能发现猫腻。
var num1=0.1;var num2=0.2;var res=0.1+0.2;
alert(res); // 0.30000000004
问题原因
答案来源于百度,链接js上0.1+0.2为什么不等于0.3(我的链接因为权限原因暂时起不了作用)
下面的内容我进行精简和修改后,大家好好看。
js中的数字都是用浮点数表示的,并规定使用IEEE 754 标准的双精度浮点数表示。IEEE双精度格式具有53 位有效数字精度(包含符号号),并总共占用64 位。
整数的二进制转化相信大家已经颇为熟悉,其实就是让十进制数不停地除以2,不停地记录余数可得,而小数的转化稍微展开,举一个例子说说:
235.725的小数部分取出,即:0.725,将其乘以进制数二进制就乘以2后得到1。45,取其整数部分1为二进制小数的第一项(十分位),在将小数部分0。45乘2得0。9,取其整数部分为二进制小数的第二位(百分位)0,在将其小数部分0。9乘2,得1。8,取其整数部分为二进制小数的第三位(千分位)1,取其小数部分0。8再乘2……以此类推,直到值为0或形成循环小数则停止。
看完这个例子,继续看这个问题的分析:
根据计算,0.1转化为二进制是一个无限小数,0.00011001100110011…(循环0011),算上符号号,就只有53位有效数字,所以只能够存储52个数字,即0.00011001100110011001100110011001100110011001100110011001。
0.2转化为二进制也是一个无限小数,0.0011001100110011…(循环0011),同样存储52个数字的结果是0.00110011001100110011001100110011001100110011001100110011。
两者求和是0.01001100110011001100110011001100110011001100110011001100,化回十进制就是0.30000000000000004。
所以核心问题就是JS转化数字为二进制计算时用的不是实际数而是近似数,所以会带来近似问题。
解决方案
该方法的核心思路是在计算的时候尽可能避免用浮点数计算。一般而言有两个解决思路,一个是将整数部分和小数部分分开,整数部分直接按照整数计算,小数部分也计算,经过整理后在此整合成完整的数,另一个是两个数同时乘一个系数使之都变为整数之后进行计算,然后再除以该系数。
思路1
整数部分和小数部分分别计算
// 执行部分
var num1=0.01;
var num2=0.2;
alert(num1+num2);
alert(Add(num1,num2)); // 将小数分段
function splitdecimal(num){ // 0:整数部分,1:小数部分,2:小数位数
var numsplit=num.toString().split(".");
numsplit[numsplit.length]=numsplit[numsplit.length-1].length; return numsplit;
} // 执行加法
function Add(num_1,num_2){
var num1=splitdecimal(num_1);
var num2=splitdecimal(num_2);
var res=[];// 结果0:整数部分,1:小数部分,2:小数位数
var zero=""; // 小数点后0的个数
// 非零部分转为浮点数
num1[0]=parseFloat(num1[0]);
num1[1]=parseFloat(num1[1]);
num1[2]=parseFloat(num1[2]);
num2[0]=parseFloat(num2[0]);
num2[1]=parseFloat(num2[1]);
num2[2]=parseFloat(num2[2]); //整数部分求和
res[0]=num1[0]+num2[0]; // 非零小数部分求和
var temp=Math.abs(num2[2]-num1[2]); if(num1[2]<num2[2]){
num1[1]=num1[1]*Math.pow(10,temp);
}else{
num2[1]=num2[1]*Math.pow(10,temp);
}
res[1]=num1[1]+num2[1]; // 是否有进位
res[2]=Math.max(num2[2],num1[2]); if(res[1]>res[2]){
var temp=res[1]%Math.pow(10,res[2]);
res[0]+=Math.floor(res[1]/Math.pow(10,res[2]));
res[1]=temp;
}
// 小数部分是否为0
if(res[1]==NaN || res[1]==0){
res[1]="";
}else{ // 移动小数点
var i=1;
var temp=res[1]; while(temp<Math.pow(10,res[2]-1)){
zero+="0";
temp*=10;
}
res[1]="."+zero+res[1].toString();
}
result=res[0].toString()+res[1]; return result;
}
思路2
乘以特定系数都变为整数再进行计算,计算后除回去
// 执行部分 var num1=1.01; var num2=0.2;
alert(num1+num2);
alert(Add(num1,num2));
// 加函数
function Add(num1,num2){
// beta:小数位数,k:系数 var num1float=num1; var num2float=num2; var num1beta=numbeta(num1float); var num2beta=numbeta(num2float); var beta=Math.max(num1beta,num2beta); var result=0; var k=Math.pow(10,beta);
// alert(num1float*beta); result=(num1float*k+num2float*k)/k; return result;
}
// 求小数位数
function numbeta(num1){ var res=num1.toString().split(".")[1].length; return res;
}
我只写了加法,大家可以尝试其他运算,另外github上有一个项目就是专门解决这个问题的,写的厉害的不行,Big.js,大家可以去了解一下!
在结束之前给大家推荐一个公众号,这是我一个高中同学最近刚开的公众号,他刚毕业,签到思特沃克中国工作,热爱分享,和大家分享!这个公众号主要发布一写成熟的技术或者是新技术的文章和思考,有时也会说说程序员的心里话,大家可以关注一下~鼓掌
没完!当然还有我的,扫下面的二维码,关注我!谢谢大家!
QQ:545281848