js浮点数计算精度缺失解决方法

一、js浮点数计算精度丢失的一些例子

     0.1+0.2 = 0.30000000000000004 

     0.3 - 0.2 = 0.09999999999999998

     10.22*100 = 1022.0000000000001

     2.4/0.8 = 2.9999999999999996

     32.2*100 = 3220.0000000000005

     (32.2*100 + 3.14*100) / 100 = 35.34 (很奇怪!!!不该是很多位小数吗???)

     32.2*1000 = 32200.000000000004

    32.3*100 = 3229.9999999999995

    32.3*1000 = 32299.999999999996

    ……

二、浮点数计算精度丢失的原因

js采用64位浮点数表示法(几乎所有现代编程语言所采用),这是一种二进制表示法。二进制浮点数表示法并不能精确表示类似0.1这样简单的数字。

这个问题不只在js中才会出现,在任何使用二进制浮点数的编程语言中都会出现。

JavaScript的未来版本或许会支持十进制数字类型以避免精度丢失的问题。

三、解决办法

方法一:decimal.js (推荐)

decimal.js

//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round
/**
 * Decimal adjustment of a number.
 *
 * @param {String}  type  The type of adjustment.
 * @param {Number}  value The number.
 * @param {Integer} exp   The exponent (the 10 logarithm of the adjustment base).
 * @returns {Number}      The adjusted value.
 */
export default {
    round: (value, exp) => {
        return decimalAdjust('round', value, exp);
    },
    floor: (value, exp) => {
        return decimalAdjust('floor', value, exp);
    },
    ceil: (value, exp) => {
        return decimalAdjust('ceil', value, exp);
    }
};
function decimalAdjust(type, value, exp) {
    // If the exp is undefined or zero...
    if (typeof exp === 'undefined' || +exp === 0) {
        return Math[type](value);
    }
    value = +value;
    exp = +exp;
    // If the value is not a number or the exp is not an integer...
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
        return NaN;
    }
    // Shift
    value = value.toString().split('e');
    value = Math[type](+(value[0] + 'e' + (value[1] ? +value[1] - exp : -exp)));
    // Shift back
    value = value.toString().split('e');
    value = +(value[0] + 'e' + (value[1] ? +value[1] + exp : exp));
    return value;
}

js 浮点数乘以100或除以100等10的倍数容易造成精度缺失,但用 e2 这种形式来乘以或除以100等10的倍数不会造成精度缺失,如上面说的 10.22*100 = 1022.0000000000001,但用 10.22e2 这种表示就是 1022,不会造成精度缺失,decimal 就是利用了此原理来处理 js 浮点数的精度问题。

(6.265).toFixed(2) 结果为 “6.26”,正确的结果应该是 “6.27”。JS 的 toFixed 方法四舍五入时,有时候精度会丢失。

decimal.js 即解决了 js 浮点数计算后精度丢失的问题,又解决了 js toFixed 方法造成的精度丢失问题,但跟 toFixed 的还是有所区别的,toFixed 是固定保留几位小数,decimal 则是会把小数末尾的0省略掉。如果也要固定保留小数位数,可以先用 decimal 精确到几位小数,然后再用 toFixed 让其固定保留几位小数。

使用方法:

Decimal.round(各种运算, 要精确的小数位数)

各种运算的最终结果精度可能是丢失的,但用 decimal 对最终运算结果处理一下精度就不丢失了。

要精确的小数位数可选值:不传,0,负数,正数。

不传或传0 是四舍五入到整数。正数如果传1就是个位为0,十位是个位四舍五入的值。

测试 decimal.js:

import Decimal from './decimal';

console.log(Decimal.round(3.17))  //3
console.log(Decimal.round(3.17, 0))  //3
console.log(Decimal.round(1.13265, -3))  //1.133
console.log(Decimal.round(3.17, -3))  //3.17
console.log(Decimal.round(1000, -3))  //1000
console.log(Decimal.round(0.1+0.2, -3))  //0.3
console.log(Decimal.round(31216, 1))  //31220
console.log(Decimal.round(31213, 2))  //31200
Decimal.round(value/100*1000+32.2, -4)

方法二:四则运算各自封装

思路:把浮点数转换成整数运算。
加法运算

export const accAdd = (arg1,arg2)=>{
    let 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;
}

Number.prototype.add = function (arg){
    return accAdd(arg,this);
}

减法运算

export const accSub = (arg1,arg2)=>{
     let r1,r2,m,n;
     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));
     //动态控制精度长度
     n=(r1>=r2)?r1:r2;
     return  (arg1*m-arg2*m)/m;
}

Number.prototype.sub = function (arg){
    return accSub(arg,this);
}

乘法运算

export const accMul=(arg1,arg2)=>{
  let m=0, s1='', s2='';
  if(arg1&&arg1!=null)
    s1=arg1.toString();

  if(arg2&&arg2!=null)
    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);
}

//给Number类型增加一个mul方法,调用起来更加方便,以下类似
Number.prototype.mul = function (arg){
    return accMul(arg,this);
}
例: (3).mul(5) // 15

除法运算

export const accDiv = (arg1,arg2) => {
    let t1 = 0, t2 = 0, r1, r2;
    try {
        t1 = arg1.toString().split('.')[1].length;
    } catch (e) {}

    try {
        t2 = arg2.toString().split('.')[1].length;
    } catch (e) {}

    r1 = Number(arg1.toString().replace('.', ''));
    r2 = Number(arg2.toString().replace('.', ''));

    return (r1 / r2) * Math.pow(10, t2 - t1);
}

Number.prototype.div = function (arg){
    return accDiv(arg,this);
}

除法有时候会用到本身就除不尽的情况,就要用 decimal 精确到几位小数一下。

四、判断小数是否相等

function epsEqu(x,y) {  
  return Math.abs(x - y) < Math.pow(2, -52);
  // 因为 Number.EPSILON === Math.pow(2, -52),所以也可以这么写:
  // return Math.abs(x - y) < Number.EPSILON;
}
// 举例
0.1 + 0.2 === 0.3 // false
epsEqu(0.1 + 0.2, 0.3) // true

小数比较时,要给它一个误差范围,在误差范围内的都算相等。

五、其他由浮点数引起的问题

parseInt(0.0000008) // -> 8

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(JavaScript)