msforest / notebook

好记性不如烂笔头,记录知识的点点滴滴。
https://github.com/msforest/notebook/wiki
0 stars 0 forks source link

二进制浮点数的存储解读 #35

Open msforest opened 5 years ago

msforest commented 5 years ago

参考:

image

ECMAScript文档写明,数字类型遵循IEEE 754标准的64位双精度格式存储。这种类型用于存储整数和分数,等同于Java和C中的double数据类型。JavaScript的一些新开发人员没有意识到这一点,并且相信如果他们使用1,它将以64位存储为:

0000000000000000000000000000000000000000000000000000000000000001

实际上存储形式是这样的:

0011111111110000000000000000000000000000000000000000000000000000

继续深入,了解为什么存储的格式与想象的不一样。

科学计数法,小学的时候学过,忘的差不多,回顾一下。 根据wiki定义,有效位数取值范围为0<=|significant|<base,因此正确表示为 image

significant表示有效位数,也被称为mantissa或precision。base表示统计的基数。exponent表示小数点向左向右移多少位,也叫指数。

任何数都可以用科学计数法来表示。如十进制和二进制系统中的数2可以表示为: image

然后看一个小数如何表示: image

指数大于0,表示小数点右移;指数小于0,表示小数点左移。

科学计数法可以用来表示数字的浮点数。进一步理解为浮点数就是小数点的移位。

IEEE754定义了两种精度存储方式——单(32)精度/双(64)精度 image

Javascript使用64位存储数字,下面是它如何以JavaScript的Number类型使用的双精度格式(每个数字为64位)分配这些位: image

符号位占1位,指数占11位,52位分配给尾数(有效数字)

1的浮点数

1的科学计数法表示如下: image

有效位是1,指数是0,可以得出如下表示: 0 00000000000 0000000000000000000000000000000000000000000000000001

然而,我们如何计算实际存储的格式呢?网上搜到一种方法可以计算出浮点数的存储格式:

function to64bitFloat(number) {
    var i, result = "";
    var dv = new DataView(new ArrayBuffer(8));

    dv.setFloat64(0, number, false);

    for (i = 0; i < 8; i++) {
        var bits = dv.getUint8(i).toString(2);
        if (bits.length < 8) {
            bits = new Array(8 - bits.length).fill('0').join("") + bits;
        }
        result += bits;
    }
    return result;
}

打印结果得到: 0 01111111111 0000000000000000000000000000000000000000000000000000 尾数占位符没有数字,指数中存在1。大大的疑问??

二进制中最高有效位始终为1,所以不存储,在执行数学运算时,第一个数字1由硬件前置。数字1在标准化形式的小数点之后没有数字,并且没有存储小数点之前的第一个数字,因此我们没有任何东西可以放入尾数,因此它全部为零。 指数存储是通过位偏移计算,得到的结果: image

因此就计算出了指数占位符中的数。

3的浮点数

3的二进制是11,科学计数法如下: image

小数点后只有一个1需要存储,小数点前的1不存储;然后根据指数的位偏移计算指数占位符: image

关于尾数的一件事要注意,数字按照它们以科学形式放置的确切顺序存储 - 从小数点开始从左到右。考虑到这一点,让我们将所有数字放在浮点表示中: image

0.1+0.2 !== 0.3

现在再来看看0.1加0.2为什么不等于0.3

0.1和0.2的浮点数

根据二进制转换规则,0.1是一个无线循环的小数: image

用科学计数法表示如下: image

由于尾数只能有52位,我们需要在小数点后将无限数舍入为52位。 image image

指数的计算根据位偏移得出: image

因此0.1的存储形式如下: image

同样的方法,0.2的存储形式为: image

0.1/0.2的科学计数法表示如下: image

计算两个数相加,需要使得指数相同。调整后的0.1如下: image

然后两数相加: image

现在,计算结果以浮点格式存储,因此我们需要对结果进行标准化,必要时进行舍入并计算偏移二进制的指数。 image

当转换为浮点格式进行存储时,它具有以下位模式: image

这正是执行语句0.1 + 0.2时存储的位模式。为了得到它,计算机必须绕三次 - 每个数字一个,第三次总和。当存储简单的0.3时,计算机仅执行一次舍入。这种舍入操作导致存储0.1 + 0.2和独立0.3的不同位模式。当JavaScript执行比较语句0.1 + 0.2 === 0.3时,它们的这些位模式被比较,并且由于它们不同,返回的结果是假的。

NaN/Infinity

这两个指数是1024,不等于Number.MAX_VALUE(指数是1023) NaN的存储形式为: image

Infinity是浮点数的另一个特例,用于处理溢出和一些数学运算,如1/0。无穷大用指数中的所有1和尾数中的全零表示 image