• 玉伯的一道课后题题解(关于 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() 来检查一个数是否能精确表示。

  • 相关阅读:
    The Quad
    将OrCAD Capture CIS的设计文件(.dsn)导入到PADS Logic VX.2.3
    OrCAD Capture CIS 16.6 将版本16.6的设计文件另存为版本16.2的设计文件
    Eclipse IDE 添加jar包到Java工程中
    PADS Logic VX.2.3 修改软件界面语言
    切换Allegro PCB Editor
    Allegro PCB Design GXL (legacy) 将brd文件另存为低版本文件
    Allegro PCB Design GXL (legacy) 设置自动保存brd文件
    Could not create an acl object: Role '16'
    windows 下apache开启FastCGI
  • 原文地址:https://www.cnblogs.com/zhangyuhang3/p/6873308.html
Copyright © 2020-2023  润新知