JavaScript小数位问题总结

由于Js问题导致最终计算出现问题,目前有以下几种情况,下面代码均在chrome下测试:

1,js本身精度问题

var a=0.1;var b=0.2;console.log(a+b)

在数值相等比较时要注意

大小比较问题

在数值相等比较时要注意

(0.1+0.2)==0.3

应该加上toFixed

(0.1+0.2).toFixed(2)==(0.3).toFixed(2)

取相同位数比较是否完全一致

 

在数值大小比较时要注意

var a=123.22;var b=52.45; a.toFixed(2)>b.toFixed(2)

toFixed实际返回的是string,比较时按字符比较,应该加上*1

var a=123.22;var b=52.45; a.toFixed(2)*1>b.toFixed(2)*1

为什么精度有问题

计算机是用二进制来表示,小数是无穷的,浮点数位数有限,那么必然有些数无法精确表示

就像十进制的1/3只能写作0.3333333….

以4位二进制为例

0.0000~0.1111

只能表示 0.50.250.1250.0625这四个十进制数以及小数点后面的位权组合(相加)而成的小数:

0.0000 0
0.0001 0.0625
0.0010 0.125
0.0011 0.1875
0.0100 0.25
0.1000 0.5
0.1001 0.5625
0.1010 0.625
0.1011 0.6875
0.1111 0.9375

 

从上表可以看出,十进制数 0 的下一位是 0.0625,所以,0~0.0625 之间的小数,就无法用小数点后 4 位数的二进制数表示;如果增加二进制数小数点后面的位数,与其相对应的十进制数的个数也会增加,但无论增加多少位,都无法得到 0.1 这个结果。

// 0.1 转化为二进制

0.0 0011 0011 0011 0011...(0011循环)

 

// 0.2 转化为二进制

0.0011 0011 0011 0011 0011...(0011循环)

 

 

JavaScript中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数(相关的还有float 32位单精度)。这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。64位比特又可分为三个部分:

符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数

指数位E:中间的 11 位存储指数(exponent),用来表示次方数

尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

 

双精度符点数的可靠位数为15位,也就是说从第16位开始可能是不对的,如0.1 + 0.2 = 0.30000000000000004,最后面的04这两位是不可靠的。

 

最大数值

Number.MAX_SAFE_INTEGER

Number.MAX_VALUE

 

Number.MAX_VALUE + 1 == Number.MAX_VALUE;

Number.MAX_VALUE + 2 == Number.MAX_VALUE;

...

Number.MAX_VALUE + x == Number.MAX_VALUE;

Number.MAX_VALUE + x + 1 == Infinity;

...

Number.MAX_VALUE + Number.MAX_VALUE == Infinity;

 

申报中经常会用到xml2json 一定要加上textNodeConversion false

因为nsrsbh  djxh等全为数字时,不能转数值,会丢失精度

const gzsbqrxx = xml2json.parse(xmlDoc, { textNodeConversion: false });

 

2,四舍五入问题

 

含义

编辑

对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是四舍六入五成双,也即“465凑偶,这里是指≤4 时舍去,""是指≥6时进上,""指的是根据5后面的数字来定,当5后有数时,舍51;当5后无有效数字时,需要分两种情况来讲:

15前为奇数,舍51

25前为偶数,舍5不进(0偶数)。

具体规则

编辑

1)被修约的数字小于5时,该数字舍去;

2)被修约的数字大于5时,则进位;

3)被修约的数字等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。

 

 

方案一:

所以解决四舍五入重点在规则3,按我们通常的四舍五入,5是一定要入的,只需要5的后面补上不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。补位的同时一定要不影响原来数的大小,下面方法实现在原小数位后补01

Number.prototype.toFixed = function (digit = 0) {
   
let value = `${this}`;
   
if (this > 0) {
       
value = (Math.round((this * (10 ** digit)) + 0.00001) / (10 ** digit)).toString();
    }
else if (this < 0) {
       
value = (Math.round((this * (10 ** digit)) - 0.00001) / (10 ** digit)).toString();
    }
   
value = scientificToNumber(value);
   
const [integer, decimal = ''] = value.split('.');
   
if (digit > 0) {
       
return `${integer}.${decimal.padEnd(digit, '0')}`;
    }
   
return integer;
};

 

 

方案二:

我们通过判断最后一位是否大于等于5来决定需不需要进位,如果需要进位先把小数乘以倍数变为整数,加1之后,再除以倍数变为小数,这样就不用一位一位的进行判断。

// toFixed兼容方法
Number.prototype.toFixed = function(len){
   
if(len>20 || len<0){
       
throw new RangeError('toFixed() digits argument must be between 0 and 20');
   
}
   
// .123转为0.123
   
var number = Number(this);
    if
(isNaN(number) || number >= Math.pow(10, 21)) {
       
return number.toString();
   
}
   
if (typeof (len) == 'undefined' || len == 0) {
       
return (Math.round(number)).toString();
   
}
   
var result = number.toString(),
       
numberArr = result.split('.');

    if
(numberArr.length<2){
       
//整数的情况
       
return padNum(result);
   
}
   
var intNum = numberArr[0], //整数部分
       
deciNum = numberArr[1],//小数部分
       
lastNum = deciNum.substr(len, 1);//最后一个数字

   
if(deciNum.length == len){
       
//需要截取的长度等于当前长度
       
return result;
   
}
   
if(deciNum.length < len){
       
//需要截取的长度大于当前长度 1.3.toFixed(2)
       
return padNum(result)
    }
   
//需要截取的长度小于当前长度,需要判断最后一位数字
   
result = intNum + '.' + deciNum.substr(0, len);
    if
(parseInt(lastNum, 10)>=5){
       
//最后一位数字大于5,要进位
       
var times = Math.pow(10, len); //需要放大的倍数
       
var changedInt = Number(result.replace('.',''));//截取后转为整数
       
changedInt++;//整数进位
       
changedInt /= times;//整数转为小数,注:有可能还是整数
       
result = padNum(changedInt+'');
   
}
   
return result;
   
//对数字末尾加0
   
function padNum(num){
       
var dotPos = num.indexOf('.');
        if
(dotPos === -1){
           
//整数的情况
           
num += '.';
            for
(var i = 0;i<len;i++){
                num +=
'0';
           
}
           
return num;
       
} else {
           
//小数的情况
           
var need = len - (num.length - dotPos - 1);
            for
(var j = 0;j<need;j++){
                num +=
'0';
           
}
           
return num;
       
}
    }
}

 

四舍五入先后问题

var a=0.112;var b=0.223;console.log((a+b).toFixed(2));

var a=0.112;var b=0.223;console.log((a.toFixed(2)*1+b.toFixed(2)*1).toFixed(2));

 

这个问题有点不可预料,因为需求也只是拿到计算公式,并不清楚计算过程变量是否有四舍五入,只能是在排查问题时能及时想到这一点。申报这边通常是把所有计算做完最后才去做四舍五入,大多也没有问题。目前发现一处计算与金三不同。

 

3,科学计数法问题

 

太小的数会自动变成科学计数法

JS中科学计数法出现的时机

    JavaScript在以下情景会自动将数值转换为科学计数法:

  1. 小数点前的数字多于21位。

 

  1. 小数点后的零多于5个。

原生的toFixed方法是可以去掉科学计数法的。

/**

 * 修复科学计数和法问题

 * @param num 小数

 * @returns {string}

 */

function scientificToNumber(num) {

    const str = num;

    const reg = /^([-]?\d+\.?\d*)(e)([-|+]?\d+)$/;

    let zero = '';

    if (!reg.test(str)) {

        return num;

    }

    const arr = reg.exec(str);

    const len = Math.abs(arr[3]) - 1;

    for (let i = 0; i < len; i += 1) {

        zero += '0';

    }

    if (arr[1].indexOf('-') == 0) {

        return `-0.${zero}${arr[1].replace('-', '')}`;

    }

    return `0.${zero}${arr[1]}`;

}

 

个人博客地址:http://cocosilent.gitee.io

猜你喜欢

转载自blog.csdn.net/xsl510079027/article/details/86686551