C语言中浮点数的二进制表示(IEEE754)

Xilong Yang
2019-08-18

最近在C语言学习中遇到了浮点运算精度的问题, 在查找资料后发现是浮点数的储存方式引起的问题,在此做一个记录。

问题代码

#include<stdio.h>

int main()
{
    int arr[10] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
    int i;
    float a = 0;
    for (i = 0; i < 10; i++)
        a += (float)arr[i] / 10.0;  //求arr中所有数的平均数
    for (i = 0; i < 10; i++)
        if(arr[i] > a) printf("%d ", arr[i]); //将arr中大于平均数的数打印出来
    return 0;
}

不难看出,以上程序理论上不应该输出任何数据,可是实际运行结果如下:

3 3 3 3 3 3 3 3 3 3 3

分析

经过一番尝试,最后发现是由于本代码中a的值并非3.0而是2.99999,由此判断应该是浮点数的运算精度问题。

浮点数在内存中是按照IEEE754标准进行储存的, 即一个float类型的数据占用8Byte内存,其中包括符号位1位,阶码8位和尾数23位。图示如下:

S(符号位) E(阶码) M(尾数)
0 0000 0000 0000 0000 0000 0000 0000 000

其中符号位决定该浮点数的正负,正值为0,负值为1。

阶码用以表示该浮点数的指数,其值为

E = e(指数值) + 127

这样可以保证E不为负数,方便机器运算。其中127为float类型的偏移值,其它浮点类型的有其它偏移值。

按照浮点规格化表示,尾数的最高有效位应为1,这意味着M表示的值为1.M。

以遇到的问题中的值0.3为例, 其转化过程如下。

  1. 将十进制数转换为二进制,小数点前除2取余,小数点后乘2取整 \[(0.3)_{10} = (0.0100110011001100110011001)_2 \] 此时可以发现0.3的二进制是无限循环的,故而只能截取到精度对应的位数。

  2. 规格化表示 \[0.100110011001100110011001=+1.00110011001100110011001\times2^{-2}\]

  3. 计算相应的值 S=0, E = 127 - 2 = 125 = 0111 1101, M = 0011 0011 0011 0011 0011 001

所以0.3在内存中应该为

S E M
0 0111 1101 0011 0011 0011 0011 0011 001

转化为16进制数为:3E999999

可以用以下程序验证

#include<stdio.h>

int main()
{
    float a = 0.3;
    printf("%x", *(int *)&a);
    return 0;
}

结论

得到结果:3E99999A, 与理论计算值3E999999相差1,应该是计算机处理过程中对末位进行了四舍五入。

© 2019- Xilong Yang | CC BY-NC 4.0 | Powered by LaTeX.css, Prism, MathJax