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

2020-12-09T02:51:02.png

按照规范:

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.jsdecimal.jsbig.jsmath.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() 转换成数值表示。

添加新评论