打印观察

上篇写了从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; }

时钟模型

这个原理用时钟来解释是非常直观形象的,如下图所示:

c语言必背代码入门大全(C语言026float彻底研究四)(1)

浮点数像时钟上的刻度一样,是离散的,而且只能指向蓝点。比如,如果当前是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²³。

后续推导相同。

我们发现,当移动次数不变时,差值越来越大,而差值增大的结果就会导致间隔增大。

c语言必背代码入门大全(C语言026float彻底研究四)(2)

这里举一个特例说明,假如要计算[0x800000,0xFFFFFF)之间的间隔

=(0xFFFFFF-0x800000) / 2²³

得到结果是1,也即是从8388608 ~ 16777215之间的浮点数,间隔是1。

区间间隔数据表

为了更好的查看各个区间的间隔,就出现了间隔数据表:

c语言必背代码入门大全(C语言026float彻底研究四)(3)

在探险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数的精度做了总结,涉及的知识比较多,最好就是多写代码验证,强调实践。

,