前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >原理解析 | JavaScript 计算0.1 + 0.2真的很难,看完才知道!

原理解析 | JavaScript 计算0.1 + 0.2真的很难,看完才知道!

作者头像
HTML5学堂
发布2020-07-14 16:57:18
7010
发布2020-07-14 16:57:18
举报
文章被收录于专栏:HTML5学堂HTML5学堂

已经很久没有写技术文章了,脑袋瓜有点生锈,写的不好别见怪,今天就是想带点干货给大家分享一下。文章的内容有一点点难度,不过基本都是计算机组成原理的知识,算是温故而知新吧!

学过JavaScript的童鞋应该非常清楚,0.1 + 0.2 是不等于0.3的,而是等于0.30000000000000004,至于为什么会这样?好像有点说不清楚,只知道是JavaScript精确度问题,其他不得而知了。没关系,看完慢慢就懂了!

目录

  • 浮点数十进制转二进制
  • IEEE 754 标准
  • 浮点数二进制运算
  • 实践扩展

十进制转二进制

0.1的二进制:0.000110011......0011......(0011无限循环)

0.2的二进制:0.00110011......0011...... (0011无限循环)

说明:转换过程就不在这边描述了

IEEE 754 标准

IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。 这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。 百度百科

浮点数存储格式

上图是64位的双精度浮点数,最高位是符号位S(sign),中间的11位是指数E(exponent),剩下的52位为尾数(有效数字)M(mantissa)。

浮点数科学计数法

根据IEEE 754标准,任意一个浮点数的二进制都可以用如下公式进行表示:

S为符号位:表示浮点数的正负(0代表正数,1代表负数);

E为指数位:存储指数,该数都会加上一个常数(偏移量),用来表示次方数;

M为尾数位:表示有效位(尾数),超出的部分自动进1舍0;

双精度的浮点数真值(带有正负号的数值是真值)最终可以表示为:

说明:E是无符号整数,长度是11位,取值范围是为0~2047。因为科学计数法中的指数是可以为负数,所以约定减去一个中间数(偏移量)1023,[0,1022] 表示为负,[1024,2047] 表示为正。

浮点数二进制运算

规格化

大部分二进制浮点数都以规格化格式进行存放,以便将有效数字的精度最大化,提升精确度。

0.1的二进制

0.000110011001100110011001100110011001100110011001100110011...... (0011无限循环)

0.1科学计数法表示

小数点向右移动4位,让其小数点左边只有一个“1”。

1.10011001100110011001100110011001100110011001100110011......(0011无限循环) * 2^-4

根据IEEE754标准,双精度浮点的尾数只能存储52位,红色的“1”是第53位,根据进1舍0的原则进行操作,操作后的值为:

1.1001100110011001100110011001100110011001100110011010* 2^-4

0.1二进制存储格式

指数-4等于1019(E) – 1023(常量),由此可得E等于1019,把1019转为二进制1111111011。

最终表示如下:

0,01111111011;1001100110011001100110011001100110011001100110011010

说明:红色为符号位;绿色为指数位;蓝色为尾数位;

0.2的二进制

0.001100110011001100110011001100110011001100110011001100110011...... (0011无限循环)

0.2科学计数法表示

小数点向右移动3位,让其小数点左边只有一个“1”。

1.10011001100110011001100110011001100110011001100110011......(0011无限循环) * 2^-3

红色的“1”是第53位,根据进1舍0的原则进行操作,操作后的值为:

1.1001100110011001100110011001100110011001100110011010...... (0011无限循环) * 2^-3

0.2二进制存储格式

指数-3等于1020(E) – 1023(常量),由此可得E等于1020,把1020转为二进制1111111100。

最终表示如下:

0,01111111100;1001100110011001100110011001100110011001100110011010

说明:红色为符号位;绿色为指数位;蓝色为尾数位;

对阶

对阶的目的是使两数的小数点位置对齐,方便两数进行运算,换句话说就是两数的阶码要相等。根据小阶向大阶看齐的原则,应使0.1的尾数向右移动1位(可以理解为小数点向左移动1位),阶码加1。

尾数向右移动1位后,阶码和尾数的值变化如下:

阶码:01111111011 + 1 ——> 01111111100

尾数:

11001100110011001100110011001100110011001100110011010

——> 1100110011001100110011001100110011001100110011001101

蓝色的“1”是右移补的位,红色的0是舍去的位,根据IEEE 754标准双精度浮点的尾数只能存储52位,遵循进1舍0的原则进行操作。

0.1的科学计数法表示:

0.1100110011001100110011001100110011001100110011001101 *2^-3

0.1的二进制存储格式:

0,01111111100;1100110011001100110011001100110011001100110011001101

尾数求和

尾数部分M通常都是规格化表示的,非"0"的尾数其第1位总是"1",而这一位也称作隐藏位,因为存储的时候该位会被省略。比如存储1.0110时,只存储尾数0110,等到读取的时候才把第1位的1加补上去,这么做相当于多保存了1位有效数字。

0.1的尾数 + 0.2的尾数 =

0.1100110011001100110011001100110011001100110011001101 + 1.1001100110011001100110011001100110011001100110011010 = 10.0110011001100110011001100110011001100110011001100111

由此可得:

0.1 + 0.2 = 10.0110011001100110011001100110011001100110011001100111* 2^-3

结果规格化

根据尾数求和的结果,进行规格化处理,即尾数向右移1位,阶码加1。

1.00110011001100110011001100110011001100110011001100111 * 2^-2

尾数只能存储52位,红色的“1”需要舍去,根据进1舍0的原则进行操作可得:

1.0011001100110011001100110011001100110011001100110100* 2^-2

二进制存储格式:

指数-2等于1021(E) – 1023(常量),由此可得E等于1021,把1021转为二进制01111111101;

0,01111111101;0011001100110011001100110011001100110011001100110100

溢出判断

浮点数的溢出其实是阶码的溢出表现出来的,在算术运算过程中要检查是否产生了溢出。若阶码正常,算术运算正常结束;若阶码溢出,则要进行相应处理。

如上求和结果的阶码为01111111101,没有产生溢出,因此运算结束。

结果转为十进制

二进制存储格式是计算机存储和运算的格式,此时把二进制转为十进制,我们可以看看最终求和的值会是多少?

规格化的值是转为非规格化

指数的值为2,将规格化的小数点向左移动2位即可。

1.0011001100110011001100110011001100110011001100110100* 2^-2

——>

0.010011001100110011001100110011001100110011001100110100

非规格化的值转成十进制

0.1 + 0.2 =

0.3000000000000000444089209850062616169452667236328125

此时此刻,你应该明白JavaScript中0.1 + 0.2 = 0.30000000000000004 这个值是怎么来的吧。

实践扩展:

为了方便大家学习与实践,提供如下PHP实践代码,它是将“0.010011001100110011001100110011001100110011001100110100”转成十进制的功能代码,大家可以尝试测试一下。

代码语言:javascript
复制
var_dump(number_format(0+ 1 * pow(2, -2) + 0 + 0 + 1 * pow(2, -5) + 1 * pow(2, -6) + 0 + 0 + 1 * pow(2,-9) + 1 * pow(2, -10) + 0 + 0 + 1 * pow(2, -13) + 1 * pow(2, -14) + 0 + 0 + 1 *pow(2, -17) + 1 * pow(2, -18) + 0 + 0 + 1 * pow(2, -21) + 1 * pow(2, -22) + 0 +0 + 1 * pow(2, -25) + 1 * pow(2, -26) + 0 + 0 + 1 * pow(2, -29) + 1 * pow(2,-30) + 0 + 0 + 1 * pow(2, -33) + 1 * pow(2, -34) + 0 + 0 + 1 * pow(2, -37) + 1* pow(2, -38) + 0 + 0 + 1 * pow(2, -41) + 1 * pow(2, -42) + 0 + 0 + 1 * pow(2,-45) + 1 * pow(2, -46) + 0 + 0 + 1 * pow(2, -49) + 1 * pow(2, -50) + 0 + 1 *pow(2, -52) + 0 + 0, 52));

好了,干货分享完毕,谢谢大家!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 懂点君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档