C语言中float数据类型精度问题
先来看一个简单的例子:
float a = 0.1;
printf("%f", a);
输出结果是:0.100000
看起来正确。其实不然!因为0.1无法被float精确表示,系统自动转换成了一个近似值0.10000001192092896
我们输出更多位小数试试:
float a = 0.1;
printf("%.20f", a);
输出是:
0.10000000000000000555
明显不精确!
再看一个类似的例子:
float b = 1.2;
printf("%.20f", b);
输出是:
1.1999999284744263123
同样是因为1.2无法精确表示,被转换成了一个近似值。
这是因为浮点数存储是基于二进制的,而大多数十进制小数无法被二进制准确表示,就会发生近似和精度损失。
类似的情况也发生在题目给的浮点数1.0000022上,它也无法被float精确表示,所以打印出来的近似值是1.0000021。
原理
0.100000 近似值是0.10000001192092896 是如何得来的呢?
对于第一个例子中浮点数0.1被近似为0.10000001192092896的过程,涉及到浮点数的二进制表示形式。
0.1在二进制中是一个无限不循环的小数:
0.1 (十进制)= 0.00011001100110011…(二进制)
但在float中只有23位可以表示小数部分。所以0.1需要被截断转换成一个近似的二进制数:
0.10000001192092896 (十进制)
= 0.0001100110011001100110011…(二进制,截断到23位)
这个近似的二进制数才能被float变量所存储。
具体的计算方法是:
-
将0.1转换成二进制无限不循环小数
-
保留小数点后23位,其他位数截断
-
将这23位二进制数归一化(调整指数位数)
-
得到一个能被float存储的近似二进制数
-
再将这个近似二进制数转换回十进制数
所以0.10000001192092896就是0.1经过上述转换过程得到的一个近似值,这是计算机试图逼近0.1的浮点数表示方法。
其中IEEE 754标准对如何进行舍入和近似做了详细规定,要尽可能使近似值接近原值,因此也称为“最良近似值”。
这就是第一个例子中0.1被近似为0.10000001192092896的过程和计算依据。
总结
0.1 在二进制中的表示是无限不循环小数:
0.1 (十进制) = 0.000110011001100110011001100110011…(二进制)
但浮点数float只有23位可以表示小数部分。所以它不得不对0.1进行截断转换:
取0.0001100110011001100110011 (二进制,截断到23位)
然后将这个截断后的二进制数转换为十进制数,就可以得到一个float变量能够表示的近似值:
0.0001100110011001100110011 (二进制)
= 0.10000001192092896 (十进制)
所以您的推断是正确的,二进制的0.00011001100110011转换为十进制就是0.10000001192092896。
这是计算机为了逼近无法准确表示的0.1,根据浮点数的二进制表达方式得到的一个最接近的近似值。
所以说,原因是0.1的二进制表示形式是无限不循环小数,而浮点数类型float只能存储有限位数。这就导致了需要对0.1进行截断和近似,才能得到一个float变量能表示的近似值0.10000001192092896。