由于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.5、0.25、0.125、0.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,四舍五入问题
含义
对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”,这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:
(1)5前为奇数,舍5入1;
(2)5前为偶数,舍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在以下情景会自动将数值转换为科学计数法:
- 小数点前的数字多于21位。
- 小数点后的零多于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