文章目录
- 前言
- 整数表示的缺陷
- 定点小数
- 定点小数加法乘法运算
- 浮点数
- 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;
}
结果输出如下图所示:
好!
总结
完结撒花!你已经完全了解计算机中小数表示了。