最近在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为例, 其转化过程如下。
将十进制数转换为二进制,小数点前除2取余,小数点后乘2取整 \[(0.3)_{10} = (0.0100110011001100110011001)_2 \] 此时可以发现0.3的二进制是无限循环的,故而只能截取到精度对应的位数。
规格化表示 \[0.100110011001100110011001=+1.00110011001100110011001\times2^{-2}\]
计算相应的值 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,应该是计算机处理过程中对末位进行了四舍五入。