我们都知道浮点数会有精度问题,但为什么会有精度问题?用来表示整数是否会有精度问题?最大可以准确描述的整数可以有多大?这些问题在我们平常开发中也会遇到,想要弄明白这些问题的答案,需要从浮点数的具体表示方式入手。后续会写的一些技术文章也有涉及到这部分知识,所以先开一篇文章从最简单的浮点数表示开始,把这些问题讲清楚。以下均以32位浮点数举例说明,64位同理。
首先我们需要知道浮点数表示由三部分组成,最高位是符号位,表示正负。中间8位是指数位,用来表示后23位有效位的小数点位移。后23位就是用来存储浮点数有效表示的01。
我们先来看看6.75这个数怎么用二进制表示,整数部分6是110,小数部分0.75是11,整体是110.11。整数部分 6是110 是与整型表示是一致的,而小数部分第一个1表示2的1次幂的倒数即1/2,第二个1表示 2的2次幂的倒数即 1/4,如此类推。0.75小数部分11(1/2+1/4),0.625小数部分是101(1/2+1/8)。
在弄清楚6.25是110.11之后,我们知道浮点数总是表示成1.xx位移n位这样,前面的1就可以省略了,小数点后的xx就记录在23位有效位中,位移n位就记录在中间8位指数位中。6.25就是1.1011,小数点位移2位,它的有效位是1011,指数位是2。在这里还需要弄清楚指数位的二进制表示方式。因为小数点会有左移或右移的可能,所以指数位8位存的数要减去127,得到一个区间在[128,-127]的数。6.25的右移2位,指数位存的是129。
我们在VS内存调试器中看,浮点数6.75在内存中的排布是00,00,d8,40,与上面的32位小端表示是相符的。
通过上述简单的例子弄清楚浮点数的二进制表示机制之后,我们知道小数部分其实是用多个2^n的倒数来近似表示的,所以有一些小数可以准确表述,有一些小数是无法准确表述的。同样的小数会有计算后二进制表示不一样的情况。这个是由浮点数机制决定的,与存的小数部分大小范围无关。
而整数部分,浮点数的二进制表达与整型是一致的,在一定范围内是可以准确表示的,超过一定范围就会有精度丢失的问题。这个范围具体是多少呢?23位有效位+省略的1位,最大能表示的数是2^24-1,即16777215。这个时候23位有效位全是1,指数位是150(位移23位,150-127)。
浮点数16777215在内存中的排布是ff,ff,7f,4b,与上面列的32位小端表示是相符的。 比这个数大的整数,因为无法用23位有效位表达具体的每一个二进制数,所以只能近似表示,会有精度丢失问题。
最后总结一下,32位浮点数的二进制表示分3部分,符号位1,指数位8,有效位23。整数部分的二进制表示是与整型一致的,最大能表示正负16777215。而小数部分是用多个2^n的倒数加和来近似表示的,无论什么范围内的小数都会有精度问题。在进行浮点数运算后,同一个数在二进制表示上都会有出入。了解这些机制有助于我们更好的使用浮点数。