共计 2497 个字符,预计需要花费 7 分钟才能阅读完成。
一、问题的引入
今天在看基础 js 文章的时候发现了一个浮点数的精度问题,当打印小数相加的时候有时候会出现数值不准确的情况,如果是在做一些需要数据精度要求较高的工作的时候稍有不慎就会出现问题
console.log(0.1+0.1) //0.2
console.log(0.1+0.2) //0.30000000000000004(精度最高保留到 17 位)
查阅资料之后,发现是因为像 0.1+0.2 这样的操作对于计算机来说转换为二进制之后将是两个无限循环的数。而对于计算机而言是不允许有无限的,进行四舍五入之后双精度浮点数保留 52 位,结果为 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 转为十进制就是 0.30000000000000004
二、基本解决方法
【1】利用 toFixed(digits) toFixed 函数在 digits 参数存在的情况下返回一个所给数值的定点数表示法的字符串(digits 要在 0 -20 之间,默认为 0)。我们可以利用该函数限定返回数值的位数,从而达到提高精度的效果。
var floatNum=0.1+0.2;
console.log(floatNum); //0.30000000000000004
console.log(floatNum.toFixed(2)) //0.30
console.log(1.35.toFixed(1)) //1.4
console.log(1.33335.toFixed(4)); //1.3334
在这里我们看上去是解决了问题,但是和直接进行相加一样有时候 toFixed 也会有问题
Chrome:
console.log(1.55.toFixed(1));// 1.6
console.log(1.555.toFixed(2));//1.55
console.log(1.5555.toFixed(3));//1.556
关于这个问题,MDN 上函数的描述为“该数值在必要时进行四舍五入,另外在必要时会用 0 来填充小数部分,以便小数部分有指定的位数。”至于是怎么定义“必要”的,不同的浏览器会有不同的情况。
IE:
console.log(1.55.toFixed(1)); //1.6
console.log(1.555.toFixed(2)); //1.56
console.log(1.5555.toFixed(3)); //1.556
【2】重写 toFixed 函数
思路为放大原有的数据,利用整数的整除来避免精度丢失
function toFixed(num, s) {
if(typeof s===’undefined’){
return parseInt(num)+”;
}
if(typeof s!==’number’){
return ‘ 请正确输入保留位数(数字)’;
}
var times = Math.pow(10, s);
var newNum = num * times+0.5;// 加 0.5 是为了实现四舍五入中 ” 入 ” 的那 0.5
newNum = parseInt(newNum) / times;
return newNum + ”//toFixed 返回的是字符类型的数据
}
console.log(toFixed(1.5)) //2
console.log(toFixed(1.55,1)) //1.6
console.log(toFixed(1.555,2)) //1.56
console.log(toFixed(1.5555,3)) //1.556
【3】基础运算自实现
function getDecimalLength(num){// 获取小数位长度
let length=0;
try{
length =String(num).split(‘.’)[1].length
}catch(e){
//TODO handle the exception
}
return length;
}
function getBeishu(num1,num2){// 获取放大倍数
let num1DecimalLength=getDecimalLength(num1);
let num2DecimalLength=getDecimalLength(num2);
let longer=Math.max(num1DecimalLength,num2DecimalLength);
return Math.pow(10,longer);
}
// 加减乘除算法
function add(num1,num2){
let beishu=getBeishu(num1,num2);
return (num1*beishu+num2*beishu)/beishu;
}
function sub(num1,num2){
let beishu=getBeishu(num1,num);
return (num1*beishu-num2*beishu)/beishu;
}
function mul(num1,num2){
let num1DecLen=getDecimalLength(num1);
let num2DecLen=getDecimalLength(num2);
let num1toStr=String(num1);
let num2toStr=String(num2);
return Number(num1toStr.replace(‘.’,”))*Number(num2toStr.replace(‘.’,”))/Math.pow(10,num1DecLen+num2DecLen)
}
function dev(num1,num2){
let num1DecLen=getDecimalLength(num1);
let num2DecLen=getDecimalLength(num2);
let num1toStr=String(num1);
let num2toStr=String(num2);
return Number(num1toStr.replace(‘.’,”))/Number(num2toStr.replace(‘.’,”))/Math.pow(10,num1DecLen-num2DecLen)
}