事实上,不仅仅是 JS,在其他采用 IEEE754 浮点数标准的语言中,0.1 + 0.2 都不会等于 0.3,但是 0.2 + 0.3 却等于 0.5,这是为何?想必这类问题也困扰着不少程序员。

IEEE754 浮点数的演算

我们知道,科学计数法中 30000 可以写成 3x104,以 10 为底数 4 为指数的科学计数法。在 IEEE754 标准中是比较类似的,只不过它是二进制数,底数也为 2。

IEEE 754 中最常用的浮点数值表示法是:单精确度(32位)和双精确度(64位),JavaScript 采用的是后者。举个例子,十进制数 150,使用双精度浮点数表示法,表示如下:

// D 表示十进制,B 表示二进制

150D =

2^

8 *

0.1001011B

// 后面省略了 46 个 0

可以通过短除法计算:

150 余数位

÷ 2

---------------

75 0

÷ 2

---------------

37 1

÷ 2

---------------

18 1

÷ 2

---------------

9 0

÷ 2

---------------

4 1

÷ 2

---------------

2 0

÷ 2

---------------

1 0

÷ 2

---------------

0 1

最后一个余数为高位值,于是拿到 150 对应的二进制数位 1001011,也就等于 2^8 * 0.1001011。

上面是整数的表示法,而小数的表示法采用的是乘二取整,如 0.1,它的二进制表示为:

// (0011) 表示循环

0.1D =

2^

-3 *

0.110011(

0011)

其演算方法如下:

0.1 整数位

× 2

---------------

0.2 0

× 2

---------------

0.4 0 * ↓

× 2

---------------

0.8 0

× 2

---------------

1.6 1

× 2

---------------

1.2 1

× 2

---------------

0.4 0 * ↑

(0011循环)

与整数不同的是,第一个计算得到的整数位为最高位,故 0.1 对应的二进制数为 0.000110011(0011),也就等于 2^-3 0.1100110011(0011)。

如果一个数既包含整数部分,又包含小数部分,其表示法的计算,需要分拆为整数和小数两部分,然后相加得到结果。

IEEE754 浮点数精度丢失

IEEE754 浮点数表示法的数据格式如下图:

// 下图采用大端表示,高位在左,低位在右。

sign exponent fraction

+---+----------+---------------------+

|

1 |

2~

12 |

13~

64 |

+---+----------+---------------------+

符号位:高位第 1 位,如图 sign 部分

指数位:高位第 2~12 位,如图 exponent 部分

尾数位:剩下的 fraction 部分

从上面小数的乘二取整演算中可以看到,有些小数对应的二进制数是无法写全的,比如 0.1,而 fraction 尾数部分有要求,只允许 52 位,超过部分进一舍零。

那么,我们就可以得到:

0.1D

= 2^-4 * 1.10011(0011)B

= 2^-4 * 1.10011(0011 repeat 12 times)0011B // ← 最后一位为 1,进 1

= 2^-4 * 1.10011(0011 repeat 12 times)010B

揭秘 0.1 + 0.2

根据上面我们了解到的知识,我们可以很容易算出这些值:

0.1D =

2^

-4 *

1.1001100110011001100110011001100110011001100110011010B

0.2D =

2^

-3 *

1.1001100110011001100110011001100110011001100110011010B

0.3D =

2^

-2 *

1.0011001100110011001100110011001100110011001100110011B

0.1 + 0.2 时,先将两者指数统一为 -3,故 0.1 小数点向左移一位,于是:

0.1100110011001100110011001100110011001100110011001101B

+ 1.1001100110011001100110011001100110011001100110011010B

------------------------------------------------------------

= 10.0110011001100110011001100110011001100110011001100111B

得到的二进制数为:

10

.0110011001100110011001100110011001100110011001100111B

小数点往左移一位使得整数部分为 1,此时尾数部分为 53 位,进一舍零,于是得到最后的值是:

2^

-2 *

1.0011001100110011001100110011001100110011001100110100

这个值转化成真值,结果为:0.30000000000000004。那么 0.1 + 0.2 = 0.30000000000000004 的推演到这里就结束了。

相关验证

毕竟咱们手动计算可能存在笔误,可以通过一个叫做 double-bits 的 npm 进行推演,我写了一个小 demo,感兴趣的可以玩耍下:

const db =

require(

'double-bits');

const pad =

require(

'pad');

// [lo, hi] where lo is a 32 bit integer and hi is a 20 bit integer.

const base2Str =

(n) => {

const f = db.fraction(n);

const s = db.sign(n) ?

'-' :

'';

const e =

`2^${db.exponent(n) + 1}`;

const t =

`0.${pad(f[1].toString(2), 20, '0')}${pad(f[0].toString(2), 32, '0')}`;

return

`${s}${e} * ${t}`;

};

console.log(base2Str(

0.1).toString(

2));

console.log(base2Str(

0.2).toString(

2));

console.log(base2Str(

0.3).toString(

2));

console.log(base2Str(

1.2).toString(

2));

上面输出结果为:

2^

-3 *

0.11001100110011001100110011001100110011001100110011010

2^

-2 *

0.11001100110011001100110011001100110011001100110011010

2^

-1 *

0.10011001100110011001111001100110011001100110011001100

2^

1 *

0.10011001100110011001111001100110011001100110011001100

最后

为了按照计算机的思维,IEEE754 的标准来计算 0.1 + 0.2,又重新复习了一遍大学计算机基础的知识,原码、反码、补码,以及除二取余、乘二取整计算法,最后能够推演出来,也算是一个胜利吧~

php键名改为0.1.2.3,揭秘 0.1 + 0.2 != 0.3(php 请自觉点用round)相关推荐

  1. php 去掉多维数组的键名,去除多维数组的最外层key 保留值

    如果你是要将JSON转成PHP数组,方法如下 首先,你这个数据格式是JSON的,要先转成PHP数组. $a = json_decode($a, TRUE); json_decode第二个参数为TRUE ...

  2. 把文件每行的tab键分隔符改成逗号分隔符

    ###################################### #把文件每行的tab键分隔符改成逗号分隔符 # ##################################### ...

  3. php某列为键数组为值,PHP 将二维数组中某列值作为数组的键名 -- 超实用

    有时候,想通过数组的中某字段值, 然后再在二维数组中获取存在该字段值的数组: 一般能想到的就是foreach 遍历比较一下跟该字段值一样,就获取到想要的数组,如下: //测试二维数组 $arr =ar ...

  4. 获取php数组的键名和值

    要返回数组中的所有值,可以使用array_values()函数.该函数将忽略原始的键名,使用顺序的数字对数组重新索引.要返回一个数组的所有键,可以使用array_keys()函数.该函数返回一个包含数 ...

  5. php 返回数组 键名,php array_keys 返回数组的键名

    array_keys返回数组中部分的或所有的键名 说明 array array_keys ( array $array [, mixed $search_value [, bool $strict = ...

  6. php 数组键值分离,array_keys array_values::PHP数组键名于键值分离

    在PHP的数组中,如果你想将数组中所有键值提取出来组成一个新的数组,可以使用array_keys 函数. 函数用法如: 分离出数组键名array_keys(array,value) W3C中是这样介绍 ...

  7. php数组操作之合并相同键名的值,排序,排重,去空值等

    一.前言 数组操作是咱们在编程时候经常遇到的,只是数组函数有点多,用到的时候难免会想不起来.特别是针对多维数组的操作,有的时候用自带的数组函数真的很方便,可以避免多次的foreach循环,这里记录一下 ...

  8. php获取数组中,相同键名的键值之和

    一.记录一下一个数组求相同键名的键值和的简便方法: 比如你有一个数组: $arr = array([0]=>array([0]=>array('user_id'=>100,'fiel ...

  9. php 获取数组最小值,php 获取数组中最小的值与键名的方法

    上一篇博文说的是php获取数组中最大的值与键名的方法,那么这篇博文就说一下php获取数组中最小的值与键名的方法.获取数组中最小的值可以使用php中的预设函数 min() ,其使用方法也非常的简单. p ...

最新文章

  1. 移动、联通、电信7模4G全网通
  2. java 之选择排序与冒号排序的详解
  3. Asp.Net Core(.net内核)
  4. c语言 链表 删除节点,C语言实现单链表节点的删除(不带头结点)
  5. 6-18 23:50 day15
  6. Educational Codeforces Round 75 (Rated for Div. 2) E2. Voting (Hard Version) 贪心
  7. mysql导出表结构 创建_mysql如何导出表结构为文本文件
  8. Ajax网络超时和和网络异常
  9. 多服务器消息推送消息,多浏览器窗口接收websocket服务器推送消息问题
  10. “独立博客”为什么独立?
  11. 为何电脑系统相对通用而手机却相对定制
  12. vs2015安装vs assist 教程
  13. 海康威视错误代码说明(一)(错误代码:1~14)
  14. 有吧友需要PDF的下载站点,好吧,我这边汇总一下
  15. “李记餐厅”微信点餐小程序+后台管理系统
  16. 需求工程方法及技术汇总
  17. NeuroImage:对情绪表现的快速接近—回避反应反映了基于价值的决策:来自脑电图研究的神经证据
  18. 什么是理想的大学生活?
  19. 游戏评论之——戴森球计划
  20. 桌面环境与桌面搜索Desktop Search tools

热门文章

  1. 18个常用的JavaScript片段分享
  2. 零基础学习Java,全方位知识点总结!
  3. 台式电脑怎么改计算机名,台式电脑的设置在哪里
  4. python有框架吗_Python几种主流框架
  5. VTK:帧率用法实战
  6. wxWidgets:窗口 ID
  7. boost::range_reference相关的测试程序
  8. boost::program_options模块实现处理响应文件的测试程序
  9. boost::mp11::mp_remove_if相关用法的测试程序
  10. boost::iostreams::example::container_sink用法的测试程序