1. 前言
C语言中关于变量的初始化、变量的改变和变量的其它一些操作将会结合操作符进行说明。另外变量的读写(printf和scanf)仍有一些细节要掌握,这将在之后的文章中说明。
这里单独来说一说float这个类型的变量,并对隐式类型转换作一些补充。
首先我们来回顾一下隐式类型转换的规则:
2. 公式表达
float类型变量在计算机中的存储不像int类型那样易于理解,而是遵循着一个特别的协议:《IEEE Standard for Floating-Point
Arithmetic》。这个标准的目的是为了统一硬件和软件,让浮点数的计算结果唯一。
Floating-point formats
浮点数由以下格式构成:
(
−
1
)
s
×
b
e
×
m
(-1)^{\operatorname{s}} \times b^{\text {e }} \times \text { m}
(−1)s×be × m
其中:
-
s(sign) 表示符号位,当s = 0时,表示正数; s = 1时,表示负数。
-
对于二进制来说,radix
b
= 2;e(exponent) 表示二进制的指数位,它是 emin ≤ e ≤ emax 内的任意整数。 -
m = significand 是有效位数,由数字串d0
•
d1 d2…dp−1构成,其中0 ≤ di <b
(也有0 ≤ m <b
,b
= 2)
例如:float a = 6.5
; a 表示成二进制为:110.1
,写成以上公式是:
(
−
1
)
0
×
2
2
×
1.101
(-1)^{\operatorname{0}} \times 2^{\text {2 }} \times \text { 1.101}
(−1)0×22 × 1.101
2. 空间存储
接下来需要对转化后的结果进行存储,即对s, e, m进行编码存储,基本格式为:
对于不同类型的浮点数变量,在编码格式上会有一些区别。具体表现为由变量类型导致的空间大小,分别影响了E和T的长度,w bits
和 p - 1bits;p(precision) 表示有效位数字的个数,也代表的encoding的精度。例如对0.3
的编码或是一些需要无限位二进制表示的小数,float32类型有23位表示,而double64类型有52位表示,其余不能被表示的部分就被截断了。
仍是以上6.5
的例子:
-
S 存储的就是0;
-
E存储数值为 e + bias = 2 + 127 = 129;bias与变量类型的大小有关,对于float_binary32,bias = 127;这在下表中做出阐述。 E的空间长度为w bits,这也与变量类型有关,float_binary32 w = 8bits,
6.5
的E为1000 0001
-
T = d1 d2…d~p −1~; 可以发现这与之前公式的m的定义不同,d0不见了。实际上,二进制的有效位总是从1开始的,经过上面的公式变化后,d0始终为1,这在存储时可以去除以节省空间,所以T只包含了
•
后的部分,此时T的长度为p-1。6.5
的T为101 0000 0000 0000 0000 0000
-
综上,
6.5
二进制0100 0000 1101 0000 0000 0000 0000 0000
->0x40d00000
另外,还要说明两个规则: -
E存全零表示0
-
E存全一表示无穷
3. 代码验证
这里的编译器为vs2015,是按字节序小端存储的。
在操作符一文中,我提出了关于float类型变量真假的猜想,这里再做个实验:
根据上文所述,E中存全零则表示0;那么我在一个浮点型数据中存上1000 0000 0111 1111 1111 1111 1111 1111
-> 0x807fffff
,应该会被判定为假。因为无法直接在float类型变量中存上这一串数值(会被编译器转换),故采用指针来“曲线救国”:
这个结果有些打脸!把T改零试试?即存上1111 1111 1000 0000 0000 0000 0000 0000
-> 0xff800000
还是不行,那把T和E都改零,存上1000 0000 0000 0000 0000 0000 0000 0000
-> 0x80000000
欧了!!!
参考资料
[1]: 《IEEE Standard for Floating-Point Arithmetic》
[2]: 《The C Programming Language》.Second Edition. Brian W. Kernighan/Dennis M. Ritchie