前文 的最后给出了玉伯的一道课后题,今天我们来讲讲这题的思路。

题目是这样的:

Number.MAX_VALUE + 1 == Number.MAX_VALUE;
Number.MAX_VALUE + 2 == Number.MAX_VALUE;
...
Number.MAX_VALUE + x == Number.MAX_VALUE;
Number.MAX_VALUE + x + 1 == Infinity;
...
Number.MAX_VALUE + Number.MAX_VALUE == Infinity;

// 问题:
// 1. x 的值是什么?
// 2. Infinity - Number.MAX_VALUE == x + 1; 是 true 还是 false ?

如果考虑浮点数的精度问题,那么 x 无解。推理很简单,根据 Number.MAX_VALUE + x == Number.MAX_VALUENumber.MAX_VALUE + x + 1 == Infinity 可以推得 Number.MAX_VALUE + 1 == Infinity ,显然这是不成立的,所以 x 无解。

但是显然玉伯这里不希望我们考虑精度损失问题。

我们先来看 Number.MAX_VALUE,在前面的文章中我们已经给出了它的值为:

1 * (Math.pow(2, 53) - 1) * Math.pow(2, 971) = 1.7976931348623157e+308

我们用二进制可以这样表示:

1 1 1 1 .. (53个1) .. 1 1 1   0 0 0 0 .. (971个0) .. 0 0 0 0

53 个 1 的 第一位是隐藏位(hidden bit),后 52 位即为 m(参照 前文 中对 m 的解释,e 同),而 971 个 0 即代表了指数 e(当然实际存储中 e 并不是这样表示的)。

接着我们把这个数加上 1,如果我们把这个数用二进制表示,可以表示为:

1 1 1 1 .. 53个1 .. 1 1 1   0 0 0 0 .. 970个0 .. 0 0 0 1

如果我们把该数用 IEEE 754 双精度浮点型表示出来,m 并不会有什么变化,因为 m 控制了这个数的精度,m 只能有 52 位,而加上的 1 对于这个大数字来说实在太微不足道!结果还是 Number.MAX_VALUE。

那么加上个什么数之后会引起质变呢?答案是 2 ^ 970,因为加上这个数之后,我们用二进制表示:

1 1 1 1 .. 53个1 .. 1 1 1   1 0 0 0 .. 970个0 .. 0 0 0 0

而该数用 IEEE 754 双精度浮点型表示时,由于 第 52 位 和 53 位同时为 1,所以会进位(Ties To Even),于是这个数变为了 Infinity。

那么 Number.MAX_VALUE + x == Number.MAX_VALUE 的解集似乎就呼之欲出了,x 的范围是 [0, 2 ^ 970),即[0, 2 ^ 970 - 1](如果考虑精度丢失的话,这个解集会是 [0, 2 ^ 970 - 2 ^ (970 - 53)])。

于是问题演变成 Number.MAX_VALUE + y == Infinity,而 y 的取值是 [1, 2 ^ 970],求 y。

前面已经说了当 y 为 2 ^ 970 时才会发生质变,所以可求得 x 为 2 ^ 970 - 1。如果把 Number.MAX_VALUE 用 0xfffffffffffff8000... 来表示的话,那么这个数可以用 0x0000000000003ffff... 来表示。

问题 2 就很简单了,因为 Infinity - Number.MAX_VALUE 还是等于 Infinity,而 x + 1 还是等于 x,显然不会达到 Infinity。

我们可以看下玉伯在评论区给出的答复:

Number.MAX_VALUE.toString(16) = ”
fffffffffffff800000000000000000000000000000
00000000000000000000000000000000000000
00000000000000000000000000000000000000
00000000000000000000000000000000000000
00000000000000000000000000000000000000
00000000000000000000000000000000000000
00000000000000000000000″

前面有 13 个 f, 二进制就是 52 个 1
还有一个 8, 二进制是 1000
也就是说,前面 53 位都是 1

这样,当 Number.MAX_VALUE + 1 时,1 替代最后一个 0,但 IEEE 754 双精度浮点数的 m 最大为 53(含隐藏位),因此添加的 1 在存储时会被舍弃掉,所以:

Number.MAX_VALUE + 1 == Number.MAX_VALUE

同理类推,当 8(1000) 变成 b(1011),b 后面的位取最大值时,依旧有:

0xfffffffffffffbfffffffffffffffffffffffffffffffffffff
fffffffffffffffffffffffffffffffffffffffffffffffffffffff
fffffffffffffffffffffffffffffffffffffffffffffffffffffff
fffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffff == Number.MAX_VALUE

进一步,当 再增 1, b 变成 c 时,将发生质变:

0xfffffffffffffc00000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000 == Infinity

这是因为前面将有 54 个连续的 1, 在存储时,exponent 将由
971 变成 972, 超出了 IEEE 754 双精度浮点数存储格式中 e 的
最大值,因此质变为 Infinity 了。

这样,题目中 x 的值就很容易得到了:

x = 0xfffffffffffffbffff… – 0xfffffffffffff80000…
= 0x00000000000003ffff…

注意这个数在IEEE 754 双精度浮点数格式下无法精确存储。

还能得到两个有趣的结论:

1. Number.MAX_VALUE 不是一个数,而是一个区间 [0xfffffffffffff80000…, 0xfffffffffffffc0000…)
2. Infinity 指的是,所有大于等于 0xfffffffffffffc0000… 的数。

最后我们再总结几条有趣的规律:

  • Javascript 能精确保存的最大整数为 2 ^ 53,即为 9007199254740992,当 x 大于等于 9007199254740992时,x === x + 1。Javascript 能精确表示的整数的范围是 [- 2 ^ 53, 2 ^ 53]
  • Number.MAX_VALUE 不是一个数,而是一个区间 [0xfffffffffffff80000…, 0xfffffffffffffc0000…) 任何大于等于 9007199254740992 的数都是一个区间,没有丢失精度的数只是区间中的一个数。"我不是一个数,是一堆数⋯⋯"
  • Infinity 指的是,所有大于等于 0xfffffffffffffc0000… 的数

2015-12-25 update:

Javascript 能精确表示的数的范围是 (-2^53, 2^53),而不是文中所说的 [-2^53, 2^53]。这里能精确表示的意思是,只有一个数能对应该数。

为什么 2^53 不能精确表示? 2^53=9007199254740992,而在 Javascript 中 9007199254740993 也表示成 9007199254740992,所以如果在运算中出现 9007199254740992,是无法确定原数的(两种可能)。

我们可以用 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 来表示最小以及最大能精确表示的数,并能用 Number.isSafeInteger() 来检查一个数是否能精确表示。

玉伯的一道课后题题解(关于 IEEE 754 双精度浮点型精度损失)的更多相关文章

  1. 又一道简单题&&Ladygod(两道思维水题)

    Ladygod Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit S ...

  2. POJ-1200 Crazy Search,人生第一道hash题!

                                                        Crazy Search 真是不容易啊,人生第一道hash题竟然是搜博客看题解来的. 题意:给你 ...

  3. Codeforces Round #524 (Div. 2)(前三题题解)

    这场比赛手速场+数学场,像我这样读题都读不大懂的蒟蒻表示呵呵呵. 第四题搞了半天,大概想出来了,但来不及(中途家里网炸了)查错,于是我交了两次丢了100分.幸亏这次没有掉rating. 比赛传送门:h ...

  4. Codeforces Round #519 by Botan Investments(前五题题解)

    开个新号打打codeforces(以前那号玩废了),结果就遇到了这么难一套.touristD题用了map,被卡掉了(其实是对cf的评测机过分自信),G题没过, 700多行代码,码力惊人.关键是这次to ...

  5. 2019山东ACM省赛L题题解(FLOYD传递闭包的变形)

    本题地址 https://cn.vjudge.net/contest/302014#problem/L Median Time Limit: 1 Second      Memory Limit: 6 ...

  6. Java程序设计(2021春)——第四章接口与多态课后题(选择题+编程题)答案与详解

    Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第四章接口与多态课后题(选择题+编程题)答案与详解 第四章选择题 4.0 ...

  7. Sea.js创始人玉伯的前端开发之路

    在Web应用程序的用户体验越来越被重视的今天,前端开发的地位也上升到了前所未有的高度,而随之而来的也有更多的挑战. 为了将前端开发者繁重的工作变得简单,框架应运而生.国内也不乏一些非常优秀的前端开发框 ...

  8. 【开源专访】Sea.js创始人玉伯的前端开发之路

    摘要:玉伯,淘宝前端类库 KISSY.前端模块化开发框架SeaJS.前端基础类库Arale的创始人.本期[开源专访]我们邀请玉伯来为我们分享一些关于前端框架.前端开发的那些事,以及前端大牛是如何炼成的 ...

  9. 一道js题

    <script> var a = 5; function test(){ this.a = 10; a = 15 this.func = function(){ var a = 20 ; ...

随机推荐

  1. R语言中的循环函数(Grouping Function)

    R语言中有几个常用的函数,可以按组对数据进行处理,apply, lapply, sapply, tapply, mapply,等.这几个函数功能有些类似,下面介绍下这几个函数的用法. Apply 这是 ...

  2. MySQL 优化之 index merge(索引合并)

    深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...

  3. 批处理脚本为Mysql重置root密码(重置密码为123456)

    @echo off title mysql ::从注册表找到Mysql的安装路径写入文件mysql.txt reg query HKLM\SYSTEM\ControlSet001\Services\M ...

  4. 使用SQL语句创建SQL数据脚本(应对万网主机部分不支持导出备份数据)

    1.查询待导出表Ad中的数据. SELECT * FROM [DB_Temp].[dbo].[Ad] 2.编写存储过程. --将表数据生成SQL脚本的存储过程 CREATE PROCEDURE dbo ...

  5. Linux文件和目录

    access() //检查是否调用进程有Access这个文件的权限,如果文件是一个符号链接,会将它解引用,成功返回0,失败返回-1设errno #include <unistd.h> in ...

  6. linux批量删除进程

    在虚拟机用脚本跑了几十个client程序用来测试服务器,然后发现参数设置错误,得重来,就傻眼了,不知道怎么关这么多client进程,总不能一个一个关.还好,学习一下,想出了以下的命令.   ps -e ...

  7. [转]OnKeyDown Numeric Validator CLIENT SIDE

    本文转自:http://forums.asp.net/t/1211724.aspx?OnKeyDown+Numeric+Validator+CLIENT+SIDE <!DOCTYPE html ...

  8. 翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器

    原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...

  9. 创建MyOffice项目

    创建查看评分窗体(FrmLOOK),添加定义成员数组,将员工数据绑定到FrmLOOK窗体的ListView控件上 public ListViewItem lv; private void Form1_ ...

  10. 强加密RNGCryptoServiceProvider

    .net RNGCryptoServiceProvider 对应 java SecureRandom public class SecureRandomextends Random  This cla ...