文章目录
- 前言
- 整数表示的缺陷
- 定点小数
- 定点小数加法乘法运算
- 浮点数
- IEEE754浮点数标准
- 移码
- 阶码的移码表示
- IEEE754中的特殊点
- 两个0
- 非规格化数字
- 正常浮点数
- 无穷大
- NaN
- 浮点数简单举例
- 浮点数一些其余特性
- 浮点数计算不符合结合律
- 浮点数舍入规则
- 浮点数与整数之间的相互转换
- 总结
前言
本文会详细解释浮点数在计算机中的表示,读者需要有简单二进制无符号数,补码表示的基本知识即可。耐心看完就能掌握计算机中的浮点数的表示及其特点。文中我已经尽量多用图、表帮助大家理解。
整数表示的缺陷
对于 N b i t Nbit Nbit 表示的无符号数,其可以表示从 0 − ( 2 N − 1 ) 0 - (2^{N}-1) 0−(2N−1)范围内的所有整数。当 N = 32 N=32 N=32 时,其表示的最大值为 ( 2 32 − 1 ) = 4 , 294 , 967 , 295 (2^{32}-1)=4,294,967,295 (232−1)=4,294,967,295
对于用补码表示的 N b i t Nbit Nbit 表示的有符号数,其可以表示从 ( − 2 N − 1 ) − ( 2 N − 1 − 1 ) (-2^{N-1})-(2^{N-1}-1) (−2N−1)−(2N−1−1) 范围内的所有整数。当 N = 32 N=32 N=32 时,其表示的最大值为 ( 2 31 − 1 ) = 2 , 147 , 483 , 647 (2^{31}-1)=2,147,483,647 (231−1)=2,147,483,647
缺点在于我们无法表示特别大的小数以及特别小的小数以及小数。
例如: 3.155692611 × 2 10 3.155692611×2^{10} 3.155692611×210 或 3.155692611 × 2 − 11 3.155692611×2^{-11} 3.155692611×2−11 或一个简单的 1.5 1.5 1.5
定点小数
定点小数,即小数点位置固定的小数表示方法。在十进制中,以小数 11.1101 11.1101 11.1101 为例,其每一位的位权如下图所示:
和十进制表示类似,大家可以猜到在二进制表示中,以 6 b i t 6bit 6bit 形式定点小数 x x . y y y y xx.yyyy xx.yyyy 为例,其每一位的位权则如下图所示:
此时, 11.110 1 2 11.1101_2 11.11012 表示的小数十进制下其值为: 2 + 1 + 0.5 + 0.25 + 0.0625 = 3.8125 2+1+0.5+0.25+0.0625=3.8125 2+1+0.5+0.25+0.0625=3.8125
按照上方定点小数表示方法,可表示的数字范围最小值: x y x\ y x y 全 0 0 0 则为 0 0 0,最大值: x y x\ y x y 全 1 1 1 则为 2 + 1 + 0.5 + 0.25 + 0.125 + 0.0625 = 3.9375 2+1+0.5+0.25+0.125+0.0625=3.9375 2+1+0.5+0.25+0.125+0.0625=3.9375。表示整数部分的长度为 m m m,表示小数部分的长度为 n n n,最大值可表示为 ( 2 m − 2 − n ) (2^m-2^{-n}) (2m−2−n)。简单理解:全 1 1 1 表示的二进制小数 11.111 1 2 11.1111_2 11.11112 加 00.000 1 2 = 2 − n 00.0001_2=2^{-n} 00.00012=2−n 即为 100.000 0 2 = 2 m 100.0000_2=2^m 100.00002=2m。
定点小数加法乘法运算
为了过程简单, 1.5 1.5 1.5 用上方二进制定点小数但小数部分少一位来表示为 01.10 0 2 01.100_2 01.1002,0.5表示为 00.10 0 2 00.100_2 00.1002。加法则按顺序排列,位权相同对应位置相加,乘法和十进制数字加法一样,分别如下图左右所示。
使用上述定点小数表示法仍旧有效无法表示特别大和特别小的小数。例如: 3.155692611234 × 2 10 3.155692611234×2^{10} 3.155692611234×210 或 3.155692611 × 2 − 11 3.155692611×2^{-11} 3.155692611×2−11
浮点数
我们先以十进制科学计数法数字: 6.02 × 1 0 23 6.02×10^{23} 6.02×1023 为例, 6.02 6.02 6.02 是尾数 ( s i g n i f i c a n d ) (significand) (significand), 23 23 23 是阶码 ( e x p o n e n t ) (exponent) (exponent), 10 10 10 是基数 ( b a s e / r a d i x ) (base/radix) (base/radix)。其中,规格化的要求中尾数的最高有效位不为 0 0 0 且小数点左侧只能有一位。
规格化: 1.0 × 1 0 − 9 1.0×10^{-9} 1.0×10−9,不能是 0.1 × 1 0 − 8 0.1×10^{-8} 0.1×10−8,也不能是 10.0 × 1 0 − 10 10.0×10^{-10} 10.0×10−10。
同理扩展到二进制之中,则以 1.0 1 2 × 2 − 1 1.01_2×2^{-1} 1.012×2−1 为例, 1.01 1.01 1.01 为尾数, − 1 -1 −1 为阶码, 2 2 2 为基数。其中,由于二进制中只有 0 0 0 和 1 1 1 ,除了 0 0 0 之外的每一个规格化之后的二进制小数小数点左侧都为 1 1 1。这就是浮点数。用科学计数法形式更准确表示规格化之后的数字为如下形式: 1. x x x . . . x ∗ 2 y y y . . . y 1.xxx...x * 2^{yyy...y} 1.xxx...x∗2yyy...y 。
IEEE754浮点数标准
二进制中除了 0 0 0 之外的所有小数规格化之后小数点左侧都为 1 1 1,标准在表示尾数时把它省略,尾数部分只表示 x x x . . . x xxx...x xxx...x 部分,阶码用于表示 y y y . . . y yyy...y yyy...y 部分,首位数符表示数据的正负。 32 b i t 32bit 32bit 单精度浮点数的各个字段及其长度如下图所示:
上述 I E E E 754 IEEE754 IEEE754 标准中,由一组 32 b i t 32bit 32bit 二进制数计算其表示的小数的公式为: ( − 1 ) S ∗ ( 1.0 + s i g n i f i c a n d ) ∗ 2 E x p o n e n t − 127 \color{red}(-1)^S*(1.0+significand)*2^{Exponent-127} (−1)S∗(1.0+significand)∗2Exponent−127。其阶码要减掉 127 127 127 的原因在于标准采用了移码来表示。具体随后详细解释。
此时,一位表示数符, 8 8 8 位用于表示阶码,阶码位数越多,则表示的范围越大。 23 23 23 位表示尾数,尾数位数越多,则表示的精度越高。
最低有效位 L e a s t S i g n i f i c a n t B i t Least \ Significant \ Bit Least Significant Bit 的位权为 2 − 23 2^{-23} 2−23 ,具体可类比上方定点小数表示中小数位数及其位权的关系。
移码
移码本身非常容易理解。对于 N b i t Nbit Nbit 无符号数,其表示的数据范围是 [ 0 , 2 N − 1 ] [0, 2^N-1] [0,2N−1]。移码的特性在于在无符号数的基础上添加了一个 b i a s bias bias 偏移量。其表示的范围变为 [ − b i a s , 2 N − 1 − b i a s ] [-bias,\ 2^N-1-bias] [−bias, 2N−1−bias]。直观来说:移码用 a + b i a s a+bias a+bias 的二进制序列来存储 a a a 的值。其规律如下图所示:
移码最直观的特点:越小的数字,其二进制表示下的无符号数也越小。这为浮点数比较提供了方便。
对于 N b i t Nbit Nbit 表示的移码,为了保持移码表示的正负数的数量基本一致,其偏移量一般取值为 2 N − 1 − 1 2^{N-1}-1 2N−1−1。对于 8 b i t 8bit 8bit一般的便宜量取值为 127 127 127。这也是 I E E E 754 IEEE754 IEEE754 标准的取值。上方未标明 b i a s bias bias 的取值,但可观察到 b i a s bias bias 为 3 3 3。
注:课程 C S 61 C CS61C CS61C 在数据表示课程中的移码把 b i a s bias bias 理解为负数,例如取偏移量为 − 3 -3 −3,而获得表示值的过程变为其无符号数加上 b i a s bias bias。这和我们的 b i a s bias bias 取正,获取到真值的过程为无符号数减掉 b i a s bias bias 异曲同工。加负数和减掉正数的区别。标准中为我们理解的 b i a s bias bias 取正值。
阶码的移码表示
阶码用 8 8 8 位表示,由于每一个符合标准的小数都必须经过规格化,当阶码不相等时,则比较阶码,阶码相等再去比较尾数。此时为了在没有特定浮点数硬件的机器上支持浮点数,例如:使用整数比较指令对浮点数进行排序,标准制定者对阶码使用移码表示。
若使用补码表示,那么当阶码为负数时,其二进制序列表示的无符号整数比阶码为正数时更大,例如: 32 b i t 32bit 32bit 的 − 1 -1 −1 和 1 1 1 用补码表示分别为: 0 x 11111111 0x11111111 0x11111111 和 0 x 00000001 0x00000001 0x00000001。
设计者选择移码来表示阶码。为 32 b i t 32bit 32bit 的单精度浮点数中 8 b i t 8bit 8bit 移码选择的 b i a s bias bias 偏移量为 127 127 127。其余长度下浮点数各字段长度如下表所示:
我们知道按照 b i a s bias bias 为 127 127 127 来计算,其移码可以表示的范围是 [ − 127 , 128 ] [-127, 128] [−127,128],区间两个端点 − 127 -127 −127 对应的阶码二进制为 8 8 8 位全 0 0 0, 128 128 128 对应阶码二进制为 8 8 8 位全 1 1 1,这两个端点在标准中都有特殊意义,故在表格中没有他们。
IEEE754中的特殊点
我们知道 I E E E 754 IEEE754 IEEE754 标准中规定了阶码的表示值的有效范围为 [ − 126 , 127 ] [-126, 127] [−126,127] ,其两个端点的特殊情况以及尾数的取值的具体意义如下表所示:
阶码为 0 0 0,尾数为 0 0 0,表示数字 0 0 0。
阶码为 0 0 0,尾数不为 0 0 0,表示非规格化小数。
阶码的范围 [ 1 , 254 ] [1, 254] [1,254] 对应表示值范围为 [ − 126 , 127 ] [-126, 127] [−126,127] 。无特殊意义,正常表示浮点数。
阶码为 255 255 255,尾数为 0 0 0,表示无穷大,符号位的正负作为正负无穷大的区分。
阶码为 255 255 255,尾数不为 0 0 0,表示 N o t a n u m b e r ( N a N ) Not \ a \ number(NaN) Not a number(NaN)
正式介绍这几种情况之前,先给出一个初步不全面的浮点数的表示范围。
最小正数:数符为正,阶码最小: − 126 -126 −126,尾数最小: 0 0 0。其表示的浮点数为: 1.0 ∗ 2 − 126 ≈ 1.1754 ∗ 1 0 − 38 1.0*2^{-126} \approx 1.1754*10^{-38} 1.0∗2−126≈1.1754∗10−38
最大正数:数符为正,阶码最大: 127 127 127,尾数最大: 23 b i t 23bit 23bit 全 1 1 1,其表示的浮点数为: ( 1 + 0.5 + . . . + 2 − 23 ) ∗ 2 127 = ( 2 − 2 − 23 ) ∗ 2 127 ≈ 3.4028235 ∗ 1 0 38 (1+0.5+...+2^{-23})*2^{127}=(2-2^{-23})*2^{127} \approx 3.4028235*10^{38} (1+0.5+...+2−23)∗2127=(2−2−23)∗2127≈3.4028235∗1038
最小负数:数符为负,阶码最大: 127 127 127,尾数最大: 23 b i t 23bit 23bit 全 1 1 1,其表示的浮点数为: ( − 1 ) ∗ ( 1 + 0.5 + . . . + 2 − 23 ) ∗ 2 127 = − ( 2 − 2 − 23 ) ∗ 2 127 ≈ − 3.4028235 ∗ 1 0 38 (-1)*(1+0.5+...+2^{-23})*2^{127}=-(2-2^{-23})*2^{127} \approx -3.4028235*10^{38} (−1)∗(1+0.5+...+2−23)∗2127=−(2−2−23)∗2127≈−3.4028235∗1038
最大负数:数符为负,阶码最小: − 126 -126 −126,尾数最小: 0 0 0,其表示的浮点数为: − 1.0 ∗ 2 − 126 ≈ − 1.1754 ∗ 1 0 − 38 -1.0*2^{-126} \approx -1.1754*10^{-38} −1.0∗2−126≈−1.1754∗10−38
用数轴表示其范围如下所示:
两个0
对于除了 0 0 0 之外的小数,规格化之后的尾数 s i g n i f i c a n d significand significand 范围永远在 ( 0 , 1 ) (0,1) (0,1) 之间。由于 0 0 0 的小数表示没有小数点左侧的 1 1 1 ,标准规定阶码和尾数为 0 0 0 用于表示数字 0 0 0。注意到符号位可以为 1 1 1,表示负数。所以我们有两个 0 0 0。 32 b t i 32bti 32bti 全 0 0 0 表示 0 0 0;符号位为 1 1 1,其余全 0 0 0 表示 − 0 -0 −0。但他们都是 0 0 0。经过下图程序验证, 0 = = − 0 0==-0 0==−0。
#include <iostream>
#include <cstdio>
union num{float f_num;int32_t field;
};
int main(){std::cout<<"hello,world"<<std::endl;std::cout<<"sizeof(float)=="<<sizeof(float)<<std::endl;num f1;f1.field=0;num f2;f2.field = 0x80000000;std::cout<<"f1 = "<<f1.f_num<<std::endl;std::cout<<"f2 = "<<f2.f_num<<std::endl;if(f1.f_num == f2.f_num)std::cout<<"0 == 0x80000000"<<std::endl;else std::cout<<"0 != 0x80000000" <<std::endl;return 0;
}
环境为 64 b i t w i n d o w s 64bit \ windows 64bit windows下 v s c o d e + m i n g w 64 vscode+mingw64 vscode+mingw64 时,程序输出如下图所示:
另外,当所有位置全 0 0 0 时,按照浮点数公式的严格定义下,其表示的数字应该为 1.0 ∗ 2 0 − 127 = 2 − 127 1.0*2^{0-127}=2^{-127} 1.0∗20−127=2−127 不为 0 0 0 但无限接近于 0 0 0 ,下面我们会知道阶码 0 0 0 表示的非规格化数,不能按照规格化的计算方式来计算其表示的值,标准则规定全 0 0 0 表示 0 0 0。
非规格化数字
根据数轴我们知道,正常情况之下标准能够表示的最小正数和最大正数为 + 2 − 126 +2^{-126} +2−126 和 − 2 − 126 -2^{-126} −2−126 。此时我们想要表示更小的非 0 0 0 小数则会超过标准阶码的表示范围引发下溢。从 0 0 0 到 + 2 − 126 +2^{-126} +2−126 之间没有内容,无法表示。
此时为了减小下溢的概率,标准规定:阶码为0,尾数不为0则表示非规格化数字,其指数默认为-126。其表示的小数计算公式变为: ( − 1 ) S ∗ ( s i g n i f i c a n d ) ∗ 2 − 126 \color{red}(-1)^S*(significand)*2^{-126} (−1)S∗(significand)∗2−126 。其中,按照移码的规则阶码为0表示值为 − 127 -127 −127,但标准规定非规格化数字的阶码默认为 − 126 -126 −126 不变,所有非规格化数字阶码全 0 0 0 一样。
此时我们能够表示的最小正数变为:数符为正,阶码为 0 0 0,尾数为 1 1 1,其表示的值为: 2 − 23 ∗ 2 − 126 = 2 0 ∗ 2 − 149 = 2 − 149 2^{-23}*2^{-126}=2^0*2^{-149}=2^{-149} 2−23∗2−126=20∗2−149=2−149
非规格化数能够表示的下一个数字:数符为正,阶码为 0 0 0,尾数为 2 2 2,其二进制序列为: 0 b 00000000000000000000010 0b00000000000000000000010 0b00000000000000000000010,其表示的值为: 2 − 22 ∗ 2 − 126 = 2 − 148 = 2 1 ∗ 2 − 149 2^{-22}*2^{-126}=2^{-148}=2^1*2^{-149} 2−22∗2−126=2−148=21∗2−149
非规格化数能够表示的下一个数字:数符为正,阶码为 0 0 0,尾数为 3 3 3,其二进制序列为: 0 b 00000000000000000000011 0b00000000000000000000011 0b00000000000000000000011,其表示的值为: ( 2 − 22 + 2 − 23 ) ∗ 2 − 126 = 2 − 148 + 2 − 149 = ( 2 0 + 2 1 ) ∗ 2 − 149 (2^{-22}+2^{-23})*2^{-126}=2^{-148}+2^{-149}=(2^0+2^1)*2^{-149} (2−22+2−23)∗2−126=2−148+2−149=(20+21)∗2−149
… …
… …
非规格化数能够表示的最后一个数字:数符为正,阶码为 0 0 0,尾数为全 1 1 1,其二进制序列为: 0 b 11111111111111111111111 0b11111111111111111111111 0b11111111111111111111111,其表示的值为: ( 2 − 1 + . . . + 2 − 22 + 2 − 23 ) ∗ 2 − 126 = 2 − 127 + . . . + 2 − 148 + 2 − 149 = ( 2 22 + 2 21 + . . . + 2 1 + 2 0 ) ∗ 2 − 149 (2^{-1}+...+2^{-22}+2^{-23})*2^{-126}=2^{-127}+...+2^{-148}+2^{-149}=(2^{22}+2^{21}+...+2^1+2^0)*2^{-149} (2−1+...+2−22+2−23)∗2−126=2−127+...+2−148+2−149=(222+221+...+21+20)∗2−149
另外从另一方面考虑这个结果, ( 2 − 1 + . . . + 2 − 22 + 2 − 23 ) ∗ 2 − 126 = ( 1 − 2 − 23 ) ∗ 2 − 126 = 2 − 126 − 2 − 149 (2^{-1}+...+2^{-22}+2^{-23})*2^{-126}=(1-2^{-23})*2^{-126}=2^{-126}-2^{-149} (2−1+...+2−22+2−23)∗2−126=(1−2−23)∗2−126=2−126−2−149
规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为全 0 0 0 其表示的值为: 1.0 ∗ 2 ∗ 1 − 127 = 2 − 126 1.0*2*{1-127}=2^{-126} 1.0∗2∗1−127=2−126
此时,尾数加 1 1 1,表示的小数值加 2 − 149 2^{-149} 2−149。换一种理解方式为从 0 0 0 到 2 − 126 2^{-126} 2−126 之间的步长为 2 − 149 \color{red}2^{-149} 2−149。尾数从全 0 0 0 到全 1 1 1,一共 2 23 2^{23} 223 种取值,我们理解为区间分做 2 23 2^{23} 223 份。具体见下表。
数符 | 阶码 | 尾数 | 真值 |
---|---|---|---|
0 | 0b00000000 | 0b00000000000000000000000 | 0 |
0 | 0b00000000 | 0b00000000000000000000001 | 2 − 149 2^{-149} 2−149 |
0 | 0b00000000 | 0b00000000000000000000010 | 2 ∗ 2 − 149 2*2^{-149} 2∗2−149 |
0 | 0b00000000 | … … | … … |
0 | 0b00000000 | 0b11111111111111111111111 | 2 − 126 − 2 − 149 2^{-126}-2^{-149} 2−126−2−149 |
而浮点数最大值能够表示到 3.4 ∗ 1 0 38 3.4*10^{38} 3.4∗1038,一直保持这么小的步长必然不行。
正常浮点数
书接上文,我们将揭示为什么浮点数能够表示数据范围如此之大,重点关注每次阶码加 1 1 1 之后区间的两个端点和步长的变化。
阶码为 1 1 1的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 1 − 127 = 2 − 126 + 2 − 149 (1.0+2^{-23})*2^{1-127}=2^{-126}+2^{-149} (1.0+2−23)∗21−127=2−126+2−149
… …
阶码为 1 1 1 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 1 − 127 = 2 − 126 + 2 − 127 + . . . + 2 − 149 (1.0+2^{-1}+...+2^{-23})*2^{1-127}=2^{-126}+2^{-127}+...+2^{-149} (1.0+2−1+...+2−23)∗21−127=2−126+2−127+...+2−149。
阶码为 2 2 2 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 2 − 127 = 2 − 125 (1.0)*2^{2-127}=2^{-125} (1.0)∗22−127=2−125
此时,从 2 − 126 2^{-126} 2−126 到 2 − 125 2^{-125} 2−125 之间步长仍旧为 2 − 149 \color{red}2^{-149} 2−149,区间分做 2 23 2^{23} 223 份。
此时完善表格如下:
数符 | 阶码 | 尾数 | 真值 |
---|---|---|---|
0 | 0b00000000 | 0b00000000000000000000000 | 0 |
0 | 0b00000000 | 0b00000000000000000000001 | 2 − 149 2^{-149} 2−149 |
0 | 0b00000000 | 0b00000000000000000000010 | 2 ∗ 2 − 149 2*2^{-149} 2∗2−149 |
0 | 0b00000000 | … … | … … |
0 | 0b00000000 | 0b11111111111111111111111 | 2 − 126 − 2 − 149 2^{-126}-2^{-149} 2−126−2−149 |
0 | 0b00000001 | 0b00000000000000000000000 | 2 − 126 2^{-126} 2−126 |
0 | 0b00000001 | 0b00000000000000000000001 | 2 − 126 + 2 − 149 2^{-126}+2^{-149} 2−126+2−149 |
0 | 0b00000001 | 0b00000000000000000000010 | 2 − 126 + 2 ∗ 2 − 149 2^{-126}+2*2^{-149} 2−126+2∗2−149 |
0 | 0b00000001 | … … | … … |
0 | 0b00000001 | 0b11111111111111111111111 | 2 − 125 − 2 − 149 2^{-125}-2^{-149} 2−125−2−149 |
0 | 0b00000010 | 0b00000000000000000000000 | 2 − 125 2^{-125} 2−125 |
阶码为 2 2 2 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 2 − 127 = 2 − 125 + 2 0 ∗ 2 − 148 (1.0+2^{-23})*2^{2-127}=2^{-125}+2^0*2^{-148} (1.0+2−23)∗22−127=2−125+20∗2−148
… …
阶码为 2 2 2 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 2 − 127 = 2 − 125 + 2 − 127 + . . . + 2 − 148 (1.0+2^{-1}+...+2^{-23})*2^{2-127}=2^{-125}+2^{-127}+...+2^{-148} (1.0+2−1+...+2−23)∗22−127=2−125+2−127+...+2−148。
阶码为3的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000011 0b00000011 0b00000011,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 3 − 127 = 2 − 124 (1.0)*2^{3-127}=2^{-124} (1.0)∗23−127=2−124
此时,从 2 − 125 2^{-125} 2−125 到 2 − 124 2^{-124} 2−124 之间步长已经变为 2 − 148 \color{blue}2^{-148} 2−148。
此时完善表格如下:
数符 | 阶码 | 尾数 | 真值 |
---|---|---|---|
0 | 0b00000000 | 0b00000000000000000000000 | 0 |
0 | 0b00000000 | 0b00000000000000000000001 | 2 − 149 2^{-149} 2−149 |
0 | 0b00000000 | 0b00000000000000000000010 | 2 ∗ 2 − 149 2*2^{-149} 2∗2−149 |
0 | 0b00000000 | … … | … … |
0 | 0b00000000 | 0b11111111111111111111111 | 2 − 126 − 2 − 149 2^{-126}-2^{-149} 2−126−2−149 |
0 | 0b00000001 | 0b00000000000000000000000 | 2 − 126 2^{-126} 2−126 |
0 | 0b00000001 | 0b00000000000000000000001 | 2 − 126 + 2 − 149 2^{-126}+2^{-149} 2−126+2−149 |
0 | 0b00000001 | 0b00000000000000000000010 | 2 − 126 + 2 ∗ 2 − 149 2^{-126}+2*2^{-149} 2−126+2∗2−149 |
0 | 0b00000001 | … … | … … |
0 | 0b00000001 | 0b11111111111111111111111 | 2 − 125 − 2 − 149 2^{-125}-2^{-149} 2−125−2−149 |
0 | 0b00000010 | 0b00000000000000000000000 | 2 − 125 2^{-125} 2−125 |
0 | 0b00000010 | 0b00000000000000000000001 | 2 − 125 + 2 − 148 2^{-125}+2^{-148} 2−125+2−148 |
0 | 0b00000010 | 0b00000000000000000000010 | 2 − 125 + 2 ∗ 2 − 148 2^{-125}+2*2^{-148} 2−125+2∗2−148 |
0 | 0b00000010 | … … | … … |
0 | 0b00000010 | 0b11111111111111111111111 | 2 − 124 − 2 − 148 2^{-124}-2^{-148} 2−124−2−148 |
0 | 0b00000011 | 0b00000000000000000000000 | 2 − 124 2^{-124} 2−124 |
… …
… …
中间省略掉大部分,我们跳到阶码为 127 127 127,二进制表示为 0 b 01111111 0b01111111 0b01111111,其表示值为 0 0 0 。读者可以猜测,此时区间两端点为多少?
阶码为127的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 127 − 127 = 2 0 = 1 (1.0)*2^{127-127}=2^0=1 (1.0)∗2127−127=20=1。
阶码为 127 127 127 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 127 − 127 = 2 0 + 2 − 23 = 1 + 2 − 23 (1.0+2^{-23})*2^{127-127}=2^0+2^{-23}=1+2^{-23} (1.0+2−23)∗2127−127=20+2−23=1+2−23。
… …
阶码为 127 127 127 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 127 − 127 = 2 0 + 2 − 1 + . . . + 2 − 23 (1.0+2^{-1}+...+2^{-23})*2^{127-127}=2^{0}+2^{-1}+...+2^{-23} (1.0+2−1+...+2−23)∗2127−127=20+2−1+...+2−23。
阶码为128的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10000000 0b10000000 0b10000000,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 128 − 127 = 2 1 = 2 (1.0)*2^{128-127}=2^{1}=2 (1.0)∗2128−127=21=2
此时,从 1 1 1 到 2 2 2 之间步长已经变为 2 − 23 \color{blue}2^{-23} 2−23。还是发现不了规律?我们继续看表
数符 | 阶码 | 尾数 | 真值 |
---|---|---|---|
0 | 0b00000000 | 0b00000000000000000000000 | 0 |
0 | 0b00000000 | 0b00000000000000000000001 | 2 − 149 2^{-149} 2−149 |
0 | 0b00000000 | 0b00000000000000000000010 | 2 ∗ 2 − 149 2*2^{-149} 2∗2−149 |
0 | … … | … … | … … |
0 | 0b01111111 | 0b00000000000000000000000 | 1 1 1 |
0 | 0b1111111 | … … | … … |
0 | 0b01111111 | 0b11111111111111111111111 | 1 − 2 23 1-2^{23} 1−223 |
0 | 0b10000000 | 0b00000000000000000000000 | 2 2 2 |
… …
… …
中间再省略掉大部分,我们跳到阶码为 150 150 150,二进制表示为 0 b 10010110 0b10010110 0b10010110,其表示值为 23 23 23 。读者可以猜测,此时步长为多少?
阶码为 150 150 150 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10010110 0b10010110 0b10010110,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 150 − 127 = 2 23 (1.0)*2^{150-127}=2^{23} (1.0)∗2150−127=223。
阶码为 150 150 150 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 150 − 127 = 2 23 + 1 (1.0+2^{-23})*2^{150-127}=2^{23}+1 (1.0+2−23)∗2150−127=223+1。
… …
阶码为 150 150 150 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 150 − 127 = 2 23 + 2 22 + . . . + 2 0 = 2 24 − 1 (1.0+2^{-1}+...+2^{-23})*2^{150-127}=2^{23}+2^{22}+...+2^{0}=2^{24}-1 (1.0+2−1+...+2−23)∗2150−127=223+222+...+20=224−1。
阶码为 151 151 151 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10000000 0b10000000 0b10000000,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 151 − 127 = 2 24 (1.0)*2^{151-127}=2^{24} (1.0)∗2151−127=224
此时,从区间 2 23 2^{23} 223 到 2 24 2^{24} 224 之间步长已经变为 1 \color{blue}1 1。 a m a z i n g ! ! ! \color{red}amazing ! ! ! amazing!!!
此时,再问一个问题:阶码为 151 151 151 时,步长和区间两端点各为多少?分别是 2 2 2 和 [ 2 24 , 2 25 ] [2^{24}, 2^{25}] [224,225]
… …
… …
阶码为 254 254 254 时,其表示值为 127 127 127 ,此时步长为 2 104 2^{104} 2104,区间端点变为 [ 2 127 , + ∞ ] [2^{127}, +∞] [2127,+∞],无穷大随后详细解释。
至此,我们已经揭示了规律:阶码从1开始,阶码每次加1,步长乘2,区间两端点乘以2。
另外,区间内永远分作 2 23 2^{23} 223 份。这也是为什么,浮点数能够表示 3.4 ∗ 1 0 38 3.4*10^{38} 3.4∗1038 这么大的数字。
无穷大
书接上文。标准规定,阶码为 255 255 255,尾数为 0 0 0,表示无穷大。数符用来区分正无穷和负无穷。
我们知道,按照移码的规律和标准规定,阶码为 254 254 254,表示值 127 127 127 已经为 32 b i t 32bit 32bit 单精度浮点数能够表示的最大阶码。
阶码为 254 254 254 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 11111110 0b11111110 0b11111110,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 254 − 127 = 2 127 + 2 126 + . . . + 2 104 (1.0+2^{-1}+...+2^{-23})*2^{254-127}=2^{127}+2^{126}+...+2^{104} (1.0+2−1+...+2−23)∗2254−127=2127+2126+...+2104。
此时,再进一步,浮点数能够表示的下一个数应当是阶码为255的规格化数能够表示的第一个数。即:阶码为 255 255 255,尾数为 0 0 0。标准将其规定为无穷大。非常合理。
NaN
无穷大之后再进一步,标准规定阶码为 255 255 255,尾数非 0 0 0 为 N o t a N u m b e r Not \ a \ Number Not a Number。
读者可通过一些协商/协议/标准来自定义其 N a N NaN NaN 具体类型,例如:尾数为 1 1 1,表示对负数做平方根,尾数为 2 2 2,表示除以 0 0 0 的非法操作等等等。
浮点数简单举例
对于一个浮点数二进制序列: 0 b 1 10000001 11100000000000000000000 0b1 \ 10000001 \ 11100000000000000000000 0b1 10000001 11100000000000000000000,计算其表示值为: ( − 1 ) ∗ ( 1.0 + 0.5 + 0.25 + 0.125 ) ∗ 2 129 − 127 = − 7.5 (-1)*(1.0+0.5+0.25+0.125)*2^{129-127}=-7.5 (−1)∗(1.0+0.5+0.25+0.125)∗2129−127=−7.5
如何表示 1 / 3 1/3 1/3 ?
1 / 3 = 0.333333333... = 0.25 + 0.0625 + 0.015625 + . . . = 1 / 4 + 1 / 16 + 1 / 64 + 1 / 256 + . . . = 2 − 2 + 2 − 4 + 2 − 8 + . . . 1/3 = 0.333333333...=0.25+0.0625+0.015625+...=1/4+1/16+1/64+1/256+...=2^{-2}+2^{-4}+2^{-8}+... 1/3=0.333333333...=0.25+0.0625+0.015625+...=1/4+1/16+1/64+1/256+...=2−2+2−4+2−8+...
二进制小数表示为 0.010101010101.. . 2 ∗ 2 0 0.010101010101..._{2} *2^0 0.010101010101...2∗20,规格化之后变为: 1.01010101.. . 2 ∗ 2 − 2 1.01010101..._2 *2^{-2} 1.01010101...2∗2−2。此时转为 32 b i t 32bit 32bit 标准表示:数符为0,阶码为 − 2 + 127 = 125 = 0 b 01111101 -2+127=125=0b01111101 −2+127=125=0b01111101,尾数为: 0 b 01010101010101010101010 0b0101 0101 0101 0101 0101 010 0b01010101010101010101010。
综上, 1 / 3 1/3 1/3 的浮点数表示为 0 b 0 01111101 01010101010101010101010 0b0 \ 01111101 \ 01010101010101010101010 0b0 01111101 01010101010101010101010
浮点数一些其余特性
浮点数计算不符合结合律
假设 x = 1.5 ∗ 1 0 38 x=1.5*10^{38} x=1.5∗1038 , y = − 1.5 ∗ 1 0 38 y=-1.5*10^{38} y=−1.5∗1038, z = 1 z=1 z=1, x + y + z x+y+z x+y+z 的结果和 x + z + y x+z+y x+z+y 的结果不一致。
验证程序如下所示:
#include <iostream>int main(){float f1=1.5e38,f2=-1.5e38,f3=1.0;std::cout<<"(1.5e38+(-1.5e38))+1.0 = "<<f1+f2+f3<<std::endl;std::cout<<"(1.5e38+1.0)+(-1.5e38) = "<<f1+f3+f2<<std::endl;return 0;
}
程序输出如下所示:
主要问题在于:浮点数的加法执行流程粗略来讲为:首先要对两个小数统一阶码,阶码统一之后才可以尾数相加,尾数相加之后再重新规格化。这其中 23 b i t 23bit 23bit 精度的限制必然会导致位的缺失。
浮点数舍入规则
既然尾数位数有限,必然要有舍入的规则来确定多余的位如何处理。浮点数的舍入规则为舍入到偶数。
对于普通小数,例如:2.4则四舍五入为2,2.6则四舍五入为3。对于中间位置,例如:2.5舍入为2,3.5舍入为4。
上述规则是在十进制下的,二进制下同理,以定点小数为例: 11. 1 2 11.1_2 11.12舍入时考虑到前方为 1 1 2 11_2 112,为奇数,则舍入为 10 0 2 100_2 1002。 10. 1 2 10.1_2 10.12 舍入时则直接丢弃小数部分,舍入为 1 0 2 10_2 102 。下标 2 2 2 表示二进制。
浮点数与整数之间的相互转换
考虑以下问题:一个整数,强制类型转换为浮点数再转为整数是否还和原来相等?
uint32_t i;
if(i == (uint32_t)(float)i){printf("true!");
}
答案是不全部相等。根据上文的区间分析我们知道,当阶码为 151 151 151 时,其表示值为 24 24 24,此时区间端点为 [ 2 24 , 2 25 ] [2^{24}, 2^{25}] [224,225],区间步长为 2 2 2。此时我们就定义整数初值为: 2 24 + 1 2^{24}+1 224+1,按照此时 2 2 2 的步长浮点数刚好无法表示,此时转换必然存在位丢失情况,而C语言中的浮点数到整数的类型转换仅仅是丢弃全部小数,只取整数: 2 24 = 16 , 777 , 216 2^{24}=16,777,216 224=16,777,216。
程序验证如下所示:
#include <iostream>
int main(){float f10=3.2f,f11=3.5f,f12=3.9f;std::cout<<"(uint32_t)3.2 = "<<(uint32_t)f10<<std::endl;std::cout<<"(uint32_t)3.5 = "<<(uint32_t)f11<<std::endl;std::cout<<"(uint32_t)3.9 = "<<(uint32_t)f12<<std::endl;std::cout<<"-------------------------"<<std::endl;uint32_t target=(1<<24)+1;std::cout<<"(float)(1<<24)+1 = "<<((float)target)<<std::endl;std::cout<<"(uint32_t)(float)(1<<24)+1 = "<<((uint32_t)(float)target)<<std::endl;return 0;
}
程序输出结果如下图所示:
正如我们猜测的结果一样。
考虑另外问题:一个浮点数,强制类型转换为整数再转为浮点数是否还和原来相等?
float f;
if(f == (float)(uint32_t)f){printf("true!");
}
当然不等。考虑 1.5 1.5 1.5,转为整数后变为 1 1 1,浮点数可以表示 1 1 1。故转换之后结果变为 1 1 1。
程序验证如下:
int main(){float f1=1.5f;std::cout<<"(float)(uint32_t)(1.5) = "<<(float)((uint32_t)(f1))<<std::endl;return 0;
}
结果输出如下图所示:
好!
总结
完结撒花!你已经完全了解计算机中小数表示了。