CPU 中的浮点数是如何存储的?

How are floating point numbers stored inside the CPU?

我是初学者,正在学习汇编基础知识。现在在看这件事的时候,我来到了这一段。它解释了浮点数是如何存储在内存中的。

The exponent for a float is an 8 bit field. To allow large numbers or small numbers to be stored, the exponent is interpreted as positive or negative. The actual exponent is the value of the 8 bit field minus 127. 127 is the "exponent bias" for 32 bit floating point numbers. The fraction field of a float holds a small surprise. Since 0.0 is defined as all bits set to 0, there is no need to worry about representing 0.0 as an exponent field equal to 127 and fraction field set to all O's. All other numbers have at least one 1 bit, so the IEEE 754 format uses an implicit 1 bit to save space. So if the fraction field is 00000000000000000000000, it is interpreted as 1 . 00000000000000000000000. This allows the fraction field to be effectively 24 bits. This is a clever trick made possible by making exponent fields of OxOO and OxFF special.

我完全不明白。

你能解释一下它们是如何存储在内存中的吗?我不需要参考文献,我只需要一个很好的解释,这样我就可以轻松理解。

浮点数在 the IEEE754 standard 之后。他们一直在使用这套规则,主要是因为浮点数可以(相对)容易地与整数和其他浮点数进行比较。

浮点有 2 个常见版本:32 位 (IEEE binary32 aka single-precision float) and 64bit (binary64 aka double precision)。它们之间的唯一区别是字段的大小:

  • 指数:32位8位,64位11位
  • 尾数:32位为23位,64位为52位

还有一个附加位,即符号位,用于指定所考虑的数字是正数还是负数。

现在,举个例子12,375 base 10 (32bit):

  • 第一步是将这个数字转换为 2 进制: 这很简单,经过一些计算你会得到:1100.011

  • 接下来你必须移动 "comma" 直到你得到 1.100011(直到 . 之前的唯一数字是 1)。我们将逗号移动了多少次? 3,即指数。这意味着我们的数字可以表示为1.100011*2^3。 (它不叫小数点,因为它是二进制的。它是 "radix point" 或 "binary point"。)

    移动 .(并用指数计算这些移动)直到尾数以前导 1. 开始称为 "normalizing"。太小而无法用这种方式表示的数(指数的有限范围)称为次正规数或非正规数。

  • 之后我们必须将偏差添加到指数中。对于 32 位浮点数中的 8 位指数字段,这是 127。我们为什么要这样做?答案是:因为通过这种方式我们可以更轻松地将浮点数与整数进行比较。 (将 FP 位模式作为整数进行比较会告诉您哪个具有更大的幅度,如果它们具有相同的符号。)此外,递增位模式(包括从尾数进位到指数)会将幅度增加到下一个可表示的值。 (nextafter())

    如果我们不这样做,负指数将使用两个补码表示法表示,本质上是将 1 放在最高有效位。但是通过这种方式,较小的浮点数似乎大于正指数浮点数。出于这个原因:我们只添加 127,这个小 "trick" 所有正指数从 10000000 底数 2(即 1 底数 10)开始,而负指数最多达到 01111110 底数 2(这是 -1 基数 10).

在我们的示例中,归一化指数是 10000010 底数 2。

  • 最后要做的是在指数后面加上尾数(.100011),结果是:

    01000001010001100000000000000000
     |  exp ||      mantix         |
    

(第一位为符号位)

有一个很好的在线转换器可以可视化 32 位浮点数的位,并显示它所代表的十进制数。您可以修改其中一个,它会更新另一个。 https://www.h-schmidt.net/FloatConverter/IEEE754.html


那是简单的版本,这是一个好的开始。它通过省略来简化:

  • 非数字 NaN(偏指数 = 全一;尾数 != 0)
  • +-无穷大(偏指数 = 全一;尾数 = 0)
  • 并没有多说次正规数(偏置指数 = 0 意味着尾数中的前导 0 而不是正常的 1)。

关于单精度和双精度的 Wikipedia 文章非常好,有图表和大量对极端情况和细节的解释。查看它们以获取完整的详细信息。

此外,一些(大多数是历史悠久的)计算机使用非 IEEE-754 的 FP 格式。

还有其他 IEEE-754 格式,例如 16 位半精度,一种值得注意的扩展精度格式是 80 位 x87,它显式存储有效数字的前导 1,而不是隐含的零或非零指数。

IEEE-754甚至定义了一些十进制浮点格式,使用10^exp来精确表示小数而不是二进制小数。 (硬件对这些的支持有限但确实存在)。

与小学数学没什么不同。我们在小学时就学会了先做正整数加减。然后我们学会了制作一个水平划痕,代表减号并表示负数,并了解了数字线,不,我们可以去负数。因此,是否存在负号(或负号与加号)表示单个数字是正数还是负数。它只需要二进制中的一位来表示我是消极的还是积极的。那是 the/this 浮点格式(或任何其他格式)中的 "sign" 位。

然后在小学的某个时候,我们在做了一段时间的分数后学习了小数点。那只是我们在两个数字之间放置的一个句点,它表示最后一个整数的位置和分数的开始位置。我可以就此打住,说没有任何理由让 2 进制不同于 10 进制、13 进制和 27 进制,你只需在两个数字之间放一个句点来指示最后一个整数和分数的第一部分在哪里.但是浮点数更进一步。现在这可能是在小学或后来的中学,但他们最终教会了我们科学记数法 and/or 通过移动小数点来表示数字的其他方式,小数点仍然代表最后一个整数之间的边界数字和分数的开头,但在旁边我们有一个乘以基数的幂

12345.67 = 1.234567 * 10^4

这就是难题的剩余部分。使用铅笔和纸,只要我们有足够的纸和足够的铅笔芯(石墨),我们就可以写出我们想要的数字,但是正如您已经知道的那样,我们通常会受到寄存器大小的限制,现在我们可以使用其他小学知识将 8 位 alu 变成无限多位 alu(只要我们有足够的 memory/storage 位)但我们仍然一次处理 8 位的东西案件。在这种情况下,他们最初选择了 32、64 和 80 位(或者可能是后来出现的)格式,因此我们的位严格受限于这些数字(我们现在有 16 位,可能更小,尽管这没有多大意义),并且他们使用某次基数作为幂指数。 something 是上面的尾数 1.234567 但没有小数点存储 1234567 小数点的位置在 assumed/agreed 上(已知)。它是数字中的第一个非零数字,因此我们将 123456.7 移至 1.234567 并将指数调整为 78.45 我们将其移至 7.845 并调整基数乘数的指数。由于这是二进制,因此只有一个值不是零,而是一个(一位是 0 或 1),因此我们将 011101.000 移至 1.110100 并调整指数。 (这类似于科学记数法,但基数为 2)

接下来,这个尾数中的位数或科学记数法中的有效数字,如果你想这样想的话,限制在 23 或一些位数之内,请参阅单精度浮点格式的维基百科页面(32 位,双精度是 64 位,工作方式完全相同,只是有更多的尾数和指数位)。所以我们取我们的数字,不管它有多少位我们找到最重要的一位,我们把小数点移到那里并调整乘数的指数就像我们上面做的那样

11101.01 = 1.110101 * 2^4

我们技术上不需要存储小数点前的 1,也不需要存储 2,但我们需要存储 110101,我们需要以二进制形式存储 4。除了上面例子中的符号表示正数,所以符号、指数和尾数我们可以重建这个数字。或者任何符合不是非常小或非常大的数字(这样指数就不会适合分配的位数)。

然后 IEEE-754 人员采取了最后一步,而不是仅仅编码指数,他们使用某种向后的二进制补码。我们已经从计算机上的整数数学中了解了二进制补码以及如何理解这些数字的样子。由于某种原因,他们并没有完全这样做,这本来会更有意义,但是他们宣布 1000...0000 二进制中全为零的一个是中点的定义或另一种看待它的方式就是全部zeros 是最小的指数,all ones 是最大的指数,你必须调整它。我们从一个 8 位数字的二进制补码中知道,在这种情况下,最大的数字是 +127,最小的是 -128,他们所做的是改变这个,这样他们就可以有一个更大的正指数,而不是像二进制补码中的 +127 到 -128这是向后它是 +128 到 -127,对我们来说它只是意味着我们通过添加 127 来调整。在我上面的例子中,2 的 4 次方,4 的二进制是 100,使用 8 位二进制补码,即 00000100 到 "encode" 它变成单精度 IEEE 754 浮点格式,变成 10000011 我只是加 127 或加 128 (10000100) 然后减一。

所以我撒谎还有一些事情,特殊情况,到目前为止我们有一位用于符号是正数还是负数,8 位用于 2 乘数的编码指数,我们有尾数或我们数字中有效数字的小数位。但是零呢,零中没有非零位,我们如何表示该数字?好吧,这是一种特殊情况,几乎是硬编码的数字,但实际上您可以在格式中表示具有不同位模式的 +0 和 -0,但我认为规范的更高版本鼓励或规定结果为零的数学是正数,但是我不确定,我肯定已经很多年没有看到规范的副本了,因为你必须付费才能合法获得它。其他特殊情况称为 NaN 或不是数字它们也是已知表示 NaN 的特殊位模式......并且有不止一个 nan,因为您可以在尾数中放置不同的模式。例如,当您除以零时,或者当您的数字太大以至于您无法用数字乘以 2 的 N 次方来表示时,因为 N 对于指数中编码的位数来说太大了(更大比单精度编码前的 +128)或数字太小(指数小于 -127)。尽管在某些格式中有称为微小数或非正规数的数字,但那些不是 1.xxxx 的数字,但它们让那个滑倒了,并且有 0.000...1xxxx 这是一种无效格式,但只比最小的小一点点我们可以表示的数字,有些 fpus/software 不支持非正规数。

现在去维基百科搜索单精度浮点格式,现在该页面应该很有意义...我希望...