(接上回)
我们在C/C++代码中使用:
printf(“%f”, 10/3, 0×40080000 );
看到运行结果了吗?为什么这行看起来不合乎所谓的语法的printf能输出3.000000呢?
翻阅手册,回顾一下printf的格式化参数说明,你会发现%f的类型是double!
我们知道在目前32位的机器上,sizeof(double) = 8字节 而sizeof(int) = 4字节,也就是说,机器在处理输出数据的时候,期望得到一个8字节的数据空间,而实际上只提供了一个4字节的数据空间,是不是这里出了问题?那么这个过程到底发生了些什么呢?
10/3 这个数据由于是整数类型常数,他并不是在运行时进行计算的,而是在编译时,编译器把他翻译成了3。实质上,这段代码就与写printf(“%f”, 3)无异,他们完全等价。
既然等价,为什么使用printf(“%f”, 3.0f);会输出正确的值?sizeof(3.0f) 不也等于4么?3.0f 和 整数3 是否等价?3.0和3.0f是否等价?
似乎有点越扯越远,大家会不会有点糊涂了?要解释清楚这个过程确实需要一点口舌,不过别急,我们一个问题一个问题地说吧:
问题一:printf(“%f”, 3.0f) 与 printf(“%f”, 3)的区别,3.0f和整数3在存储上有什么区别?3.0和3.0f在存储上有什么区别?
其实,3.0f和整数3在内存中的表示是完全不同的,他们都占用4个字节空间,整数3在内存当中就是 0×00000003,而3.0f在内存中却为:0×40400000。
并且,你会发现,3.0和3.0f在内存中的表示也不相同,3.0是一个双精度浮点数,他在内存中表示为:0×4008000000000000。你可以通过调试器查看这一点。
那么浮点数是按什么规则表示的呢?IEEE标准从逻辑上用三元组{S,E,M}来表示浮点数N,其中S代表符号位,E代表指数位,M代表尾数。
如果是单精度浮点数(float):N共32位,其中S占1位,E占8位,M占23位
如果是双精度浮点数(double):N共64位,其中S占1位,E占11位,M占52位。
(注:本文并不打算详细探讨浮点数的表示规则,有兴趣的朋友可自行参考IEEE754浮点数标准)
N可以用以下公式算得:
N = (-1)^S * m * 2^e
当E的二进制位不全为0,也不全为1时,
e = |E| – bias (bias = 2^(k-1) – 1)
单精度时k=8,bias=127 双精度时k=11,bias=1023
其中m = |1.M|
当E的二进制位全部为0时,此时:
e = 1- bias
m = |0.M|
当E的二进制位全为1时,若M的二进制位全为0,则n表示无穷大,若S为1则表示负无穷,S为0则为正无穷。若M的二进制位不全为0时,表示NaN(Not a Number),代表着不合法或未初始化的值。
例如:
单精度浮点数3.0f,表示为2进制:
S | E | M |
0 10000000 10000000000000000000000
N = (-1)^0 * 1.5 * 2^1 = 3.0f
双精度浮点数3.0,表示为2进制:
符号位 指数位 尾数位
S | E | M |
0 01000000000 1000 0000 0000 0000 … 0000
看到了吗?他们的计算原理是一样的,但是位数不同,导致他们在内存里面的表示也不相同。
3 在内存里面用16进制表示为:0×00000003
3.0f 在内存里面用16进制表示为:0×40400000
3.0 在内存里面用16进制表示为:0×4008000000000000
我们现在知道了printf中%f是按double进行处理的,那么按双精度浮点规则,我们来看3是怎么被算成0的?
3 用 64bit二进制表示:
0000 0000 0000 0000 0000 0000 0000 … 0000 0011
S | E | M |
0 00000000000 0000 0000 0000 0000 … 0011
这里的m已经是一个非常小,近乎于0的数字了,因此,在float保有的精度范围内,显示成为了0。
有兴趣的同学可以算算,这个值大约等于:1.48乘以负的323次方。
好,现在弄清楚了浮点数的表示,新的问题又来了,printf(“%f”, 10/3, 0×40080000 ); 能显示3.000000,这又是怎么工作的呢?
(未完待续)