提出问题
parseInt() 函数用于解析数字字符串中的整数。例如,从数字字符串 '9.999' 中解析整数 9:
parseInt("9.999") === 9
现在我要用 parseInt 来解析 0.0000008,取整吗,预期结果应该是 0,但运行之后我们发现结果是数字 8。
parseInt(0.0000008) === 8 // true
这种神秘行为到底是为什么?
parseInt 把第一个参数转化为 string
运行 parseInt(0.0000008) 我们输入的是个小数,但是 parseInt 会利用 String 方法把输入的数字转成字符串 —— toString。
由此我们知道 parseInt 之所以出现不符合预期的行为,并不是因为 parseInt 函数本身,而是因为数字在 toString 过程中出现了不符合预期的行为。
研究 parseInt 奇怪的行为,本质上是研究 toString 的细节。
toString 细节
想要详细知道 toString 的细节,当然是去 ECMA 看文档了,ECMA 262 关于 toString 的细节文档链接:ToString Applied to the Number Type。
处理细节截图如下:
现在假定要自己实现 toString 这个函数,1-4 的步骤很简单,基本不做复杂逻辑处理直接返回结果,代码如下:
function toString(m) {
if (Number.isNaN(m)) {
return "NaN";
}
if (m === 0) {
return "0";
}
if (m < 0) {
return "-" + ToString(-m);
}
if (m === Infinity) {
return "Infinity";
}
}
第五步开始难点,关于数的科学计数法表示,根据描述 m 表示为:
m = s * 10^(n−k)
其中 n、s、k 都是整数,k 大于等于一且要尽可能的小。
按照规范:
0.0008:此时 s 是 8,k 是 1,n是 -3,所以走的是步骤 8;
0.0000008:此时 s 是 8,k是 1,n是 -6,所以并没有走步骤 8,而是走到了步骤 9,你会看到步骤 9 是首先是输出 s,也就是 8,然后输出小写字母 e(LATIN SMALL LETTER E),因为 n - 1 是 -7,所以输出-(HYPHEN-MINUS),然后输出 abs(n - 1),也就是 7,所以加起来就是"8e-7";
可以得到一个结论:一个数大于或等于 10^21 或小于 10^-6 ,则使用科学记数法。
现在目标明确了,toString 的时候 0.0000008 变成了 "8e-7"。parseInt("8e-7") 自然得到结果 8。
小于 10^-6 的数我们验证了,现在验证下大于等于 10^21 的数,比如 9999999999999999, 它的科学计数法为 1e+21 所以,parseInt(9999999999999999) 等于 1。
当调用 toString 处理的数字处于下面范围一定特别小心。