本文都是下面文章开头6个问题的解答:
张雅宸:浮点数的二进制表示以及几个例子zhuanlan.zhihu.com问题1
int c = 0.58 * 100;
printf("%dn",c); //57
int x = 0.58f * 100.0f;
printf("%dn",x); //58
当两个数字都是float类型时,相乘的结果却是58。揭开谜底的过程涉及了浮点数的存储方式、浮点数的乘法规则和舍入。
关于浮点数的存储方式可以参看浮点数的二进制表示以及几个例子,这里不再赘述。
0.58 * 100
0.58的双精度表示是:
0 01111111110 0010100011110101110000101000111101011100001010001111
100的双精度表示是(浮点数):
0 10000000101 1001000000000000000000000000000000000000000000000000
我们看一下0.58 * 100的计算过程:
转变为标准的科学技术法:
0.58是1.0010100011110101110000101000111101011100001010001111 * 2^-1
100是1.1001000000000000000000000000000000000000000000000000 * 2^6
将两个指数相加
-1 + 6 = 5
将两个尾数相乘
1.0010100011110101110000101000111101011100001010001111 * 1.1001000000000000000000000000000000000000000000000000 =
1.11001111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000
规格化
上面的结果是规格化小数,无需变化(小数点左边只有一个1)
符号位相加mod2
0 + 0 = 0
舍入
重点在这里,由于双精度的位数只有52位,所以我们要舍弃一部分尾数。IEEE默认使用向偶数舍入。所以上面的结果舍入后是:
1.1100111111111111111111111111111111111111111111111111
判断溢出
判断阶码相加是否有溢出。 无
所以最后的结果是
0 10000000100 1100111111111111111111111111111111111111111111111111
结果为:57.999999999999992894572642399
由于C语言中float/double转向整数使用向零靠近,所以最后结果0.58 * 100 = 57。
我们再看一下0.58f * 100.0f的计算过程:
0.58f * 100.0f
0.58的单精度表示是:
0 01111110 00101000111101011100001
100的单精度表示是(浮点数):
0 10000101 10010000000000000000000
具体过程:
转变为标准的科学技术法
0.58是1.00101000111101011100001 * 2^-1
100是1.10010000000000000000000 * 2^6
将两个指数相加
-1 + 6 = 5
将两个尾数相乘
1.00101000111101011100001 * 1.10010000000000000000000 =
1.1100111111111111111111110010000000000000000000
规格化
上面的结果是规格化小数,无需变化(小数点左边只有一个1)
符号位相加mod2
0 + 0 = 0
舍入
由于单精度的位数只有23位,所以我们要舍弃一部分尾数。上面的双精度舍入时是把最后的0全部舍入。但是这里的单精度舍入时,需要进一位。这也是造成两个乘法结果不相等的原因。 所以上面的结果舍入后是:
1.11010000000000000000000
判断溢出
判断阶码相加是否有溢出。 无
所以最后的结果是
0 10000100 11010000000000000000000
结果恰好为58.0。所以0.58f * 100.0f = 58
最后说一下,向偶数舍入和我们学的四舍五入还不太一样。可以看一下浮点数的二进制表示以及几个例子的浮点数舍入部分。
问题2
printf("%d",0.1 + 0.2 == 0.3); //0,not 1
这个问题涉及到的是浮点数的加法。