一些数学符号的解释:
ϵ
\epsilon
ϵ: 一个无限小的数,微积分中的一个概念。如果a=1-
ϵ
\epsilon
ϵ,
a的值无限接近于1。
∑
\sum
∑:求和符号。
∑
i
=
0
5
x
i
=
i
5
+
i
4
+
i
3
+
i
2
+
i
1
+
i
0
\sum_{i=0}^5x^i=i^5+i^4+i^3+i^2+i^1+i^0
∑i=05xi=i5+i4+i3+i2+i1+i0
x
i
x_i
xi: x下标,此位(0或1)在位向量中的位置。(最右边为第0位)
[
00000000
]
2
[0000 0000]_2
[00000000]2: 下标2,表示进制。
∞
\infty
∞:无穷。
a
=
+
∞
a=+\infty
a=+∞,a为无限大的一个数。
a
=
−
∞
a=-\infty
a=−∞,a为无限小的一个数。
C
k
n
C_k^n
Ckn:不考虑顺序的组合。从n个元素中取k个元素,k个元素的组合数量为
C
k
n
C_k^n
Ckn。
浮点数,例如C语言的float与double,与整数int都是最常用的数据类型,但是与int又有根本上的不同。在应用层面一个是可以表示整数与小数,另一个只能表示整数,另一方面int从二进制变换到十进制是相对直观的,而float相对复杂,它需要将位向量划分为三个部分再通过一个公式换算才能完成换算。本文通过切入五个问题对浮点数及其涉及的机制进行总结。1,什么是浮点数?小数点是如何浮动的?2,浮点数相对于定点表示法的优势是什么?3,目前通用的IEEE754浮点数是如何计算的?4,如果浮点数的赋值不在浮点数可表示的匹配值之内会发生什么?5,同样数值的int和float的底层二进制编码有什么区别?
1,什么是浮点数?小数点是如何浮动的?
为什么称为浮点数?相对的定点数是什么?
“浮点”一词相对于“定点”,表示小数点是可移动的。在定点表示法中,我们比较熟悉的是无符号编码unsigned int和补码编码int,在前一篇文章中(二进制(1):无符号编码和补码编码)已对定点表示法进行了一定的总结。回顾一下,在无符号编码中二进制位向量1111 1111的十进制值为
2
7
+
2
6
+
2
5
+
2
4
+
2
3
+
2
2
+
2
1
+
2
0
=
255
2^7+2^6+2^5+2^4+2^3+2^2+2^1+2^0=255
27+26+25+24+23+22+21+20=255。255也既是255.0,以二进制的角度看,小数点最低有效位的右边11111111.。定点表示法也可以用来表示小数,例如可以规定让二进制向量最低四位表示数值的小数部分,那么位向量1111 1111的值会变为
2
3
+
2
2
+
2
1
+
2
0
+
2
−
1
+
2
−
2
+
2
−
3
+
2
−
4
=
15.9375
2^3+2^2+2^1+2^0+2^{-1}+2^{-2}+2^{-3}+2^{-4}=15.9375
23+22+21+20+2−1+2−2+2−3+2−4=15.9375,相当于将小数点移到中间1111.1111。
其转换公式可归纳为,如果有位向量
[
b
m
b
m
−
1
.
.
.
b
1
b
0
b
−
1
b
−
2
.
.
.
b
−
n
+
1
b
−
n
]
[b_m b_{m-1}... b_1 b_0 b_{-1} b_{-2}... b_{-n+1} b_{-n} ]
[bmbm−1...b1b0b−1b−2...b−n+1b−n],则:
b
=
∑
i
=
−
n
m
2
i
∗
b
i
b=\sum_{i=-n}^m2^i*b_i
b=i=−n∑m2i∗bi
浮点数的小数点是如何浮动的?
下面将会完全抛弃上文中定点表示法的思路,重新为位向量0000 0000设计一种二进制转换十进制的方案,一种简化版的浮点数。我们设:
V:位向量在新的编码模式下转换为十进制后的值。
E:位向量在老的无符号编码规则下的值。
V
=
2
E
V=2^E
V=2E
在这种编码模式下,小数点会发生什么变化? 假设最底层的位向量为[0000 0001],那么
V
=
2
[
00000001
]
2
=
2
1
=
2
V=2^{[0000 0001]_2}=2^1=2
V=2[00000001]2=21=2, 最底层的0与1是不会发生变化的,只是因为制定了新的规则,所以00000001变为了十进制的2,从另一个角度去分析这种变化,将V变为unsigned int的二进制位向量,观察小数点的位置,E–>V == 0000 0001(.)—>0000 0001(0.)。
如果底层位向量为00000100,
V
=
2
[
00000100
]
2
=
2
8
=
256
V=2^{[0000 0100]_2}=2^8=256
V=2[00000100]2=28=256,E–>V == 0000 0100(.)–>0000 0100(000000.)。
因此可以看出,在
V
=
2
E
V=2^E
V=2E的这种编码模式下,随着底层位向量的值的不断变化,小数点的位置也是不断变化的,这种小数点的浮动,也既是“浮点”,其实现的核心既是
V
=
2
E
V=2^E
V=2E。
再看一下目前各编程语言都在用的IEEE浮点标准:
V
=
(
−
1
)
s
∗
M
∗
2
E
V=(-1)^s*M*2^E
V=(−1)s∗M∗2E
2,浮点数相对于定点表示法的优势是什么?
1,更大的取值范围
定点表示法的缺点是它能表示的数值范围相对要小,加入了小数则更加有限,在上文的例子中的8位位向量在有限的位数中既要表示整数又要表示小数,例如上文中的的[1111.1111],它的最大值是
2
3
+
2
2
+
2
1
+
2
0
+
2
−
1
+
2
−
2
+
2
−
3
+
2
−
4
=
15.9375
2^3+2^2+2^1+2^0+2^{-1}+2^{-2}+2^{-3}+2^{-4}=15.9375
23+22+21+20+2−1+2−2+2−3+2−4=15.9375 。但如果用
V
=
2
E
V=2^E
V=2E这种浮点取值方式,它的整数部分最大可达
2
[
1111
]
=
2
15
=
32768
2^{[1111]}=2^{15}=32768
2[1111]=215=32768。十进制
2
100
2^{100}
2100在无符号编码中的二进制表示需要101位才能完成,而在上文的浮点编码模式下,
V
2
100
=
2
E
[
1100100
]
V2^ {100}=2^{E[1100100]}
V2100=2E[1100100],只需要7位。
2,可以更精确的表示小数
小数数量是接近无穷的,想用一堆1和0的组合来精确的表示所有小数是不切实际的,所以任何编码模式所能表示的小数数量都是有限的,二进制表示小数只是一种模拟,当一个浮点数被赋值为小数时,它的实际值将会是它能表示的最接近被赋值的那个数的一个匹配值,一个有限长度的位向量能表示的匹配值的数量可以理解为这个位向量的精度,例如64位double类型能表示的匹配值肯定大于32位的float,所以前者的精度更大。
定点表示法的第二个缺点既是它能表示的匹配值很少,同样上面定义的1111.1111的小数部分只能表示
2
−
1
,
2
−
2
,
2
−
3
,
2
−
4
2^{-1},2^{-2},2^{-3},2^{-4}
2−1,2−2,2−3,2−4四个值组合出来的值,例如[0000.0001]=0.5, [0000.1001]=0.5+0.0625=0.5625,共有
C
4
0
+
C
4
1
+
C
4
2
+
C
4
3
+
C
4
4
=
2
4
−
1
=
15
C_4^0+C_4^1+C_4^2+C_4^3+C_4^4=2^4-1=15
C40+C41+C42+C43+C44=24−1=15个数值。而将上文的
V
=
2
E
V=2^E
V=2E按照IEEE浮点对小数表示进行扩展
V
=
M
∗
2
E
V=M*2^E
V=M∗2E,M为小数部分,暂不考虑负数的情况,同样一个8位的位向量中,小数位为4位,整数位为4位,小数部分可以表示最多
(
C
4
0
+
C
4
1
+
C
4
2
+
C
4
3
+
C
4
4
)
∗
[
1111
]
2
=
15
∗
15
=
225
(C_4^0+C_4^1+C_4^2+C_4^3+C_4^4)*{[1111]_2} =15*15=225
(C40+C41+C42+C43+C44)∗[1111]2=15∗15=225个数值。
3,目前通用的IEEE754浮点数是如何计算的?
以上举例的浮点数规则只是用来理解浮点数的核心概念,目前通用的IEEE754浮点数值的计算公式如下,公式虽短但其实很有深度:
V
=
(
−
1
)
s
∗
M
∗
2
E
(
公
式
1
)
V=(-1)^s*M*2^E(公式1)
V=(−1)s∗M∗2E(公式1)
各变量取值范围:
s:等于1或0,负权,决定该数正负。
M:取值范围0至2-
ϵ
\epsilon
ϵ。有两个case,规格化时M=1+f,非规格化时M=f,f的取值范围为0至1-
ϵ
\epsilon
ϵ。
E:规格化时为e-Bias,非规格化时为1-Bias。e为底层二进制某部分01通过无符号编码转换出来的十进制值,Bias依赖于该部分01位向量长度。
一个32位的IEEE浮点数位向量分为三个部分:
[0(符号位) 00000000(阶码位) 00000000000000000000000(小数位)]
从右起,0-23位为小数位,24-31位为阶码位,32位最高位为符号位。
符号位:符号位比较好理解,既是公式1中的s,如果符号位为0,V的值就会为正,如果符号位为1,V的值就会为负。
阶码位:采用无符号编码规则,阶码位决定了V的取值范围,它既是公式1中的E=e-Bias中的小e。
小数位:公式1中的M=1+f或M=f的f,计算f的值的方法是也是定点数表示法, 与int不同由于它是表示小数的,所以
f
=
[
e
23
e
22
…
e
1
e
0
]
=
e
23
∗
2
−
1
+
e
22
∗
2
−
2
+
…
+
e
1
∗
2
−
23
+
e
0
∗
2
−
24
f=[e_{23} e_{22}…e_1 e_0]=e_{23}*2^{-1}+e_{22}*2^{-2}+…+e_1*2^{-23}+e_0*2^{-24}
f=[e23e22…e1e0]=e23∗2−1+e22∗2−2+…+e1∗2−23+e0∗2−24,也可以理解为
f
=
2
−
24
∗
(
e
23
∗
2
23
+
e
22
∗
2
22
+
…
+
e
1
∗
2
1
+
e
0
∗
2
0
)
f=2^{-24}*(e_{23}*2^{23}+e_{22}*2^{22}+…+e_1*2^{1}+e_0*2^{0})
f=2−24∗(e23∗223+e22∗222+…+e1∗21+e0∗20)。
三个case:
在不同的情况中,公式1中的阶码E与小数M的计算方式会有差异:
—case1,规格化,阶码位e非全0也非全1。
—case2,非规格化,阶码位e全0。
—case3,特殊情况,阶码位e全1。
1,先分析比较32位的浮点数的case1与case2:
case1规格化:
E
=
e
−
B
i
a
s
,
M
=
1
+
f
,
B
i
a
s
=
2
浮
点
数
的
阶
码
位
数
−
1
−
1
(
32
位
有
8
位
阶
码
,
既
2
7
−
1
=
128
−
1
=
127
)
E=e-Bias,M=1+f,Bias=2^{浮点数的阶码位数-1}-1(32位有8位阶码,既2^7-1=128-1=127)
E=e−Bias,M=1+f,Bias=2浮点数的阶码位数−1−1(32位有8位阶码,既27−1=128−1=127)
case2非规格化:
E
=
1
−
B
i
a
s
,
M
=
f
E=1-Bias,M=f
E=1−Bias,M=f
case1规格化之中,E的最小值是
[
00000001
]
2
−
127
=
−
126
[0000 0001]_2-127=-126
[00000001]2−127=−126,最大值为
[
11111110
]
2
—
12
7
10
=
254
−
127
=
127
[1111 1110]_2—127_{10} =254-127=127
[11111110]2—12710=254−127=127。M的最小值是
1
+
[
00...00
]
2
=
1
1+[00...00]_2=1
1+[00...00]2=1,最大值为
1
+
[
11...11
]
=
(
2
−
ϵ
)
1+[11...11]=(2-\epsilon)
1+[11...11]=(2−ϵ)非常接近2的一个值。所以V的最大值为,
V
=
(
−
1
)
0
∗
(
2
−
ϵ
)
∗
2
127
=
(
2
−
ϵ
)
∗
1.701411835
∗
1
0
38
≈
3.4
∗
1
0
38
V=(-1)^0*(2-\epsilon)*2^{127}=(2-\epsilon)*1.701411835*10^{38}\approx3.4*10^{38}
V=(−1)0∗(2−ϵ)∗2127=(2−ϵ)∗1.701411835∗1038≈3.4∗1038,此时V的位向量是[0 11111110 11…11]。V的最小值为与最大值相比只是符号位变为了1,
V
=
(
−
1
)
1
∗
(
2
−
ϵ
)
∗
2
127
≈
−
3.4
∗
1
0
38
V=(-1)^1*(2-\epsilon)*2^{127}\approx-3.4*10^{38}
V=(−1)1∗(2−ϵ)∗2127≈−3.4∗1038,V的位向量为[1 11111110 11…11]。V最接近0的值分别在0的两端,为
V
=
(
−
1
)
0
或
1
∗
1
∗
2
−
126
=
±
2
−
126
V=(-1)^{0或1}*1*2^{-126}=\pm2^{-126}
V=(−1)0或1∗1∗2−126=±2−126。注意这个数与下面非规格化的最大与最小值非常接近,这个机制实现了一个规格化到非规格化数的平滑过渡。另一个值得注意的特点是它的数值分部形式是
v
=
x
∗
2
y
v=x*2^{y}
v=x∗2y,如下图所示越接近0数值分布越密集,这也导致了浮点数的一个重要属性既是越接近0的数值精度越高。
case2非规格化之中,E的值是唯一的,1-127=-126。M的最小值是 [ 00...00 ] = 0 [00...00]=0 [00...00]=0,最大值为 [ 11...11 ] = ( 1 − ϵ ) [11...11]=(1-\epsilon) [11...11]=(1−ϵ)一个非常接近1的值。V的最大值为, V = ( − 1 ) 0 ∗ ( 1 − ϵ ) ∗ 2 − 126 ≈ 2 − 126 V=(-1)^0*(1-\epsilon)*2^{-126}\approx2^{-126} V=(−1)0∗(1−ϵ)∗2−126≈2−126。V的最小值与最大值相比只是符号位变为了1,为 ( − 1 ) 1 ∗ ( 1 − ϵ ) ∗ 2 − 126 ≈ − 2 − 126 (-1)^1*(1-\epsilon)*2^{-126}\approx-2^{-126} (−1)1∗(1−ϵ)∗2−126≈−2−126。V的最接近0的值为0,[0 0000000 00…00]。
这里可以看出,在数轴上从规格化的最小值一直到0的部分,精度达到了峰值,匹配值均匀分布在数轴上0的两端,在 ± 2 − 126 \pm2^{-126} ±2−126之间每两个匹配值的间距为 2 − 24 ∗ 2 − 126 = 2 − 150 2^{-24}*2^{-126}=2^{-150} 2−24∗2−126=2−150,不再有“数越小越精确”的性质。
2,再看一下情况3,特殊值:
0 11111111 00…0
当阶码位全为1时,如果小数位全为0,此数表示无穷大,
∞
\infty
∞,而符号位决定了他是
+
∞
+\infty
+∞或
−
∞
-\infty
−∞。例如当一个float值除以0后或发生溢出情况它的值为无穷。
0 11111111 00…1
当阶码位全为1时,如果小数位不全为0,此数为NaN,not a number,不是一个数。两个值为无穷的float相减结果为NaN,如果一个float变量被赋值
−
1
\sqrt-1
−1,它的值也会变为NaN。当一个值无法用实数或无穷表达时,它是NaN。
4,如果浮点数的赋值不在浮点数可表示的匹配值之内会发生什么?
向偶数舍入
与一个整数变量不可能表示所有整数一样,一个浮点数也无法表示所有小数。因为浮点数根据长度与格式不同,它只能表示有限的匹配数,如果变量被赋的值不在这些匹配数之中,系统将会去寻找最接近的匹配值,也既是将变量被赋的值进行舍入。IEEE浮点数采用的舍入模式是“向偶数舍入”,这种舍入模式是先寻找最接近的匹配值,如果被舍入的值正好在两个匹配数的正中间,将再寻找最接近的最低有效位为偶数的匹配值。这种方案在某些统计场景中是最合理的。
举例来说,假设某个浮点数可表示的匹配值只有1和2,如果将变量赋值为1.4,浮点数的值将为1,1.6为2,1.5为2。在二进制的层面理解,可以归纳为倾向于将最低有效位变为0,例如有4位浮点数二进制位向量00.00,出现超精度赋值00.0010,00.0010在00.00与00.01之间,这时则选择舍入为00.00。
运算中的问题
当两个浮点数进行运算时,在运算之前他们会分别进行舍入,在运算之后,他们的运算结果将会被进行舍人,由于这种对运算结果的自动舍入,浮点数的运算不具有结合率与分配率。
不符合结合率:例如a+b+c不一定等于a+(b+c),在第一次运算后a+b与b+c都有可能得出舍入后的值。
不符合分配率:1e20*(1e20-1e20)
≠
\neq
=(1e20*-1e20)-(1e20*-1e20),前者在第一次运算后会变为,1e20*0,后者是
−
∞
∗
−
∞
-\infty*-\infty
−∞∗−∞。
int转换float的问题
当一个int变量强制转换为float变量后,它不会溢出,因为float变量可赋值的范围要大于int,但是它有可能被舍入成最近的匹配值,因为int可以精确表示它可被赋值范围内的所有整数,但是float的可表示数值是以 M ∗ 2 E M*2^E M∗2E的形式分布在数轴上,数值越大,精确度越低。也既是在整数的绝对值超过一个很大的值的时候,它的匹配值无法全部覆盖整数。
5,同样数值的int和float的底层二进制编码有什么区别?
例:
1234
5
10
12345_{10}
1234510在无符号编码中的位向量为
1234
5
10
=
[
11000000111001
]
2
12345_{10}=[11000000111001]_2
1234510=[11000000111001]2
我们可以尝试手动将12345的无符号编码转换为IEEE32位浮点数格式编码:
构造小数位f
1,移小数点至最高有效位的右边,再将最高有效位转化为2的幂,
[
1.1000000111001
]
∗
2
13
[1.1000000111001]*2^{13}
[1.1000000111001]∗213,既是转化成了
V
=
(
−
1
)
s
∗
M
∗
2
E
V=(-1)^s*M*2^E
V=(−1)s∗M∗2E。
2,因为此数为规格化数,根据公式1,M=1+frac,可以舍去最高位的1,变为了[.1000000111001]。
3,低位段添加0使小数位达到23位,[10000001110010000000000]。
构造阶码部分e
4,E=13,bias=127,E=e-bias,e=E+bias=140。
14
0
10
=
[
10001100
]
2
140_{10}=[10001100]_2
14010=[10001100]2。
5,添加符号位,结果为:
1234
5
10
12345_{10}
1234510=[0 10001100 10000001110010000000000]
可手动转换说明风马牛不相及的两种编码的机制有某种内在联系,转换之后可以看到一个现象,无符号编码删除最高位后的位向量与浮点数小数数位的高位段完全相同。
学习资料:深入理解计算机系统(原书第二版 )—Randal E.Bryant
维护日志:
2020-1-7:增删查改,修改部分错误