【原创】为什么浮点数1e38f + 1 - 1e38f等于0
1. 问题
为什么1e38f + 1 - 1e38f为0?
2. 分析
int ii = 2; //00 00 00 02 int *pii = ⅈ float i = 1e38f; //7e 96 76 99 (0111 1110 1001 0110 0111 0110 1001 1001) float j = -1e38f;//fe 9676 99 (1111 1110 1001 0110 0111 0110 1001 1001) float *pi = &i; float *pj = &j; int fs = sizeof(float); float k = i + j + 1; //3f 80 00 00 (0011 1111 1000 0000 0000 0000 0000 0000) float l = i + 1 + j; //00 00 00 00 float *pk = &k; float *pl = &l;
根据IEEE 754single-precision binary floating-point format
单精度浮点数:4个字节,最高位为符号位,23-30共8位指数,0-22共23为尾数(隐藏了最高位1);指数用移码表示,指数的偏移量为127;尾数用补码表示。
float i = 1e38f; //99 76 96 7e (0111 1110 1001 0110 0111 0110 1001 1001)
符号位:0
指数:1111 1101
尾数:001 0110 0111 0110 1001 1001
float j = -1e38f; //99 76 96 fe (1111 1110 1001 0110 0111 0110 1001 1001)
符号位:1
指数:1111 1101(和上面一样有没有)
尾数:001 0110 0111 0110 1001 1001
指数对齐,尾数相加,(对齐时,指数向大的对齐,相加前小的指数对应的尾数要右移指数差位)
这里指数一样大,不用对齐;
1 = +1 * 2^0
符号位:0
指数:127(0111 1111)
尾数:1(000 0000 0000 0000 0000 0000)
0 0111 1111 000 0000 0000 0000 0000 0000
3. i + 1计算步骤:
3.1 指数对齐
1111 1101和0111 1111,差为253-127=126
尾数相加时要把隐藏的1写出来;
尾数补上最高位1,再右移126位,由于右移126位超过了23位,尾数都被移除,全被置0。
3.2 尾数相加
1001 0110 0111 0110 1001 1001
+ 0000 0000 0000 0000 0000 0000
= 0 1001 0110 0111 0110 1001 1001
3.3 溢出判断
上面的数没有溢出;
如果溢出将该数右移1位且指数加1
3.4 结果合成
由于尾数实际为24位,最高位总为1,实际上只存储23位,最高位不存储。
去除尾数最高位及指数部分清0:result =result &(~0xFF800000)
和较大指数合并:result= result| (e1 << 23);
result即为浮点数的二进制表示了,把它转换成浮点数:c = *((float *)&result);
由上述可以得出,尾数相加后不变,指数不变,所以和结果不变,仍为较大的那个浮点数。
进而我们可以得出一个结论:当两个浮点数的指数相差超过浮点数的尾数位数,那么两个浮点数的相加结果将会等于较大的浮点数! 所以1e38f + 1 - 1e38f = 1e38f - 1e38f = 0
4. 代码
#include <stdio.h> // simple ieee 754 single precision float number // addition arithmetic. // format: // S E F // 1 8 23 float test1(float a, float b) { float c = 0; unsigned int p1 = *((unsigned int *)&a); unsigned int p2 = *((unsigned int *)&b); unsigned int t; // compute exponent difference // essentially, we must let two number's exponent be same int e1 = (p1 << 1) >> 24; int e2 = (p2 << 1) >> 24; int diff = e1 - e2; if (diff < 0) { diff = 0 - diff; t = p1; p1 = p2; p2 = t; e1 = e2; } //convert mantissa to signed integer // there is a hidden bit //:) i do not want to handle minus number here // int p3 = p1 | 0x00800000; p3 = p3 & 0x00ffffff; int p4 = p2 | 0x00800000; p4 = p4 & 0x00ffffff; //mantissa should be shift right according to difference of //exponent. unsigned int result = p3 + (p4 >> diff); if (result >= 0x01000000) { result = result >> 1; e1 = e1 + 1; } // combination result = result&(~0xFF800000) | (e1 << 23); c = *((float *)&result); return c; } int _tmain(int argc, _TCHAR* argv[]) { float c = test1(4.1f, 1.0f); return 0; }
5. 流程图
6. 参考文献
https://en.wikipedia.org/wiki/Single-precision_floating-point_format
http://www.cnblogs.com/cornsea/archive/2010/09/18/1830366.html