下面这段代码,输出值是多少呢?
void main()
{
float data = 266270.83;
printf("data=%0.2f\n",data);
}
在我32位的虚拟机上,打印输出值是:data=266270.84
为什么会出现这种情况呢,难道是计算机出问题了。
当然不是计算机出现了问题,我们从下面几点来分析探讨。
首先,我们从浮点数在内存中的存储形式来分析。
大家都知道,数据在内存中是以010101这种二进制的形式存储的,那浮点数相对整数的存储又有哪些特殊的地方呢。
按照科学计数法,浮点数在内存中分三部分存储,分别是符号位、指数位、尾数位。
float:单精度浮点数,1位符号位,8位指数位,23位尾数位,总共32位
double:双精度浮点数,1位符号位,11位指数位,52位尾数位,总共64位。
所以,我们需要把浮点数先转换成二进制表示,转换遵循以下规则
1、先把整数部分和小数部分分别转换为二进制形式
2、整数部分转换为二进制形式采用“除2取余,逆序排列”法,即整数部分除以2,取出余数,商继续除以2,直到得到0为止,然后将取出的余数逆序排列
3、小数部分转换为二进制形式采用“乘2取整,顺序排列”法,即小数部分乘以2,然后取出整数部分,剩下的小数部分继续乘以2,直到小数部分为零为止。如果永远不为零,则按照要求保留足够位数的小数,多余位需要舍去(0舍1入法),取出的整数部分顺序排列。
4、转换出的二进制浮点数,进行小数点移位操作,把小数点移到整数位只有一位。
5、计算指数位数据:单精度浮点数指数计算需要做偏移位计算(2^(e-1)-1,e为指数位数),偏移位为127,第4步的小数点移位位数+偏移位=实际指数位数据
例:266270.83
1、整数部分:266270 表示为二进制 100 0001 0000 0001 1110
2、小数部分:0.83 表示为二进制 1101 0100 0111…………(总小数位数已超23位)
3、二进制浮点数:100 0001 0000 0001 1110. 1101 0100 0111…………
4、二进制浮点数小数点移位操作:总共移位(18位)二进制表示为---- 1 0010
5、移位后小数表示:1.00 0001 0000 0001 1110 1101 0100 0111…………
6、指数部分计算:0111 1111+1 0010 = 1001 0001
7、浮点数在内存中的表示形式:
符号位 | 指数位 | 尾数位 |
0 | 100 1000 1 | 000 0010 0000 0011 1101 1010 |
因为舍去的位为 100 0111…………
按照0舍1入法,舍去的最高位为1,所以最低为需要加1,所以最终在内存中表示形式为:
符号位 | 指数位 | 尾数位 |
0 | 100 1000 1 | 000 0010 0000 0011 1101 1011 |
8、根据浮点数内存中表示形式,进行反推
0 符号位
1000 1 指数位(18)
.000 0010 0000 0011 1101 1011 尾数位
尾数小数点右移18位
000 0010 0000 0011 110.1 1011
0. 1 1 0 1 1
0.5 0.25 0 0.0625 0.03125
计算出来最终为:266270.84375
打印2位小数,所以最后打印出来就如文章开始打印出的 266270.84
解决办法:
遇见这种情况,说明单精度浮点数表示已经出现了精度的问题,那我们用双精度浮点数来表示,即可解决此问题。
简单的单精度浮点数精度问题判断
我们知道单精度浮点数小数位数有效为6位,对应二进制是24位,即要表示一位有效小数,至少占4位二进制。那么,我们就可以根据整数位的二进制长度和小数精度位数来简单确定一个单精度浮点数的精度是否会有问题。
如:266270.83,
整数部分:100 0001 0000 0001 1110 已占用 19-1 = 18位
小数部分:对应的两位精度小数,需要2*4=8位
合计:整数+小数 18+8=26>24,所以其两位小数精度会有问题
如:16384.001
整数部分: 100 0000 0000 0000 已占用 15-1 = 14位
小数部分:对应的三位精度小数,需要3*4=12位
合计:整数+小数 14+12=26>24,所以其三位小数精度会有问题
void main()
{
float x= 166384.001;
float y = 0.001;
float z;
z = x+y;
printf("data=%0.3f,%0.3f,%0.3f\n",x,y,z);
}
大家可以试试这段代码,结果很惊喜。