浮点数的“偏置值”是什么?

70

在学习计算机中如何表示浮点数时,我遇到了一个不太理解的术语“偏置值”。

浮点数中的偏置值与指数部分的正负有关。

浮点数的偏置值为127,也就是说,将127始终加到浮点数的指数部分。这样做有什么帮助来确定指数是否为负数或正数吗?


另一个与此问题相关的有趣阅读是这篇维基百科文章:https://en.wikipedia.org/wiki/IEEE_754-1985 - nbro
4个回答

94
b0lt已经解释了偏差是如何工作的。猜测一下,也许您想知道为什么在这里使用偏差表示,即使现代计算机几乎在所有其他地方都使用二进制补码(即使不使用二进制补码的机器,也使用反码或原码,而不是偏差)。

IEEE浮点标准的目标之一是,您可以将浮点数的位视为相同大小的(有符号)整数,如果以这种方式进行比较,则值将按与它们所表示的浮点数相同的顺序排序。

如果对指数使用二进制补码表示,则小的正数(即具有负指数的数)看起来像非常大的整数,因为第二个MSB会被设置。通过改用偏差表示,您就不会遇到这种情况——浮点数中的较小指数始终看起来像更小的整数。

顺便说一句,这也是为什么浮点数通常按符号、指数和最不重要的有效数字的顺序排列的原因——这样,您可以将正浮点数作为整数处理并对其进行排序。这样做时,结果将按正确的顺序包含浮点数。例如:

#include <vector>
#include <algorithm>
#include <iostream>

int main() { 
    // some arbitrary floating point values
    std::vector<double> vals = { 1e21, 1, 2.2, 2, 123, 1.1, 0.0001, 3, 17 };
    std::vector<long long> ivals;

    // Take those floating point values, and treat the bits as integers:
    for (auto &&v : vals) 
        ivals.push_back(*reinterpret_cast<long long *>(&v));

    // Sort them as integers:
    std::sort(ivals.begin(), ivals.end());

    // Print out both the integers and the floating point value those bits represent:
    for (auto &&i : ivals) 
        std::cout << i << "\t(" << *reinterpret_cast<double *>(&i) << ")\n";
}

当我们运行这个程序时,结果看起来是这样的:
4547007122018943789     (0.0001)
4607182418800017408     (1)
4607632778762754458     (1.1)
4611686018427387904     (2)
4612136378390124954     (2.2)
4613937818241073152     (3)
4625478292286210048     (17)
4638355772470722560     (123)
4921056587992461136     (1e+21)

如您所见,即使我们将它们按整数排序,那些位表示的浮点数也会以正确的顺序出现。

但是,在处理浮点数时存在限制。虽然所有(非古老的)计算机都同意正数的表示方式,但最近有三种表示负数的方法:符号大小、反码和补码。

对于使用符号大小表示整数的计算机,只需将位视为整数并进行比较即可正常工作。对于使用反码或补码的计算机,负数将以相反的顺序排序。由于这仍然是一个简单的规则,编写能够运行的代码相当容易。如果我们将上述的 sort 调用更改为以下内容:

std::sort(ivals.begin(), ivals.end(),
    [](auto a, auto b) { if (a < 0.0 && b < 0.0) return b < a; return a < b; }
);

如果使用正确的代码,它将正确地对正数和负数进行排序。例如,输入:

std::vector<double> vals = { 1e21, 1, 2.2, 2, 123, 1.1, 0.0001, 3, 17, -0.001, -0.00101, -1e22 };

将会产生以下结果:

-4287162073302051438    (-1e+22)
-4661071411077222194    (-0.00101)
-4661117527937406468    (-0.001)
4547007122018943789     (0.0001)
4607182418800017408     (1)
4607632778762754458     (1.1)
4611686018427387904     (2)
4612136378390124954     (2.2)
4613937818241073152     (3)
4625478292286210048     (17)
4638355772470722560     (123)
4921056587992461136     (1e+21)

是的,我也想知道为什么。这回答了我的问题。非常有趣。谢谢。 - mudgen
4
一个很好的答案(特别是浮点数组成部分顺序背后的原理)。 - legends2k
1
@JerryCoffin 我不明白为什么浮点数可以被视为整数...我的意思是,相同的位序将为每种数据类型保留不同的值。你能详细解释一下吗? - user1534664
2
@user1534664:我已经进行了一些编辑,以希望澄清上一个观点。 - Jerry Coffin
@JerryCoffin 太棒了,我现在明白你所说的“ordering”是什么意思了 :) - user1534664
1
这是一个很好的答案。它也有点向你展示了为什么字节序在过去可能对速度很重要,而现在字节序只是过去遗留下来的文物。非常微妙的观点,但它们以如此之大的方式影响了计算机。 - in70x

80

在单精度浮点数中,你有8位来存储指数。与其把它存储为有符号的二进制补码数,人们决定将127加到指数上(因为在8位有符号数中最小值为-127),然后将其存储为无符号数。如果存储的值大于该偏置,则意味着指数的值为正数;如果小于该偏置,则为负数;如果相等,则为零。


1
那么在IEEE格式中,由于127的偏置,如果指数字段为“00000000”,这意味着该数字的指数为-127? - Kenny Worden
1
@KennethWorden 不,指数值全为零是特殊的,数字被解释为“非规格化”(包括0本身)。 - Chris Gregg
8
为什么最低的8位有符号数是-127?难道不应该是-128吗?n位有符号整数的范围应该是-2^(n-1)到2^(n-1)-1。 - Jimmy Suh
2
@JimmySuh,更直观的方法是将指数位看作一个8位无符号整数:255 保留为 Inf/NaN,254 映射到 127,...,1 映射到 -126,0 保留为 0/denorms(可视化工具:https://www.h-schmidt.net/FloatConverter/IEEE754.html)。 - Matthias

20

在上面的回答中添加更多细节。

为了表示浮点数中的0无穷NaN(非数字),IEEE决定使用特殊的编码值。

  • 如果指数字段的所有位都设置为0,则浮点数为0.0。

  • 如果指数字段的所有位都设置为1,而分数部分的所有位都为0,则浮点数为无穷大

  • 如果指数字段的所有位都设置为1,而分数部分的所有位都不等于0,则浮点数为NaN

因此,在单精度中,我们有8个比特来表示指数字段,并且有2个特殊值,因此基本上有256-2=254个可以表示的指数值。因此,我们可以有效地表示-126到127之间的指数,即254个值(126+127+1),其中1是加上的0。


3
为了解释您的困惑:指数看起来会变成负数,是由于偏差(bias)。如果您在指数范围中看到一个二进制值为+125,当您"去偏差"后,实际指数值就是-2。这可能是因为在这种情况下,要有所"偏差"意味着要减去127。但有时候,即使减去127,指数仍然会保持正数。如果您只看位(bit)的话:

[0][01111111][00000000000000000000000]代表数字0!即使您看到所有那些1。这些例子是针对使用IEEE 754标准的处理器的单精度(32位)浮点数。使用此标准时,存储的值如下所示:

[符号位][偏差指数][尾数] 符号位用于浮点数的小数部分,而不是指数部分。要使这些数字看起来更自然,例如类似于数学课堂上看到的1.01x2^5,您需要将眼睛移到符号位和指数。顺便说一句,1.01x2^5被认为是一个"普通"的数字,因为在二进制点左边只有1个数字,当然,这种科学计数法乘以2而不是10,因为我们使用的是基数2,这使得移动二进制点变得轻松!

让我们来看一个例子,例如十进制数0.15625,首先我将视觉上移动指数:

                                                    
----------------------------------(exponent)
0 01111100 01000000000000000000000   ^
--+------+-+-----------------------  |
  |      |                           |
  +------+                           |
subtract 127 here                    |
      |                              |
      v                              |
      ---------------->--------------  

这里的指数为124,所以减去127得到-3。现在记住隐含的1,因此你将拥有1.01000000000000000000000。忘记所有这些零:1.01x2^-3 就是二进制数字 0.001010。还要记住,第一个比特位是零,因此“最终化”数字为正0.15625。如果我们从 1 01111100 01000000000000000000000 开始,我们可以很容易地得到-0.15625。

这里是上面提到的那些特殊情况,是的,有正无穷大和负无穷大:

                       
     31                               
     |                                
     | 30    23 22                    0
     | |      | |                     |
-----+-+------+-+---------------------+
qnan 0 11111111 10000000000000000000000
snan 0 11111111 01000000000000000000000
 inf 0 11111111 00000000000000000000000
-inf 1 11111111 00000000000000000000000
-----+-+------+-+---------------------+
     | |      | |                     |
     | +------+ +---------------------+
     |    |               |
     |    v               v
     | exponent        fraction
     |
     v
     sign

我在Intel手册中找到了这些内容,具体是在四卷套装的第91页的表格4-3中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接