懂了js中0.1+0.2 !=0.3之后,没懂0.1+0.5=0.6,0.2+0.3=0.5 和 0.3+0.4=0.7?没关系,来一起懂!

这篇文章主要是结合几个典型的例子,来讲清楚浮点数运算造成精度丢失的问题,计算机中浮点数的相等和不相等是怎么判定的,授之以鱼且授之以渔。

抛砖引玉

大家可以自行验证一下,直接浏览器console面板逐个验证:

0.1+0.2===0.3;//false
0.1+0.5===0.6;//true
0.2+0.3===0.5;//true
0.3+0.4===0.7//true

我精心设计的这几个例子分别代表的情况:

  • 不精确数+不精确数!=不精确数

  • 不精确数+精确数===不精确数

  • 不精确数+不精确数===精确数

  • 不精确数+不精确数===不精确数

是不是有点迷糊?看完下文解释,我们最后进行逐个分析:

概念解释

计算机中存储数字都是用二进制来表示的,有些浮点数可以用有限位数的二进制数精确表示,还有些(可以说大部分常见的浮点数都是)是不能用有限位数的二进制数精确表示的。

计算机总该要存储和表示他们,但是由于一方面的计算机在设计的时候的一些先天硬件原因,一次性处理二进制的位数是有限的,另一方面更主要的是针对不同类型(比如整数,浮点数)的数据计算机计算(加减乘除之类)的方式不一样,所以计算机对特定格式的数字有统一的存储格式。其中针对浮点数主要是IEEE754格式:1位符号位,8位阶码,23位尾数;

当然还有其他的存储格式,我们可以想象不同计算机,根据实际用途不同,他们对精度的要求可能就不同,可能就会采用不同的存储格式,比如用于航天的要更精确,位数要更多,尾数也更长等等。

所以对于一个浮点数,如果他的二进制形式是无限循环的,计算机一旦存储了他们就一定截取尾数,会造成值的放大或者缩小。所以到了计算机里就不存在无限循环小数,只会存在精确数(我测算了一下我的笔记本,最多精确到十进制小数点后54位)

//我这里只截取他们十进制的前20位小数
0.1.toPrecision(20)//'0.10000000000000000555',比实际要大
0.2.toPrecision(20)//'0.20000000000000001110',比实际要大
0.3.toPrecision(20)//'0.29999999999999998890',比实际要小
0.4.toPrecision(20)//'0.40000000000000002220',比实际要大
0.5.toPrecision(20)//'0.50000000000000000000',精确
0.6.toPrecision(20)//'0.59999999999999997780',比实际要小
0.7.toPrecision(20)//'0.69999999999999995559',比实际要小
0.8.toPrecision(20)//'0.80000000000000004441',比实际要大
0.9.toPrecision(20)//'0.90000000000000002220',比实际要大

0.3.toPrecision(60)//我设置精确到60位小数,但是实际只精确到54位,其他的都被截取了
//'0.299999999999999988897769753748434595763683319091796875000000'

3333.3.toPrecision(54)//现在加上整数部分,发现精确位数比54位还少,这是因为IEEE754有一个对阶的操作,对尾数超出23位二进制的部分进行了舍弃,细节请自行查阅
//'3333.30000000000018189894035458564758300781250000000000'

看完上面的精确到十进制20位的结果,现在再看看最初的例子吧,你一定能发现为什么是那个结果:

0.1+0.2!=0.3

因为0.1和0.2在存储的时候比实际的都要大,而0.3在存储的时候比实际要小。0.1和0.2都进行了放大,他们的和肯定就放的更大了,但是0.3却缩小,这就造成了他们不相等

0.1+0.5===0.6

0.1放大,0.5精确,0.6放大 => 结果可能是相等的。实际我们简单计算一下就会发现确实是相等的。

0.2+0.3===0.5

0.2放大,0.3缩小,0.5精确 => 结果可能是相等的。实际我们简单计算一下就会发现确实是相等的。

0.3+0.4===0.7 (真正让你懂底层细节)

0.3缩小,0.4放大,0.7缩小 => 结果可能是相等的。但是实际计算一下会发现不相等的。然而浏览器却告诉我们相等,难道它在骗我们?我们简单算一下

0.3+0.4相当于:
0.29999999999999998890 + 0.40000000000000002220 =0.70000000000000001110
,而0.7是0.69999999999999995559 也就是说计算机里面0.69999999999999995559===0.70000000000000001110

我们可以大胆猜测一下:他们之间的差应该是有一个阈值,在阈值范围之内,就算是他们相等。

验证一下:随便把0.70000000000000001110的最后几位改个数字看看他们相不相等

0.69999999999999995559===0.70000000000000000007//true

事实是:当比较两个数是否相等的时候,会比较他们实际在计算机中存储的那个值是否相等。就是说就算他们在字面上不相等,但是如果在计算机中存储的内容一样,那么就算是他们之间相等。
验证一下:

0.69999999999999995559.toPrecision(20)//'0.69999999999999995559'
0.70000000000000000007.toPrecision(20)//'0.69999999999999995559'

不出所料他们在计算机中存储的内容果然一样。其实上文中的那个猜测,其实也算粗略地算正确,可以辅助我们理解,但是我们应该知道实际比的是计算机中的二进制的值。

总结和问题解决

通过简单的例子和大胆猜测,细心求证。我想大家应该明白了为什么浮点数运算会造成精度丢失问题,最主要原因是因为计算机位数有限,在存储和表示时会造成对数字不同层度地放大和缩小。

那要怎么解决呢,其实,我们只需要一个js中一个数字对象的方法toFixed(n),就可以进行精度矫正。这个方法接受一个数字,表示保留几位小数,他会自动帮助我们进行舍入,注意他返回的是一个字符串,想要数字的话需要进行类型转换。
例如:

+(0.1+0.2).toFixed(3)===0.3//true
  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值