对于浮点类型数据,首先我们需要明白的一点是:浮点数和整型数的编码方式是很不一样的,IEEE浮点标准采用V = (-1)
s×M×2
E的形式来表示一个数,其中符号s决定是负数(s=1)还是正数(s=0),由1位符号位表示。有效数M是一个二进制小数,它的范围在1~2-ε之间(当指数域E既不全为0也不全为1,即浮点数为规格化值时。ε为有效数M的精度误差,比如当有效数为23位时,ε为2
-24),或者在0~1-ε之间(当指数域全为0,即浮点数为非规格化值时),由23位或52位的小数域表示。指数E是2的幂,可正可负,它的作用是对浮点数加权,由8位或者11位的指数域表示。下面我们来详细地剖析IEEE浮点数据表示,相信在认真研读完以下内容之后,你对浮点数的存储将会有非常清晰的认识。不用担心文中会充满数学家才会考虑的算式和公式,虽然大多数人认为IEEE浮点格式晦涩难懂,但理解了其小而一致的定义原则之后,相信你能感觉到它的优雅和温顺,以下的叙述将尽量用浅显易懂的语言和例子让大家心情舒畅地了解浮点存储的内幕。--------------------------------------分割线 --------------------------------------编辑推荐:C语言中简单的for循环和浮点型变量 http://www.linuxidc.com/Linux/2013-08/88514.htmC语言陷阱:浮点运算 http://www.linuxidc.com/Linux/2013-05/83695.htmC语言中浮点数精度进行截断输出 http://www.linuxidc.com/Linux/2010-06/26612.htm如何在C++语言中对浮点数进行格式化处理 http://www.linuxidc.com/Linux/2009-03/19166.htm--------------------------------------分割线 --------------------------------------以32位浮点数为例,其存储器的内部情况是这样的: 符号域指数域小数域
从上述公式V = (-1)
s×M×2
E可以计算出一个浮点数具体的数值,要理解这三个数据域是如何被解释的,我们需要知道:浮点数的编码根据指数域的不同取值表示被分成三种情形:
1、规格化值。当指数域不全为0且不全为1的时候即为这种情形,这是最常见的情况。这时有效数域被解释为小数值f,且 0≤f<1,其二进制表示为0.f
n-1f
n-2…f
1f
0,而有效数M被解释为为M=1+f。同时指数域被解释为偏置形式的有符号数,就指数E被定义为E=e-Bias,其中e就是指数域的二进制表示,Bias是一个等于2
k-1-1的偏置值(k为指数域位数,对于32位浮点数而言是8,对于64位浮点数而言是11)。我们可以调整指数域E来使得有效数M的范围在1≤M<2之间,也就可以始终使得M的第一位是1,从而也没有必要在有效数域中显式地表示它了。我们来做一个透视浮点数存储的练习,以此来更好地理解以上内容。例如浮点数12345.0,其二进制表示为(1.1000000111001)
2×2
13,显然符号位S应该为0,而指数域部分E应该根据公式E= e-Bias计算,e就是我们想要的内部存储表示,Bias在32位单精度浮点中的值为127,因此e=E+Bias=13+127=140,用二进制表示即为(10001100)
2,而小数域就是在其二进制表示的基础上减去最高位的1即可,即0.1000000111001,补满23位小数位,即可得到0.10000001110010000000000,存储时略去前面的的整数部分和小数点,因此浮点数12345.0的IEEE浮点表示为:
| 0 | 1000 1100 | 100 0000 1110 0100 0000 0000 |
2、非规格化值。当指数域全为0时属于这种情形。非规格化编码用于表示非常接近0.0的数值以及0本身(因为规格化编码时有效数M始终大于等于1),我们会看到,由于在非规格化编码时指数域E被解释为一个定值:E = 1-Bias = -126(而不是规格化时E = e-Bias),使得规格化数值和非规格化数值之间实现了平滑过渡。另外,此时有效数M解释为M=f,对比上面所讲的规格化编码,此时的有效数M就是小数域的值,不包含开头的1。举个例子,编码为如下情形的浮点数就是一个非规格化的样本:
| 0 | 0000 0000 | 000 0000 0000 0000 0000 0001 |
这个数值的大小,就应该被解释为(-1)
0×(0.00000000000000000000001)
2×2
1-Bias,即2
-23×2
-126 = 2
-149,转成十进制表示大约等于1.4×10
-45,实际上这就是单精度浮点数所能表达的最小正数了。以此类推,规格化值和非规格化值所能表达的非负数值范围如下所示:
| | 指数域 | 小数域 | 32位单精度浮点数 |
| 值 | 十进制 |
| 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | 0 | 0.0 |
| 最小非规格化数最大非规格化数最小规格化数最大规格化数 | 0000 00000000 00000000 00011111 1110 | 000 0000 0000 0000 0000 0001111 1111 1111 1111 1111 1111000 0000 0000 0000 0000 0000111 1111 1111 1111 1111 1111 | 2-23×2-126(1-ε)×2-1261×2-126(2-ε)×2127 | ≈1.4×10-45≈1.2×10-38≈1.2×10-38≈3.4×1038 |
| 1 | 0111 1111 | 000 0000 0000 0000 0000 0000 | 1×20 | 1.0 |
规格化和非规格化值非负数值范围从上表中可以看出,由于在非规格化中将指数域数值E定义为E = 1-Bias,实现了其最大值与规格化的最小值平滑的过渡(最大的非规格化数为(1-ε)×2
-126,只比最小的规格化数2
-126小一点点,ε为2
-24)。 2、特殊数值。当指数域全为1时属于这种情形。此时,如果小数域全为0且符号域S=0,则表示正无穷+∞,如果小数域全为0且符号域S=1,则表示负无穷-∞。如果小数域不全为0时,浮点数将被解释为NaN,即不是一个数(Not a Number)。比如计算负数平方根或者处理未初始化数据时。以下是理清各种数据之间关系的总结:1. 浮点数值V = (-1)
s×M×2
E。2. 在32位和64位浮点数中,符号域s均为1位,小数域位数n分别为23位和52位,指数域位数k分别为8位和11位。3. 对于规格化编码,有效数M = 1+f,指数E = e-Bias(e即为k位的指数域二进制数据,对于32位浮点数而言e的范围是1~254,此时E的范围是-126~127)。4. 对于非规格化编码,有效数M = f,指数E = 1-Bias(这是一个常量)。5. Bias为偏置值,Bias = 2
k-1-1,k即为指数域位数,在32位和64位浮点数中k分别为8和11。6. f为小数域的二进制表示值,即n位的小数域f
n-1f
n-2…f
1f
0将被解释为f=(0.f
n-1f
n-2…f
1f
0)
2。有了以上的背景知识之后,我们就可以更从容地分析浮点运算了。毕竟我们不是数学家,学习浮点数不是为了科学研究,更多地是从实用主义的角度出发,是为了要写出更好更可靠的代码。
更多详情见请继续阅读下一页的精彩内容: http://www.linuxidc.com/Linux/2014-05/101243p2.htm