浮点小数(Floating Point)

浮点小数是程序中经常使用的一种数据类型,其使用非常广泛,特别是在科学计算、工程领域以及计算机图形学、人工智能等领域。

1. 原理

在计算机中,浮点数常常采用 IEEE 754 标准来表示。这个标准定义了两种浮点数格式:单精度(32位)和双精度(64位)。

  1. 在单精度浮点数中,通常使用1位表示符号、8位表示指数和23位表示尾数;
  2. 在双精度浮点数中,通常使用1位表示符号、11位表示指数和52位表示尾数。

那么,当我们拿到一个小数时,是如何存储到这 32 个二进制位中?

我们以 3.75 为例,来说明下存储过程。

  1. 将 3.75 转换为二进制表示:\(11.11\)
  2. 二进制表示转换为科学计数法:\(1.111*2^1\)
  3. 我们这个数是正数,所以最高位符号位放 0,反之如果是负数,则最高位符号位放 1
  4. 中间 8 个比特位存储指数,即:\(2^1\)中的1,但是并不是直接把 1 放进去,这 8 个比特位是不包含符号位的,其只能表示正数,但是指数是可能存在负数的,所以,将其值加上 127 转换为正数即可,处理的时候再减去 127 就行。所以,这 8 个比特位实际存储的是:1+127=128,对应的二进制为:10000000
  5. 尾数部分是:1.111,这部分的值都是 1.xxx,所以为了节省了一个二进制位,这里只存储 111,处理的时候加上前面的 1 即可。
  6. 所以,3.75 实际在内存中存储为:0 10000000 11100000000000000000000

[expand title=”整数转换二进制”]

除二取余,倒序排列,高位补零

  1. 将要转换的整数不断除以2,直到商为0。
  2. 每次除法得到的余数(0或1)就是二进制数的一个位数,从下往上排列得到最终的二进制表示。

举个例子,将整数27转换为二进制:

步骤1:27 ÷ 2 = 13 余 1
步骤2:13 ÷ 2 = 6 余 1
步骤3:6 ÷ 2 = 3 余 0
步骤4:3 ÷ 2 = 1 余 1
步骤5:1 ÷ 2 = 0 余 1

将余数从最后一步开始排列,得到二进制数为 11011。

[/expand]

[expand title=”小数转换二进制”]

乘二取整

  1. 将小数乘以2,取结果的整数部分作为二进制数的一个位数。
  2. 将小数部分保留,再次乘以2,重复以上步骤直到小数部分为0或者达到所需的精度。

举个例子,将小数0.625转换为二进制:

步骤1:0.625 × 2 = 1.25,取整数部分1,小数部分保留为0.25
步骤2:0.25 × 2 = 0.5,取整数部分0,小数部分保留为0.5
步骤3:0.5 × 2 = 1,取整数部分1,小数部分为0,转换结束。

因此,0.625 的二进制表示为0.101。

[/expand]

[expand title=”C/C++程序验证”]

我们通过一个程序例子来获得 3.75 在内存中真实的二进制位是不是和我们分析的一样,如下代码:

#include <iostream>
#include <stack>

void test()
{
    float value = 3.75;

    // 初始化 p 指针指向 value 的低地址位置(也是低位字节位置)
    unsigned char *p = (unsigned char *)&value;
    // 输出的二进制位是由低到高,通过栈翻转下输出
    std::stack<unsigned int> bit_values;

    // 遍历每一个字节
    for (unsigned int i = 0; i < sizeof(value); ++i)
    {
        // 从右向左获得每一个字节(8bit)的数据
        unsigned char temp = p[i];
        // 用于位与运算提取每个位的值
        unsigned char mask = 0x1;

        for (unsigned int j = 0; j < 8; ++j)
        {
            // 提取最低位的二进制位值
            unsigned int bit_value = temp & mask;
            // 存储二进制位置到栈容器中
            bit_values.push(bit_value);
            // 当前字节数据向右移动一个位置
            temp >>= 1;
        }
    }

    while (!bit_values.empty())
    {
        printf("%u", bit_values.top());
        bit_values.pop();
    }
}

int main()
{
    test();
}

程序输出结果:

# 输出结果
01000000011100000000000000000000
# 手算结果
01000000011100000000000000000000

[/expand]

2. 浮动

浮点数中的 “浮点” 指的是小数点可以 “浮动”,即可以在数字中的不同位置。例如

  1. 浮点数 1234.5 表示为 1.2345 × 10^3。在这个表示中,1.2345 是尾数,而 10^3 是指数。这个指数表示了小数点向右移动了3 位;
  2. 浮点数 12.345 表示为 1.2345 × 10^1。在这个表示中,1.2345 是尾数,而 10^1 是指数。这个指数表示了小数点向右移动了 1 位。

3. 精度

浮点数这种存储原理使其能够表示非常大或非常小的数值范围,但也引入了一些精度问题,因为在有限的位数内表示无限的实数范围是不可行的,这可能导致浮点数的舍入误差。

指数位可以为正负值,如果是一个很小的负数,例如:-127,则表示小数点向左移动了 127 位,这将会是一个非常小的数值。反之,如果是一个较大的正数,例如:127,则表示小数点向右移动了 127 位,这将会是一个非常大的数字。

尾数部分存储了数据中真实的各位的值,但是由于其位数有限,能表示的数值范围有限,故而又存在精度的问题。尾数的位数越多,则能表示的精度越高。

4. 缺点

  1. 运算复杂性:浮点数的计算通常由专门的浮点数处理单元(FPU)或浮点数部件执行。这些部件需要复杂的电路设计和逻辑,以支持浮点数的加法、减法、乘法、除法等操作,这就导致浮点数运算通常需要更多的指令和周期来完成,与整数运算相比,这增加了计算的开销。
  2. 浮点数精度:浮点数通常使用有限的位数来表示,例如单精度浮点数使用32位表示,双精度浮点数使用64位表示。这种有限精度可能会导致舍入误差,并降低计算的准确性。
  3. 数据存储开销:浮点数通常需要 4 或 8 个字节存储,对于存储海量浮点数场景,开销是非常昂贵。

未经允许不得转载:一亩三分地 » 浮点小数(Floating Point)
评论 (0)

5 + 8 =