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

题目是这样的:

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. 查看ORACLE的实际执行计划

    ORACLE的执行计划分为预估执行计划和实际执行计划.其中,你用Toad.PL/SQL Developer.SQL Developer.EXPLAIN PLAN FOR或者SET ATUOTRACE ...

  2. mysql访问连接过多

    今天开发中启动服务器,发现不管怎么样都会报连接池已满,随后删除数据库中的连接,发现可以启动,后来关闭后重新启动又出现连接池已满的错误.后监控数据库发现,当我关闭服务的时候,数据库的连接并没有关闭,随后 ...

  3. (1)编写一个接口ShapePara,要求: 接口中的方法: int getArea():获得图形的面积。int getCircumference():获得图形的周长 (2)编写一个圆类Circle,要求:圆类Circle实现接口ShapePara。 该类包含有成员变量: radius:public 修饰的double类型radius,表示圆的半径。 x:private修饰的double型变量x,

    package com.hanqi.test; //创建接口 public interface ShapePara { //获取面积的方法 double getArea(); //获取周长的方法 do ...

  4. 0008《SQL必知必会》笔记04-子查询、联接与组合查询

    1.子查询:就是嵌套的查询,用一个查询的结果作为另一个查询的条件. 比如要列出订购了物品“RGAN01”的所有顾客的ID.姓名和联系人,需要经历以下几步 (1)从orderitems中找出订购了“RG ...

  5. jQuery简单入门(四)

    4.表单应用 表单是HTML的重要组成部分,在采集.提交用户输入的信息和显示列表数据等需求中有重要作用 表单应用 一个简单的表单HTML示例: <form action=”url” method ...

  6. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作(二)

    CSSDesk body { background-color: #2574b0; } /*! zybuluo */ article,aside,details,figcaption,figure,f ...

  7. 二:Go编程语言规范-类型

    1.类型 布尔值,数值与字符串类型的实例的命名是预声明的. 数组,结构,指针,函数,接口,切片,映射和信道这些复合类型可由类型字面构造. 每个类型 T 都有一个 基本类型:若 T 为预声明类型或类型字 ...

  8. 动手学习TCP:总结和索引

    TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...

  9. css3中变形与动画(三)

    transform可以实现矩阵变换,transition实现属性的平滑过渡,animation意思是动画,动漫,这个属性才和真正意义的一帧一帧的动画相关.本文就介绍animation属性. anima ...

  10. Lucene TF-IDF 相关性算分公式(转)

    Lucene在进行关键词查询的时候,默认用TF-IDF算法来计算关键词和文档的相关性,用这个数据排序 TF:词频,IDF:逆向文档频率,TF-IDF是一种统计方法,或者被称为向量空间模型,名字听起来很 ...