zoukankan      html  css  js  c++  java
  • 玉伯的一道课后题题解(关于 IEEE 754 双精度浮点型精度损失)

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

    题目是这样的:

    复制代码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_VALUE 和 Number.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_INTEGER 和 Number.MAX_SAFE_INTEGER 来表示最小以及最大能精确表示的数,并能用 Number.isSafeInteger() 来检查一个数是否能精确表示。

  • 相关阅读:
    新经资讯项目业务逻辑梳理
    HTTP状态保持的原理
    CSRF的原理和防范措施
    装饰器路由具体实现梳理
    Flask中异常捕获
    正则匹配路由
    (搬运以学习)flask 上下文的实现
    flask之请求钩子
    如何在linux中创建虚拟环境
    面包屑导航
  • 原文地址:https://www.cnblogs.com/zhangyuhang3/p/6873308.html
Copyright © 2011-2022 走看看