js数字转字符串后科学计数问题
起因还是由于封装的函数库里面有一个精度计算的问题,代码如下:
const addExact(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 Math.round(arg1 * m + arg2 * m) / m;
};
原理就是按照小数点做切割,然后判断最大位数,把原来的小数转成整数相加后再除,解决小数位相加精度问题。
但是,代码里面忽略了一点,在某些情况下,浮点数toString()
会得到科学计数法表示的形式,而某些情况下不会,比如:
0.0004.toString(); // 0.0004
0.0000004.toString(); // 4e-7
搜索了一下,原来是有规范的:ToString Applied to the Number Type
按照规范:
0.0004:此时s
是4,k
是1,n
是-3,所以走的是步骤8;
0.0000004:此时s
是4,k
是1,n
是-6,所以并没有走步骤8,而是走到了步骤9,你会看到步骤9是首先是输出s
,也就是4,然后输出小写字母e(LATIN SMALL LETTER E),因为n - 1是-7,所以输出-(HYPHEN-MINUS),然后输出abs(n - 1),也就是7,所以加起来就是'4e-7';
简单总结一下:小数点后的0多于5个时候会默认展示科学计数法,或者小数点前的数字多于21个。
另外,又做了两次实验,假如整数 + 小数整体长度超过16位,精度就会有问题:
0.1000000000000001.toString(); //0.1000000000000001
0.10000000000000001.toString(); //0.1
所以,如果对精度什么的有要求,还是乖乖的用三方的库吧比如bignumber.js,decimal.js,big.js,math.js等。我们可以根据自己的需求来选择对应的工具。并且,这些库不仅解决了浮点数的运算精度问题,还支持了大数运算,并且修复了原生toFixed
结果不准确的问题。
最后来一个应急的解决方案:
function toNonExponential(num) {
var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
}
toNonExponential(3.3e-7) // "0.00000033"
toNonExponential(3e-7) // "0.0000003"
toNonExponential(1.401e10) // "14010000000"
toNonExponential(0.0004) // "0.0004"
用.toExponential()
将数字转化为科学记数法表示,匹配正则表达式/\d(?:\.(\d*))?e([+-]\d+)/
,获取科学记数法中小数点后的字符及幂指数(e 后面的值),这样可以确定数字是几位小数,再用toFixed()
转换成数值表示。