当前位置: 首页 > Web前端 > vue.js

面试官:说说JavaScript数值精度丢失的问题,解决方案?

时间:2023-03-31 21:44:58 vue.js

本文已收录前端面试题库1.场景转载一道经典面试题0.1+0.2===0.3//为什么false是false?我们来看下面的类比比如一个数1÷3=0.33333333......3会一直无限循环,数学可以表达,但是计算机需要存储起来,这样才能拿出来用下次再来,可是0.333333……这个数是无限循环的,不能存大内存,所以不能存相对于数学的一个值,只能存一个近似值,还有精度的问题计算机存储它然后再取出时会丢失。我们也可以理解为浮点数是以浮点数的格式存储的。在JavaScript中,目前主流的数值类型是Number,Number采用IEEE754规范中的64位双精度浮点数编码。这种存储结构的好处是可以对整数和小数进行归一化,以节省存储空间对于一个整数,可以方便地转换成十进制或二进制。但是对于一个浮点数,由于小数点的存在,小数点的位置是不固定的。解决办法是用科学记数法,这样小数点的位置就固定了,计算机只能用二进制(0或1)来表示。二进制转为科学计数法的公式如下:其中a的值为0或1,e为例如小数点移动的位置:27.0转为二进制为11011.0,科学计数法表示as:前面提到javaScript的存储方式是双精度浮点数,它的长度是8个字节,也就是64位,64位可以分为三部分:符号位S:第一个bit为正负符号位(sign),0代表正数,1代表负数指数位E:中间11位存放指数(exponent),用于表示乘方数,即可以是正的也可以是负的。在双精度浮点数中,指数的固定偏移量为1023位尾数M:后52位为尾数(mantissa),超出部分自动向上取整,如下图所示:例如:27.5转换为二进制11011.111011.1转换为科学计数法![[公式]](https://www.zhihu.com/equatio...符号位为1(正数),指数位为4+,1023+4,即1027需要转为二进制,因为是十进制,即10000000011,小数部分为10111,加52位,即:1011100000000000000000000000000000000000000000000000`所以27.5存储为计算机的二进制标准形式(符号位+指数位+小数部分(顺序)),如下图0+10000000011+011100000000000000000000000000000000000000000000000`,问题分析然后回到问题0.1+0.2===0.3//false通过上面的学习,我们知道,在javascript语言中,0.1和0.2都转化成二进制后再进行运算//0.1和0。另一个问题是,为什么x=0.1是0.1?主要原因是在存储二进制时,小数点的最大偏移量为52位,最多可以表示的位数为2^53=9007199254740992,对应科学计数法的尾数为9.007199254740992,即JS可以表示的最大精度。它的长度是16,所以可以用toPrecision(16)来做精度计算,超出的精度会自动向上取整。10000000000000000555.toPrecision(16)//返回0.1000000000000000,去掉最后的零后,正好是0.1,但你看到的0.1其实不是0.1。如果你不相信我,你可以尝试更高的精度:0.1.toPrecision(21)=0.100000000000000005551如果整数大于9007199254740992会怎样?由于指数的最大值是1023,所以可以表示的最大整数是2^1024-1,这是可以表示的最大整数。但是你不能这样计算这个数字,因为从2^1024变成Infinity>Math.pow(2,1023)8.98846567431158e+307>Math.pow(2,1024)Infinitythenfor(2^53,2^63)之间的数字会怎样?(2^53,2^54)之间的数会二选一,只能准确表示偶数。(2^54,2^55)之间的数会四选一,只能准确表示4个倍数……更多的2的倍数依次跳过。解决大数问题可以参考第三方库bignumber.js。原理是把所有数字都当成字符串,重新实现计算逻辑。缺点是性能比原来差很多汇总计算机需要将十进制数转换为二进制科学计数法来存储双精度浮点数,然后计算机使用自己的规则{符号位+(指数位+指数的二进制偏移量)+小数部分}来存储科学计数法二进制在存储时有位数限制(64位),有些十进制浮点数转换成二进制数会死循环,导致二进制四舍五入运算(0四舍五入为1),转为十进制时,会导致计算错误。3.解决方案理论上不可能用有限的空间存储无限小数来保证精度,但是我们可以对其进行处理,得到我们想要的结果。当你得到1.400000000000000001这样的数据时,建议使用toPrecision向上取整,使用parseFloat转换成数字后再显示,如下:parseFloat(1.40000000000000001.toPrecision(12))===1.4//封装True的方法是:functionstrip(num,precision=12){return+parseFloat(num.toPrecision(precision));}对于+-*/等算术运算,不能使用toPrecision。正确的方法是先把小数化成整数再计算。以加法为例:/***精确加法*/functionadd(num1,num2){constnum1Digits=(num1.toString().split('.')[1]||'').length;constnum2Digits=(num2.toString().split('.')[1]||'').length;constbaseNum=Math.pow(10,Math.max(num1Digits,num2Digits));return(num1*baseNum+num2*baseNum)/baseNum;}最后还可以使用第三方库,比如Math.js、BigDecimal.js参考https://zhuanlan.zhihu.com/p/...https://developer.mozilla.org...去github面试题库看更多