1 浮点数的一般表示
J f J_f Jf | J 1 J 2 … J m J_1J_2\dots J_m J1J2…Jm | S f S_f Sf | S 1 S 2 … S n S_1S_2\dots S_n S1S2…Sn |
---|---|---|---|
阶符 | 阶码 | 数符 | 尾数 |
阶码的位数决定了浮点数的表示范围的大小,尾数的位数决定了浮点数的表示精度
- 阶符:阶码的符号位。1为负;0为正
- 阶码:即幂的大小。设幂 e = ( J 1 J 2 … J m ) 2 e=(J_1J_2\dots J_m)_2 e=(J1J2…Jm)2
- 数符:尾数的符号位。1为负;0为正
- 尾数:尾数的大小。设尾数 M = ( S 1 S 2 … S n ) 2 M=(S_1S_2\dots S_n)_2 M=(S1S2…Sn)2
一般基数 r = 2 r=2 r=2,则浮点数真值 N = ( − 1 ) J f ∗ r e ∗ ( − 1 ) S f ∗ M N=(-1)^{J_f}*r^e*(-1)^{S_f}*M N=(−1)Jf∗re∗(−1)Sf∗M
2 IEEE 754标准的浮点数
2.1 浮点数的格式
m s m_s ms | E E E | M M M |
---|---|---|
数符 | 阶码,用移码表示 | 尾数,用原码表示 |
类型 | 数符 | 阶码 | 尾数 | 总位数 | 阶码偏置值(十进制) | 指数范围(真值表示) | 指数范围(移码表示) |
---|---|---|---|---|---|---|---|
短浮点数 | 1 | 8 | 23 | 32 | 127 | [-126,+127] | [+1,+254] |
长浮点数 | 1 | 11 | 52 | 64 | 1023 | [-1022,+1023] | [+1,+2046] |
临时浮点数 | 1 | 15 | 64 | 80 | 16383 | [-16382,+16383] | [+1,+32766] |
从上表可以看出,IEEE 754标准的浮点数有短浮点数(单精度、float型)、长浮点数(双精度、double型)、临时浮点数三种,且数据由三部分组成:
- 数符:尾数的符号位。1表示负;0表示正
- 阶码:表示数的幂,基为2,用移码表示
- 尾数:表示数的小数部分,基为2,用原码表示。且隐藏了一位1,这样是为了多表示一位有效位(临时浮点数无隐含的1)
因此可知,一个IEEE 754标准的浮点数的值为 N = ( − 1 ) m s ∗ 2 E ∗ ( 1. M ) 2 N=(-1)^{m_s}*2^E*(1.M)_2 N=(−1)ms∗2E∗(1.M)2
两点说明
1、关于阶码
1.1、为什么用移码表示
补码不能直观的表示数据的大小,比如一个8位的数据:用补码表示
(
−
1
)
10
=
(
11111111
)
2
=
F
F
H
,
(
8
)
10
=
(
00001000
)
2
=
08
H
(-1)_{10}=(11111111)_2=FFH,(8)_{10}=(00001000)_2=08H
(−1)10=(11111111)2=FFH,(8)10=(00001000)2=08H,8是大于-1的,但是补码的话 FFH > 08H,这与实际结果正好相反;而移码通过加上一个偏置值(若数据为 n 位,则通常取偏置值为
2
n
−
1
2^{n-1}
2n−1,将符号位取反即可),能够反映数据之间的实际大小关系,移码表示
(
−
1
)
10
=
(
01111111
)
2
=
7
F
H
,
(
8
)
10
=
(
10001000
)
2
=
88
H
(-1)_{10}=(01111111)_2=7FH,(8)_{10}=(10001000)_2=88H
(−1)10=(01111111)2=7FH,(8)10=(10001000)2=88H,88H > 7FH,这与预期结果相符合
1.2、阶码的偏置值
阶码部分用移码表示,假设阶码为 n 位,则规定阶码偏置值取
2
n
−
1
−
1
2^{n-1}-1
2n−1−1,因此短浮点数、长浮点数、临时浮点数阶码偏置值为
2
8
−
1
−
1
=
127
、
2
11
−
1
−
1
=
1023
、
2
15
−
1
−
1
=
16383
2^{8-1}-1=127、2^{11-1}-1=1023、2^{15-1}-1=16383
28−1−1=127、211−1−1=1023、215−1−1=16383。移码偏置值不是
2
n
−
1
2^{n-1}
2n−1吗,这里为什么取
2
n
−
1
−
1
2^{n-1}-1
2n−1−1,因为阶码全1是用来表示特殊用途的数,1.3条有详细解释,不理解的话直接将这当作一个规定记住即可
1.3、阶码的取值范围
假设阶码为 n 位,则可表示的范围为
0
到
2
n
−
1
0到2^{n}-1
0到2n−1,,因此短浮点数、长浮点数的阶码取值范围为 0到255、0到2047。又因为当阶码全0、阶码全1时有特殊用途,所以阶码E的实际取值范围为 1到254、1到2046、1到32766(去掉阶码全0和全1)。当阶码E为全0或全1时,要综合考虑尾数M的值,它们用来表示一些特殊的数:
- 0:当E全0,M为0时用来表示0值,至于是+0/-0,则取决于符号位是0还是1。
- subnormal:当E全0,M非0时,用来表示subnormal(中文译为非规则浮点数)。它表示那些比规格化浮点数能表示的最小量级的数还小的数,例如短浮点格式的一个数 1.11 ∗ 2 − 128 1.11*2^{-128} 1.11∗2−128,这已经超出了规格化形式能表示的范围,如果变成subnormal的话就是 0.0111 ∗ 2 − 126 0.0111*2^{-126} 0.0111∗2−126,前面说到,规格化浮点数的隐含位为1,而subnormals的隐含位可以看成0,这就是非规格化的含义所在。并且subnormals的尾数部分有若干前导0组成,因为它已经超出了规格化形式的范围,一个数越小,它的前导0越多,否则指数部分就不符合要求。
- 无穷大:当E全1,M为0时表示无穷大,至于是 + ∞ / − ∞ +\infty/-\infty +∞/−∞,则取决于符号位是0还是1。
- NaN(Not a Number):当E全1,M非0时,表示NaN。在IEEE浮点数格式中,NaN表示那些不是实数(real number)的值,例如
0
0
\frac{0}{0}
00。具体细分,有两类NaN,
quiet NaNs(QNaNs)
和signaling NaNs(SNaNs)
,QNaNs表示一个不确定的值,例如一个数除以无穷大或者无穷大乘0的结果;SNaNs则用于无效的操作,以表示浮点硬件异常。
1.4、阶码的实际大小
阶码采用移码的形式表示,阶码的实际大小需要减去对应的偏置值,通过这种方式来表示阶码的正负值。所以短浮点数、长浮点数的阶码实际大小为
E
−
偏置值
E-偏置值
E−偏置值,即
1
−
127
=
−
126
到
254
−
127
=
127
、
1
−
1023
=
−
1022
到
2046
−
1023
=
1023
1-127=-126到254-127=127、1-1023=-1022到2046-1023=1023
1−127=−126到254−127=127、1−1023=−1022到2046−1023=1023
2、关于尾数
假设尾数位数为 m 位,因为尾数部分隐含了一位整数1,所以尾数的实际位数为 m+1 位,因此短浮点数、长浮点数尾数实际有效位数为24、53,真值为 1. M 1.M 1.M,故:
短浮点数的真值为
(
−
1
)
m
s
∗
(
1.
M
)
∗
2
E
−
127
(-1)^{m_s}*(1.M)*2^{E-127}
(−1)ms∗(1.M)∗2E−127
长浮点数的真值为
(
−
1
)
m
s
∗
(
1.
M
)
∗
2
E
−
1023
(-1)^{m_s}*(1.M)*2^{E-1023}
(−1)ms∗(1.M)∗2E−1023
2.2 浮点数的取值范围
以短浮点正数为例:
当阶码和尾数都取最小时(E为0000 0001,M为0),表示的数值最小,阶码部分为 1 − 127 = − 126 1-127=-126 1−127=−126,尾数部分为 隐含的1加上其余的23位0;
当阶码和尾数都取最大时(E为1111 11110,M全1),表示的数值最大,阶码部分为 254 − 127 = 127 254-127=127 254−127=127,尾数部分为 隐含的1加上其余的23位1;
所以取值范围为 1.0 × 2 − 126 1.0\times 2^{-126} 1.0×2−126到 24 个 1 1.11 … 1 ⏞ × 2 127 \begin{matrix} 24个1 \\ \overbrace{ 1.11\dots 1 } \times 2^{127}\end{matrix} 24个11.11…1 ×2127,即
格式 | 正数 | 负数 |
---|---|---|
单精度 | 2 − 126 到 ( 2 − 2 − 23 ) × 2 127 2^{-126}到(2-2^{-23})\times 2^{127} 2−126到(2−2−23)×2127 | − 2 − 126 到 − ( 2 − 2 − 23 ) × 2 127 -2^{-126}到-(2-2^{-23})\times 2^{127} −2−126到−(2−2−23)×2127 |
双精度 | 2 − 1022 到 ( 2 − 2 − 52 ) × 2 1023 2^{-1022}到(2-2^{-52})\times 2^{1023} 2−1022到(2−2−52)×21023 | − 2 − 1022 到 − ( 2 − 2 − 52 ) × 2 1023 -2^{-1022}到-(2-2^{-52})\times 2^{1023} −2−1022到−(2−2−52)×21023 |
2.3 类型转换时的精度损失和溢出
这里以C语言为例。C语言中的float、double型分别对应IEEE 754标准的单精度和双精度浮点数,一个int型数据占4个字节、float占4字节、double占8字节。
1 溢出
- 当float、double向int转换时可能会发生溢出,比如有符号int型表示的数据范围为 2 − 31 到 2 31 − 1 2^{-31}到2^{31}-1 2−31到231−1,而float、double类型的数据的表示范围超过了int类型
- 当double向float、int转换时可能会发生溢出、float向int转换时也可能会溢出
2.精度损失
- 当int向float转换时可能会产生精度损失,因为int类型共四字节32位,而float尾数的有效位数为24位(包括隐含的1),当int型数据的有效位数超过24的话就会发生精度损失。比如
u n s i g n e d i n t m = F F F F F F F F H 即( 2 − 2 − 31 ) × 2 31 unsigned \quad int\quad m = FFFF \quad FFFFH\\ 即(2-2^{-31})\times 2^{31} unsignedintm=FFFFFFFFH即(2−2−31)×231
因为 m 的二进制表示为32位1,超过了float的24位,超出的1会被舍掉,所以就会产生精度损失。 - 当double向float、int转换时可能会有精度损失,因为double类型尾数的有效位数位53,超过了float的24;而且浮点数向整数转换时,若是小数部分不为0,一定会有精度损失,因为整数没有小数部分
例: 将十进制小数转换成IEEE 754标准的浮点数
例:现有一个十进制小数43.875,请将其转换成IEEE 754类型的短浮点数(即float类型),并将最终结果用二进制或十六进制表示。
分析:
IEEE 754标准的短浮点数要符合如下几个标准
- 阶码用移码表示,占8位。其值为阶码真值加偏置值127
- 尾数用原码表示,占23位。另有一位隐含的整数1
- 最高位为数符,占1位。0表示正数、1表示负数
解答:
第一步 将十进制小数转换成二进制表示
一定要转换成 1.M 的形式,其中 M 为尾数,
(
43.875
)
10
=
(
101011.111
)
2
=
(
1.01011111
)
×
2
5
(43.875)_{10}=(101011.111)_{2}=(1.01011111)\times 2^5
(43.875)10=(101011.111)2=(1.01011111)×25
第二步 求出数符、阶码、尾数的二进制表示
由题意知:数符
m
s
=
0
m_s=0
ms=0
由第一步知:阶码
E
=
5
=
(
101
)
2
E=5=(101)_{2}
E=5=(101)2,尾数
M
=
(
01011111
)
2
M=(01011111)_2
M=(01011111)2
因此:
- 1位数符为 0
- 8位阶码为(移码表示) 5 + 127 = 132 = ( 1000 0100 ) 2 5+127=132=(1000\quad 0100)_2 5+127=132=(10000100)2
- 23位尾数为(原码表示) ( 010 1111 1000 0000 0000 0000 ) 2 (010\quad 1111\quad 1000\quad 0000\quad 0000\quad 0000)_2 (01011111000000000000000)2
第三步 整理结果
数符 | 阶码 | 尾数 |
---|---|---|
0 | 1000 0100 | 010 1111 1000 0000 0000 0000 |
即 43.875 = 0100 0010 0010 1111 1000 0000 0000 0000 = 422 F 8000 H 43.875=0100\quad 0010\quad 0010\quad 1111\quad 1000\quad 0000\quad 0000\quad 0000=422F\quad 8000H 43.875=01000010001011111000000000000000=422F8000H |
public class Main {
public static void main(String[] args) {
float f = 43.875f;
// 输出浮点数的 二进制表示
System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));
// 输出浮点数的 十六进制表示♑
System.out.println(Integer.toHexString(Float.floatToIntBits(f)));
}
}