一、浮点数的精度
double型浮点数能精确到多少位小数?
试试就知道了!
#include<stdio.h>
int main(){
printf("%.20f\n",0.5);
printf("%.20f\n",0.25);
printf("%.20f\n",0.1);
printf("%.20f\n",0.3);
printf("%.20f\n",0.7);
return 0;
}
输出结果如下:
可见,它的精度并不是固定的。
二、浮点数产生误差的原因
浮点数的精度取决于要表示的具体值。
这个问题涉及到数在计算中的表示机制。虽然我们在代码中输入的是十进制数,但归根到底其在计算机内是以二进制存储的。计算机先要把我们输入的十进制数转化为二进制数,存储在内存中。当我们要输出时,它再把二制数转化为十进制数。
在转化的过程中,就会产生误差。更具体来说,它是产生在十进制转化为二进制这一过程中。
要弄清这一点,咱们首先要知道小数的实际意义,它表示的是几分之几,也就是“份数”。
比如我们知道0.25的分数是1/4,但这只是它的简写形式,它在十进制中的本质含义是这样的:
0.25=2/10+5/100
用文字描述,它表示10份中的2份加上100份中的5份。如果用饼表示,它表示把一张饼平均分成10份,取其中的2份,放在盘子里,再把同样一张饼平均分成100分,取其中的5份,放在盘子里,现在盘子里的饼就是2/10+5/100=0.25。
也就是说,在十进制小数中,其本质意义是,把一样东西分成10份、100份、1000份……,分别从中取0-9份凑到一块儿。它表现为分母为10的幂的分数的和,用代数式表示如下:
(
的值为整数0-9)
合并同类项后可以写成:
同理,在二进制小数中,其本质意义是,把一样东西分成2份、4份、8份……,分别从中取0-1份凑到一块儿。它表现为分母为2的幂的分数的和,用代数式表示如下:
(
的值为整数0、1)
合并同类项后可以写成:
比如0.101表示把一张饼平均分成2份,取其中的1份,放在盘子里,再把同样一张饼平均分成4分,取其中的0份,放在盘子里,再把同样一张饼平均分成8分,取其中的1份,放在盘子里,最后盘子里的数就是0.101。
这其实就是二进制小数转十进制的方法。
明白了小数的真实意义,十进制转二进制会产生误差就很好理解了。
因为份中的几份不一定能转化为
份中的几份,即形如
的数不一定能转为形如
的数。
比如本文开头代码中测试的数:
0.5=5/10=1/2,0.25=25/100=1/4
这两个数都能转化为份中的几份,因此它们在转化为二进制时是不会产生误差的。也就是
的十进制小数都不会产生浮点数误差。
因为:
所以这个数的小数部分必然是5的倍数,故这个结论也可以描述为:最后一位小数是5的十进制小数都不会产生浮点数误差,比如:0.125、0.375都不会产生误差。
反观0.1、0.3、0.7这样的数呢?拿最简单的小数0.1转化为二进制演示一下:
小数点后第1位:1/10<1/2(不到2份中的1份)→0
小数点后第2位:1/10<1/4(不到4份中的1份)→0
小数点后第3位:1/10<1/8(不到8份中的1份)→0
小数点后第4位:1/10>1/16(超过16份中的1份)→1
小数点后第5位:(1/10-1/16)=6/160>1/32(超过32份中的1份)→1
小数点后第6位:(6/160-1/32)=1/160<1/64(不到64份中的1份)→0
小数点后第7位:1/160<1/128(不到128份中的1份)→0
小数点后第8位:1/160>1/256(超过256份中的1份)→1
小数点后第9位:(1/160-1/256)=6/2560>1/512(超过512份中的1份)→1
这个式子永远找不到等号,最终的二进制数是这样一个无限循环小数:0.00011001100110011...
所以,像0.1、0.3、0.7这样无法写成形式的数,永远都无法分为
这样的分数的和,只能无穷无尽的往下分,分到山无棱、江水为竭。计算机对此情景表示很无奈,只好拦腰一刀砍下,保留前面一部分,这就产生了误差。
相反,二进制转十进制却不存在误差问题,因为任何一个都可以通过分子分母同时乘以
转换为
。