当前位置: 首页 > 科技观察

如何解决Javascript数字丢失精度的问题?_0

时间:2023-03-21 12:01:30 科技观察

我们在处理数据的时候可能会遇到0.1+0.2!=0.3这样的问题,我们来分析一下原因:因为JS采用IEEE754双精度版本(64位),只要使用IEEE754语言All有这个问题(因为我知道java)。我们都知道计算机用二进制存储东西,0.1和0.2二进制转换后就是死循环,所以其实没有问题,但是JS采用的浮点数标准会将后面的数字截掉,导致丢失精度为0.1+0.2=0.30000000000000004。场景再现一道经典面试题:0.1+0.2===0.3//false为什么是false?我们看下面的比喻:比如一个数1÷3=0.33333333...3会一直无限循环,数学可以表达,但是计算机需要存储起来,以便下次取出来再次使用time,but0.333333...这个数是无限循环的,不能存储在大内存中,所以不能存储一个相对于数学的值,只能存储一个近似值,会出现精度丢失的问题计算机将其存储并再次取出。比如18466.67*100,按道理说等于1846667,其实等于1846666.9999999998,效果如下:浮点数“浮点数”是表示数字的标准,整数可以也以浮点数的格式存储。我们也可以理解为浮点数是小数。在JavaScript中,目前主流的数值类型是Number,Number使用IEEE754规范中的64位双精度浮点数编码。这样的存储结构的好处是可以对整数和小数进行归一化,节省存储空间。对于整数,可以很容易地转换为十进制或二进制。但是对于一个浮点数,由于小数点的存在,小数点的位置是不固定的。解决办法是用科学记数法,这样小数点的位置就固定了。但是,计算机只能用二进制(0或1)来表示。二进制转科学计数法的公式如下:其中a的值为0或1,e为小数点移动的位置。例如:27.0转换成二进制就是11011.0,用科学计数法表示为:前面说过,javaScript的存储方式是双精度浮点数,它的长度是8个字节,即64位。64位的bit可以分为三部分:符号位S:第一位是正负符号位(sign),0代表正数,1代表负数。指数位E:中间11位存储指数(exponent),用于表示幂数,可以是正数也可以是负数。在双精度浮点数中,指数有一个固定的偏移量1023。尾数位M:最后52位为尾数(mantissa),超出的部分会自动向上取整。如下图所示:例如:27.5转换为二进制11011.111011.1转换为科学计数法,符号位为1(正数),指数位为4+,1023+4,即需要1027因为是十进制,所以转化为二进制,即10000000011,小数部分为10111,加上52位,即:1011100000000000000000000000000000000000000000000000`。因此,27.5以计算机的二进制标准形式(符号位+指数位+小数部分(次序))存储,如下所示:0+10000000011+011100000000000000000000000000000000000000000000000`二进制的基数是2有2个数,即0和1全2变成1。八进制的基数8由8个数组成,分别是0、1、2、3、4、5、6、7和8转1。我们日常生活中用到的都是十进制,即10转1的十六进制以16为底。它由16个数字符号组成,分别是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F中国古代用的是满16进1重量单位是十六进制,16两就是1斤,所以有所谓的“半斤”八两”例:例:十进制:1234567891011...数到10时,只要输入1位即可,即十位写1,十位写0个位,即完整的十进制为一。二进制:0110111011110111101...数2时,需要进1位,前一位写1,当前位变为0,即全二进制进一。碱基之间如何转换?如果没有,自己百度一下。问题分析再回到问题:0.1+0.2===0.3//false通过上面的学习,我们知道在javascript语言中,0.1和0.2都是先转为二进制再运算。//0.1和0.2都转化成二进制后再进行运算0.00011001100110011001100110011001100110011001100110011010+0.0011001100110011001100110011001100110011001100110011010=0.0100110011001100110011001100110011001100110011001100111//转成十进制正好是0.30000000000000004所以输出false。Onemorequestion,sowhyx=0.1gets0.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开始就变成无穷大了。>Math.pow(2,1023)8.98846567431158e+307>Math.pow(2,1024)Infinity那么(2^53,2^63)之间的数字会发生什么变化?(2^53,2^54)之间的数会二选一,只能准确表示偶数。(2^54,2^55)之间的数会四选一,只能准确表示4个倍数……更多的2的倍数依次跳过。解决大数问题可以参考第三方库bignumber.js。原理是把所有数字都当成字符串,重新实现计算逻辑。缺点是性能比原来差了很多。浮点数存储机制(单精度浮点数、双精度浮点数)。浮点数数据类型主要有:单精度float、双精度double单精度浮点数(float)单精度浮点数在内存中占用4个字节,8位有效数字,表示范围:-3.40E+38~+3.40E+38双精度浮点数(double)双精度浮点数在内存中占8个字节,有效位16位,表示范围:-1.79E+308~+1.79E+308有两个浮点数常量两种表示形式:1、十进制数形式:由数字和小数点组成,必须有小数点,如0.123、123.0。科学计数形式:如:123e3或123E3,其中e或E前必须有数字,e或E后的指数必须是整数(当然也包括负整数)。浮点型简单的说就是用小数来表示数据,小数点可以浮动在对应二进制的不同位置,可以定义为浮点型。~不得不佩服这学历,定义一个数据名好深奥啊~但是!!!JavaScript中小数的存储方式不同于Java、Python等其他语言。JavaScript中的所有数字,包括整数和小数,都只有一种类型,即Number类型。它的实现遵循IEEE754标准。我们不关心IEEE754标准的内容,我们只需要记住以下几点:总结计算机需要将十进制数转换为二进制科学计数法来存储双精度浮点数,然后计算机使用自己的规则{符号位+(指数位+指数偏移量binary)+fractionalpart}存储二进制科学计数法。因为存储时有位数限制(64位),而且一些十进制浮点数转换成二进制数会出现死循环,导致二进制四舍五入操作(0舍入1),转换成十进制时导致计算错误。解决方案理论上,在空间有限的情况下,无法保证存储无限小数的准确性,但我们可以对其进行处理,得到我们期望的结果。当你得到1.4000000000000001这样的数据显示时,建议先用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,我们可以这样处理:parseFloat((0.1+0.2)。toFixed(10))parseFloat((18466.67*100).toFixed(0))