IEEE754浮点数
以前每次看到【0.1+0.2结果为什么不是0.3?】这个类问题时候,都是回答 “因为浮点数精度问题导致的”,然后就没有然后了,更深入的回答无法给出。偶然刷到一篇掘金文章解说浮点数问题,因此在这里记录一下。(文章地址:https://juejin.cn/post/6844903474480709640#heading-11)
1. 双精度浮点在64位上划分为3段,简称 1-11-52:
1. 1位最高位表示符号位,0表示正,1表示负;
2. 11位表示指数部分
3. 52位表示尾数部分,即有效域部分
举例:十进制数字:5346.8,用科学技术法表示就是:5.3468e3,第一部分是符号,第二部分5.3468是有效数字,第三部分为指数,指数为3。
二进制中尾数部分是52位,但很多十进制的数字不是仅用52位二进制就可以表示完全的,有些十进制转为二进制,它会是无限循环的二进制表示(下面会说十进制怎么转为二进制),所以52各=个坑位是不够的,那超出了怎么办?超出了则会进行 "舍入"规则处理,IEEE754规定了几种舍入规则,但是默认的是舍入到最接近的值,如果“舍”和“入”一样接近,那么取结果为偶数的选择。发生“舍入”操作,则导致精度丢失,所以在浏览器打印【0.1+0.2】结果不会是0.3。
2. 十进制转为二进制
1. 通过 【.toString()】实现转为二进制
2. 十进制转为二进制规则
整数转化规则:整数除以2,得商得余数,商继续除以2,直到商为0停止,得到的余数倒顺序一下得到的就为转化后的二进制值。
小数转化规则:小数乘以2,得到的值抽出整数部分,然后整数部分改为0继续乘以2,直到小数部分结果等于0,抽出的整数部分整理在一起,前面加个【0.】,得到的就为转后的二进制值。
3. 解决大多数业务场景的浮点数问题
//注意要传入两个小数的字符串表示,不然在小数转成二进制浮点数的过程中精度就已经损失了 function numAdd(num1/*:String*/, num2/*:String*/) { var baseNum, baseNum1, baseNum2; try { //取得第一个操作数小数点后有几位数字,注意这里的num1是字符串形式的 baseNum1 = num1.split(".")[1].length; } catch (e) { //没有小数点就设为0 baseNum1 = 0; } try { //取得第二个操作数小数点后有几位数字 baseNum2 = num2.split(".")[1].length; } catch (e) { baseNum2 = 0; } //计算需要 乘上多少数量级 才能把小数转化为整数 baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); //把两个操作数先乘上计算所得数量级转化为整数再计算,结果再除以这个数量级转回小数 return (num1 * baseNum + num2 * baseNum).toFixed(0) / baseNum; };
3. 浏览器中最大的安全数字是 -Math.pow(2,53)+1 到 Math.pow(2,53)-1
超过了这个安全数字,再进行运算,得到的结果不能保证正确。
按照我们的理解,下面的式子都很奇怪,一下false,一下true;
其实,超过了安全数字的,只要加的数字小于 Math.pow(2,x-53) ,等式都会成立。