上篇写了从16777216.0开始,分别用float存储,通过测试样本可以看出有一半的数没法用float来存储。比如16777217、16777219等。
现在我们可以理解精度了。比如,如果说有一个东西能精确到两位数,意思是指从0~99都能表示出来。
回到我们的例子,有人说float可以精确到8位数,这个是不成立的。因为将近有一半的数竟然无法存储,比如用户需要存储16777217,float存储不了,结果被存成16777216!
/***
* 作用:打印浮点数16777217.0f
*
*
*
****/
int main(int argc, char* argv[]){
float f=16777217.0f;
printf("%f\n",f);
//显示结果:16777216.000000
return 0;
}
这个原理用时钟来解释是非常直观形象的,如下图所示:
浮点数像时钟上的刻度一样,是离散的,而且只能指向蓝点。比如,如果当前是16777216,那么下一站就是16777218,不存在(红颜色的)16777217,后续类似。
其二,由于间隔是2,就不可能存储像16777216.5这样的小数,换句话说,下一站直接跨到16777218,连16777217这样的整数都无法精确,那么后面带的小数就根本无法表示。
浮点数分布推导在浮点数的存储方法中,如果指数位不变,那么相邻的两个尾数位之间的间隔是一定的,但是相邻的两个指数域之间的间隔是不同的。
我们假设尾数部分全为0,指数部分为1000 0000,此时表示的单精度浮点为2.0;将指数部分加1变成1000 0001,此时表示的单精度浮点数为4.0;将指数部分再加1变为1000 0010,此时表示的单精度浮点数为8.0。也就是说[2.0,4.0)范围内的浮点数间隔为2/2²³,而[4.0,8.0)范围内的浮点数间隔为4/2²³。
¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸
指数部分每加1,浮点数之间的间隔也会相应地扩大一倍。
【解释】
一、为什么[2.0,4.0)范围内的浮点数间隔为2/2²³ ?
先假定指数位不变,尾数开始:
0 1000 0000 000 0000 0000 0000 0000 0000加1移动1位,得到:0 1000 0000 000 0000 0000 0000 0000 0001再加1移动1位,得到0 1000 0000 000 0000 0000 0000 0000 0010....
移到2²³次之后得到:0 1000 0001 000 0000 0000 0000 0000 0000
根据公式间隔 = 差值/移动次数 = (终点值 - 起点值) / 2²³。
后续推导相同。
我们发现,当移动次数不变时,差值越来越大,而差值增大的结果就会导致间隔增大。
这里举一个特例说明,假如要计算[0x800000,0xFFFFFF)之间的间隔
=(0xFFFFFF-0x800000) / 2²³
得到结果是1,也即是从8388608 ~ 16777215之间的浮点数,间隔是1。
区间间隔数据表为了更好的查看各个区间的间隔,就出现了间隔数据表:
在探险1中,实际验证是的16777216开始的区间,间隔为2,至于误差到底是偏左偏右,作者个人认为已经不太重要,因为它本身就是一个不精确的数,也即我们一开始就没抱着精确的目标使用浮点数,多一点少一点已经不是重点。下面举几例来说明表:
一、验证8388608开始的区间
此区间间隔为1,因此无法表示小数,代码如下:
/***
* 作用:验证间隔1的区间,
* 观察:不能表示小数,成立!
*
****/
int main(int argc, char* argv[]){
float f1=8388608.5f;
printf("%f\n",f1);
//打印结果:8388608.000000
float f2=8388609.607f;
printf("%f\n",f2);
//打印结果:8388610.000000
float f3=8388610.345f;
printf("%f\n",f3);
//打印结果:8388610.000000
return 0;
}
二、验证1~1.99999...区间
此区间间隔为1.19209e-7。
/***
* 作用:观察1.0000001之后的精度表示
*
*
*
****/
int main(int argc, char* argv[]){
printf("%.17f\n",1.0000001f);
printf("%.17f\n",1.0000002f);
printf("%.17f\n",1.0000003f);
printf("%.17f\n",1.0000004f);
printf("%.17f\n",1.0000005f);
printf("%.17f\n",1.0000006f);
printf("%.17f\n",1.0000007f);
printf("%.17f\n",1.0000008f);
printf("%.17f\n",1.0000009f);
return 0;
}
结果如下:
1.00000011920928960 =>1.0000001
1.00000023841857910 =>1.0000002
1.00000035762786870 =>1.0000003
1.00000035762786870 =>1.0000004
1.00000047683715820 =>1.0000005
1.00000059604644780 =>1.0000006
1.00000071525573730 =>1.0000007
1.00000083446502690 =>1.0000008
1.00000095367431640 =>1.0000009
结论:有三个数不能正常表示,因此精度达不到小数点后7位
结语这一篇对float数的精度做了总结,涉及的知识比较多,最好就是多写代码验证,强调实践。
,