当将整数转换为浮点数时,后台会发生什么?

6

我不太理解如何将整数转换为浮点数,能否逐步解释一下?假设我有一个带符号的二进制整数,我想手动将其转换为浮点数,但我不知道如何操作。请问是否可以逐步演示一下如何进行这种转换?

我经常在C语言中进行这种转换,例如:

  int a = foo ( );
  float f = ( float ) a ;

但是,我还没有弄清楚背后发生了什么。此外,为了更好地理解,我想手动进行转换。

编辑:如果您对转换非常了解,您还可以提供有关从浮点数到双精度浮点数转换的信息。此外,对于从浮点数到整数的转换也是如此。


我了解浮点数算术以及它在任何计算机中的存储方式。但是,转换是我认为不同的概念。 - user478571
2个回答

19

浮点数值(至少是IEEE754的)基本上有三个组成部分:

  • 符号s
  • 一系列指数位e;和
  • 一系列尾数位m

精度决定了可以用于指数和尾数的位数。让我们来看一下单精度浮点数的值0.1:

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm    1/n
0 01111011 10011001100110011001101
           ||||||||||||||||||||||+- 8388608
           |||||||||||||||||||||+-- 4194304
           ||||||||||||||||||||+--- 2097152
           |||||||||||||||||||+---- 1048576
           ||||||||||||||||||+-----  524288
           |||||||||||||||||+------  262144
           ||||||||||||||||+-------  131072
           |||||||||||||||+--------   65536
           ||||||||||||||+---------   32768
           |||||||||||||+----------   16384
           ||||||||||||+-----------    8192
           |||||||||||+------------    4096
           ||||||||||+-------------    2048
           |||||||||+--------------    1024
           ||||||||+---------------     512
           |||||||+----------------     256
           ||||||+-----------------     128
           |||||+------------------      64
           ||||+-------------------      32
           |||+--------------------      16
           ||+---------------------       8
           |+----------------------       4
           +-----------------------       2

符号是正的,很容易理解。

指数为64+32+16+8+2+1 = 123 - 127 bias = -4,因此乘数为2-41/16。偏置存在是为了使您可以得到非常小(例如10-30)和非常大的数。

尾数很大。它由1(隐式基数)加上(对于那些每个位都值得1/(2n)的位,其中n1开始增加到右边),{1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}

当您将它们全部相加时,您会得到1.60000002384185791015625

当您将其乘以2-4乘数时,您将得到0.100000001490116119384765625,这就是为什么他们说您无法精确表示IEEE754浮点数的0.1

关于将整数转换为浮点数,如果您在尾数中有与整数位模式相同数量的位数(包括隐式的1),则可以将整数位模式传输并选择正确的指数。不会失去精度。例如,双精度IEEE754(64位,其中52/53为尾数)没有问题接受32位整数。

如果整数位数更多(例如32位整数和32位单精度浮点数,其仅具有23/24位尾数),则需要缩放整数。

这涉及剥离最低有效位(实际上是舍入),以使其适合于尾数位。当然这会导致精度损失,但无法避免。


让我们看一个具体值123456789。以下程序转储了每种数据类型的位。

#include <stdio.h>

static void dumpBits (char *desc, unsigned char *addr, size_t sz) {
    unsigned char mask;
    printf ("%s:\n  ", desc);
    while (sz-- != 0) {
        putchar (' ');
        for (mask = 0x80; mask > 0; mask >>= 1, addr++)
            if (((addr[sz]) & mask) == 0)
                putchar ('0');
            else
                putchar ('1');
    }
    putchar ('\n');
}

int main (void) {
    int intNum = 123456789;
    float fltNum = intNum;
    double dblNum = intNum;

    printf ("%d %f %f\n",intNum, fltNum, dblNum);
    dumpBits ("Integer", (unsigned char *)(&intNum), sizeof (int));
    dumpBits ("Float", (unsigned char *)(&fltNum), sizeof (float));
    dumpBits ("Double", (unsigned char *)(&dblNum), sizeof (double));

    return 0;
}

我的系统输出如下:

123456789 123456792.000000 123456789.000000
integer:
   00000111 01011011 11001101 00010101
float:
   01001100 11101011 01111001 10100011
double:
   01000001 10011101 01101111 00110100 01010100 00000000 00000000 00000000

我们将逐一查看这些内容。首先是整数,即2的幂次方:

   00000111 01011011 11001101 00010101
        |||  | || || ||  || |    | | +->          1
        |||  | || || ||  || |    | +--->          4
        |||  | || || ||  || |    +----->         16
        |||  | || || ||  || +---------->        256
        |||  | || || ||  |+------------>       1024
        |||  | || || ||  +------------->       2048
        |||  | || || |+---------------->      16384
        |||  | || || +----------------->      32768
        |||  | || |+------------------->      65536
        |||  | || +-------------------->     131072
        |||  | |+---------------------->     524288
        |||  | +----------------------->    1048576
        |||  +------------------------->    4194304
        ||+---------------------------->   16777216
        |+----------------------------->   33554432
        +------------------------------>   67108864
                                         ==========
                                          123456789

现在让我们来看一下单精度浮点数。请注意尾数的位模式与整数的近乎完美匹配:

(译者注:这段内容主要涉及计算机内部数据类型的二进制表示方式)


mantissa:       11 01011011 11001101 00011    (spaced out).
integer:  00000111 01011011 11001101 00010101 (untouched).

在尾数的左侧有一个隐含的 1 位,并且它也在另一端进行了舍入,这就是精度损失的来源(即从程序输出中值从 123456789 改变为 123456792 的情况)。

计算出数值:

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm    1/n
0 10011001 11010110111100110100011
           || | || ||||  || |   |+- 8388608
           || | || ||||  || |   +-- 4194304
           || | || ||||  || +------  262144
           || | || ||||  |+--------   65536
           || | || ||||  +---------   32768
           || | || |||+------------    4096
           || | || ||+-------------    2048
           || | || |+--------------    1024
           || | || +---------------     512
           || | |+-----------------     128
           || | +------------------      64
           || +--------------------      16
           |+----------------------       4
           +-----------------------       2

符号为正,指数为128+16+8+1 = 153 - 127 bias = 26,因此乘数为2的26次方,即67108864

尾数为1(隐式基数)加上(如上所述){1/2, 1/4, 1/16, 1/64, 1/128, 1/512, 1/1024, 1/2048, 1/4096, 1/32768, 1/65536, 1/262144, 1/4194304, 1/8388608}。将它们全部相加,得到1.83964955806732177734375

将其乘以2的26次幂的乘数,得到123456792,与程序输出相同。

双精度掩码输出为:

s eeeeeeeeeee mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
0 10000011001 1101011011110011010001010100000000000000000000000000

我不会计算那个数的值 :-) 但是,我会显示幂位数作为整数格式旁边的二进制表示以展示其通用比特表示:

我不想浪费时间计算那个数的值,但是我会显示它在整数格式下的尾数以展示它的通用位表示:

mantissa:       11 01011011 11001101 00010101 000...000 (spaced out).
integer:  00000111 01011011 11001101 00010101           (untouched).

左边的隐式位和右边宽广得多的位可见有共性,因此在这种情况下没有精度损失。


转换float和double之间的数值也相对容易理解。

首先您需要检查特殊值,例如NaN和无穷大。这些由特殊的指数/尾数组合表示,最好提前检测并生成相应的格式。

然后,在从double转换为float的情况下,由于指数中的位数较少,因此可用范围较小。如果您的double超出了float的范围,则需要处理它。

假设它适合,接下来您需要:

  • 重新设置指数的基数(两种类型的偏差不同)。
  • 复制尽可能多的尾数位(必要时舍入)。
  • 使用零位填充目标尾数的其余部分(如果有)。

1
这在概念上非常简单。一个IEEE 754-1985标准下的float类型有以下表示方式:
  • 1位符号位
  • 8位指数(0表示非规格化数,1表示-126,127表示0,255表示无穷大)
  • 23位尾数(即小数点后面的部分)
所以基本上它的处理步骤如下:
  • 确定数字的符号和大小
  • 找到24个最重要的有效位,进行适当的四舍五入
  • 调整指数
  • 将这三个部分编码成32位形式
当你实现自己的转换时,很容易进行测试,因为你可以将结果与内置类型转换运算符进行比较。

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