带你学习《深入理解计算机系统》进制存储和运算(2)——浮点数底层探秘

浮点概念

 

B()和D()分别表示二进制数和十进制数。

包括我在内,不少朋友都曾经把浮点和小数想成是一个概念,这里就先看下小数,比如D(3.14)=3*10^0+1*10^(-1)+4*10^(-2),可以看出,关于权位的值是支持负数以实现小数概念的。那么二进制也应该有类似的表述,比如B(1.101)=1*2^0+1*2^(-1)+0*2^(-2)+1*2^(-3)=D(1.175)。如果二进制小数在计算机里也是这样实现的话,那掌握起来就太容易了。可真是这样么?是不是数据中还要存储一个小数点呢?事实上,“浮点”这个名词取得很贴切:通过一定区域存储的指数值,按照相应的规则,来决定小数点的位置的变化,就像小数点会来回浮动而不会专门固定存储。

 

在IEEE浮点标准里,浮点数V的计算公式:V=(-1)^s * M * 2^E,其中

s决定V的正负,0为正,1位负。

有效数M,不考虑小数点的位置,去掉首位后的数值

指数位E,它就是小数点浮动的依据

 

光从这个公式来看,觉得还是很简单,然而,IEEE浮点设计者为了使得浮点数实际数值的递增和二进制无符数递增一致(就是看起来像二进制整数递增),写出了很诡异的存储规则。

先来看浮点数内存分配。教材里的单精度浮点有32位太长不方便描述(也容易把你(wo)看晕),于是我们用8位来举例,假设现在我定义了一种不存在的类型叫超级单精度浮点(以后就叫超单精),该类型只占1个字节,共8位,看下图:

 

 

符号指数原位e有效数原位m
00110111

 

我把超单精分段成1、4、3位,从图上能看出,符号位要分配一位s=1,指数位分配四位k=4,有效数分配三位n=3。下面分别描述如何将三块二进制数翻译成公式s、M、E的值

(f是有效数原位m代表的实际小数值(注意不是浮点最终小数值),而n表示有效数原位所占的位数)

1、符号位s是最简单的,二进制的0和1与s完全相反对应

2、M = 1+f =1+m*2^(-n)……(e!=0 && e!=2^(k)-1)时)

         =  f =m*2^(-n)……(e==0时)

3、E = e-Bias =e-(2^(k-1)-1)……(e!=0  && e!=2^(k)-1)时)

       = 1-Bias =1-(2^(k-1)-1)……(e==0时)

4、e == 2^(k)-1,m=0时,V为∞

      e == 2^(k)-1,m!=0时,V为NaN

    

 

看到这里可能觉得一头雾水,比如f为啥有时+1有时又不加?Bias是个啥东东?这里就专门针对两个问题进行说明。

首先来看看Bias存在的意义,其实就是为了使得无符数e所代表的指数位E可正可负。要知道,在二进制标准里,负数是补码来实现的,已经足够麻烦了,现在你浮点这还有负指数概念,难道还用补补补补码概念么?肿办?于是就根据指数位k,定义了“偏置”Bias,以便将指数原位e解释成有符数。这样,通过E = e-Bias,把本来是无符数的指数原位e,构造成可正可负的指数位E了。

 

根据公式Bias=2^(k-1)-1,我们以k=4为例,Bias的值应该恒为0111。

 

在此,IEEE浮点规定了三种情形,当e的取值范围在0001~1110,被称为规格化数;当e的值为0000时被称为非规格化数;当e取值为1111时,被称为特殊数值

 

1、在规格化数的情况中,指数原位e(无符数)的最大值是1110,最小值为0001。也就是说,根据规格化数公式E = e-Bias,当指数位分配4位时,最大正指数Emax=B(1110-0111)=B(0111)=D(7),最小负指数Emin=B(0001-0111)= D(-6)。于是,指数位E的规格化取值范围就是-6~7;

2、非规格化数的情况,e取值为0000,以k=4为例,根据非规格化数公式E = 1-Bias,E就恒等于-6;

3、特殊值的情况,e取值为1111,规律很简单,当M=0时V就等于+∞或-∞,当M!=0时,V就是无效数NaN。

 

关于有效数M,在IEEE浮点数标准中,指数位e的值对于有效位M的值有着特殊的制约关系,根据上面1、2两种情况,当e为规格化数情况时,M=1+f;当e位非规格化数情况时,M=f。特殊值情况下M只需确定是否为0即可。

 

看到这或许你更晕头转向了。因为上面讲的是纯理论,接着我们举个5例子,一切就引刃而解了。我们还是举8位超单精浮点,其中符号位要分配一位s=1,指数位分配四位k=4,有效数分配三位n=3,因此我们先求出Bias = 2^(k-1)-1 = 2^(4-1)-1 = 7

根据公式V=(-1)^s * M * 2^E,我们要分别求出M和E,才能求出这几个二进制代表的小数值。

 

1、规格化数举例

 

符号指数原位e有效数原位m
00010111

 

我们发现e=0010,可以判断为规格化数,因此M = 1+f = 1+m*2^(-n) = 1+7*2^(-3) = 15/8;(n表示有效数原位分配了3位)

因为是规格化数,因此E = e-Bias = 2-7 = -5;(e是0010也就是2)

因此V = (-1)^0 * 15/8 * 2^(-5) = 15*2^(-8) = 15/256 = 0.05859375

 

 

符号指数原位e有效数原位m
00110111

 

我们发现e=0110,可以判断为规格化数,因此M = 1+f = 1+m*2^(-n) = 1+7*2^(-3) = 15/8;

因为是规格化数,因此E = e-Bias = 6-7 = -1;

因此V = (-1)^0 * 15/8 * 2^(-1) = 15*2^(-4) = 15/16 = 0.9375

 

2、非规格化数举例

 

符号指数原位e有效数原位m
00000010

 

我们发现e=0000,可以判断为非规格化数,因此M = f = m*2^(-n) = 2*2^(-3) = 2/8;(B(010)就是2)

因为是非规格化数,因此E = 1-Bias = 1-7 = -6;

因此V = (-1)^0 * 2/8 * 2^(-6) = 2*2^(-9) = 2/512 = 0.00390625

 

3、特殊数值举例

 

符号指数原位e有效数原位m
01111010

我们发现e=1111,可以判断为特殊值,而m!=0,因此V = NaN,无效数

 

 

符号指数原位e有效数原位m
01111000

我们发现e=1111,可以判断为特殊值,而m=0,因此V = +∞

 

 

接下来的任务就是,搞清楚为什么这帮学霸要如此设计规则,我把我定义的8位超单精浮点遍历一下部分取值(为了节省篇幅符号位s都为0,只讨论正数),由于8位分别划分成1,4,3位,因此:k=4,n=3,Bias=7,2^(-n)=1/8

 

V=(-1)^s * M * 2^E

 

类型归类

二进制表示

e

E

m

f

M

V

以512

为分母

精度

0

0  0000  000

0

E=1-Bias=

 -6

0

f=m*2^(-n) =

0

M=f=

0

V=(-1)^s * M *2^E=

0

0/512 

e=0,以512为分母递增

2^(-3)*2^(-6)=512

精度都由k、n值决定

非规格化数

min

0  0000  001

0

E=1-Bias=

 -6

1

m*2^(-n) =

1/8

M=f=

1/8

V=(-1)^s * M *2^E=

1/512

 

 

 

0  0000  010

0

-6

2

2/8

2/8

2/512

 

 

 

0  0000  011

0

-6

3

3/8

3/8

3/512

 

 

 

0  0000  100

0

-6

4

4/8

4/8

4/512

 

 

 

0  0000  101

0

-6

5

5/8

5/8

5/512

 

 

 

0  0000  110

0

-6

6

6/8

6/8

6/512

 

 

非规格化数

max

0  0000  111

0

-6

7

7/8

7/8

7/512

 

 

规格化数

min

0  0001  000

1

E=e-Bias=

-6

0

f=m*2^(-n) =

0

M=1+f=

8/8

V=(-1)^s * M *2^E=

8/512

 

e=1,以512为分母递增

精度都由e、k、n值决定

当规格化数的e=1时,

E的计算结果,

刚好与非规格化的E相等

实现无缝连接

 

0  0001  001

1

e-Bias=

 -6

1

m*2^(-n) =

1/8 

M=1+f=

9/8

9/512

 

M多加个1,刚好绕过

非规格化数的分数取值

 

0  0001  010

1

-6

2

2/8

10/8

10/512

 

 

 

0  0001  011

1

-6

3

3/8

11/8

11/512

 

 

 

……

 

……

……

……

……

 

 

 

0  0001  111

1

-6

7

7/8

15/8

15/512

15/512

 

 

0  0010  000

2

E=e-Bias=

-5

0

f=m*2^(-n) =

0

M=1+f= 

8/8

V=(-1)^s * M *2^E=

8/256=16/512

16/512

e=2,以256为分母递增

2^(-3)*2^(-5)=256

精度都由e、k、n值决定

 

0  0010  001

……

2

-5

……

1

1/8

……

9/8

……

9/256

……

18/512

……

 

 

0  0010  111

2

-5

7

7/8

15/8

15/256

30/512

 

 

0  0011  000

3

E=e-Bias=

-4

0

f=m*2^(-n) =

0

M=1+f=

8/8

V=(-1)^s * M *2^E=

8/128=32/512

32/512

e=3,以128为分母递增

精度都由e、k、n值决定

 

0  0011  001

3

-4

1

1/8

9/8

9/128

36/512

 

 

0  0011  010

3

-4

2

2/8

10/8

10/128

40/512 

 

 

0  0011  011

3

-4

3

3/8

11/8

11/128

44/512

 

 

0  0011  100

3

-4

4

4/8

12/8

12/128

48/512

 

 

0  0011  101

3

-4

5

5/8

13/8

13/128

52/512

 

 

0  0011  110

3

-4

6

6/8

14/8

14/128

56/512

 

 

0  0011  111

3

-4

7

7/8

15/8

15/128

60/512

 

 

0  0100  000

4

-3

0

0

8/8

8/64

64/512

e=4,以64为分母递增

精度都由e、k、n值决定

 

0  0100  001

4

-3

1

1/8

9/8

9/64

72/512

 

 

0  0100  010 

4

 -3

2

 2/8

10/8

10/64

80/512

 

 

0  0100  011 

4

-3

3

 3/8

11/8

11/64

88/512

 

 

……

……

0  0110  111

e

6

E

-1

m

7

f

7/8

M

15/8

V

15/16

以16

为分母

精度 

 

0  0111  000

7

e-Bias=

0

0

f=m*2^(-n) =

0

M=1+f=

8/8

V=(-1)^s * M *2^E=

1

16/16

e=7,以8为分母递增

精度都由e、k、n值决定 

 

0  0111  001

7

0

1

f=m*2^(-n) =

1/8

M=1+f=

9/8

9/8

18/16

 

 

0  0111  010

……

7

0

……

2

… 

2/8

……

10/8

……

10/8

……

20/16

……

 

 

0  0111  111

7

0

7

7/8

15/8

15/8

30/16

 

 

0  1000  000

8

1

0

0

8/8

2

32/16

e=8,以4为分母递增

精度都由e、k、n值决定

 

0  1000  001

……

0  1010  000

0  1010  001

0  1010  010

0  1010  011

0  1010  100

……

8

10

10

10

10

10

1

……

3

3

3

3

3

……

1

0

1

2

3

4

……

1/8

……

0

1/8

2/8

3/8

4/8

……

9/8

……

8/8

9/8

10/8

11/8

12/8

……

9/4

……

8

9

10

11

12

……

36/16

……

8*16/16

9*16/16

160/16

11*16/16

12*16/16

……

看到没?

以整数递增了 

 

0  1110  110

14

e-Bias=

7

6

6/8

14/8

224

 

 

规格化数

max

0  1110  111

14

7

7

7/8

15/8

240

 

 

无穷大

无效数

0  1111  000

0  1111 010

--

--

--

非0

--

非0

--

+∞

NaN

 

 

这个应该屎上最全的浮点转小数的过程表了,感觉很长吧?但比教材详细和简单多了。根据上面这个表,我们能获取很多很多信息

1、这张表的二进制部分,从0 0000 000 ~0 1111 000, 其递增规律与无符数二进制完全相同,如果把“……”部分也补写全,那就是遍历了8位超单精浮点数的所有值。

2、观察非规格化到规格化数,过渡得非常完美。M只有在非规格化数的部分才会出现1/8~7/8这样的数,而规格化数的M均为8/8~15/8,从这里可以看出,当e=0时,标准公式让E的取值和e=1时一样的取值一样,都为-6,而公式M=f,少加个1,就是为了计算1/8~7/8这部分数而专程设计的;至于规格化部分,就严格按照公式E=e-Bias和M=1+f来计算了。而m一直重复1/8~7/8这样的数

3、浮点数存储中,当e==0时,如果把m的B(000)~B(111)看成是M:B(0.000)~B(0.111)的特征值,M自然是1/8~7/8,然后再被2^E(其中E=1-Bias,非规格)缩小,变成1/512~7/512,根据V的公式可以看出,m总可以根据2^(-3)这种权进行递增,然后再通过2^E计算最终值。如果M再递增,从B(0.111)变成B(1.000),那么此时特征值变回了0,首位1溢出被省略,所以M要通过1+f的公式被重新调整成8/8补上溢出的进位,与之前的7/8接轨就合理了,再通过2^E(其中E=e-Bias=1-Bias规格)计算最终值为8/512。当m递增到1.001时,1溢出被省,因此M利用1+f调整成9/8,2^E计算最终值9/512,关键的关键是当m由1.111继续递增,变成10.000,此时特征值重新变成000,而且m已经有两个进位了,那M是不是该用2+f得出16/8的值来接轨呢?NO!要知道,所有m的进位都作用在e上,这时e被进位成0010,现在E变成2-Bias,E增大1位,相当于是2^E扩大一倍,因此为了抵消2^E的变化,M再次被迫调整成8/8,于是1+f的公式仍然奏效,就这样的规律,以此类推,规格化数的M从8/8到15/8循环出现。之所以要这么设计,是为了牺牲m溢出的进位表示,从而节省一个存储位。而e=0时,就表示m没有进位被省,所以无需加1。

        从这里我们能看出,当初IEEE聘请的学霸在设计有效数位时,对进位溢出用指数位来反映的处理方式是明确的,只是说为了尽可能多表述小数,于是将没有进位的有效数,通过定义非规格化以及公式的变换,归并到有进位的同一个体系中来。

 

4、从精度上来看,我定义的超单精浮点,能表示最小的非0小数是1/512,所以精度最高的区间就是(1/512,16/512),之后精度会随着e的增大而降低。可以看出,m和f都有0~7这样的循环规律,每次循环结束,精度都降低2倍。事实上,任何一组数,其区间的精度都能轻易求出来,那就是我自己总结的结论(教材未提及):精度区间参数2^(E-n)。比如0  0011  100,它的E=e-Bias=3-7=-4,于是e=0011这个精度区间参数就是2^(-4-3)=2^(-7)=1/128。而实际上它的值是12/128(因为有效数100值是4,f=4/8,M=12/8),那么根据精度区间参数,0  0011  011一定等于11/128,0  0011  101一定等于13/128,查下表或自己算下,对不对?

      综上所述,浮点数的精度区间参数是由e和n确定的2^(E-n)就是判定精度区间参数的唯一标准,当你要判断某个数能够被特定的k、n精确表示时,那就先根据公式计算出2^(E-n)吧,然后把它作为分母算出你这个数的分子,如果分子是自然数,那精度就没问题!

 

      举个例子,比如18.0这个数,二进制小数表示:1.0010 * 2^4,如果用k=4,n=3的超单精浮点来表示,根据2^(E-n)=2^(4-3),该数处在以2为参数的精度区间内,由于1.0000 * 2^4的值是16,而18 可由 16+2 * x(x∈0,1,2,3…)计算得出,因此18.0可以由超单精精确表示。同理,如果是19.0,无法由16+2 * x(x∈0,1,2,3…)得出,所有超单精浮点无法精确表示。

 

5、虽说精度会随着e的增大而降低,但另外还有个现象就是,即使不用运算,我也知道,2^m/512(m<=2^n-1)一定能被我的超单精浮点表示出来。通俗的说就是但凡2的整倍数做分子,512做分母的数都能表示出来。why?因为这恰好是m=f=0,M=1时,V=(-1)^s * M * 2^E的取2^E,简单说,2的整数倍数永远能以精度最高的精度区间参数(1/512)来表示。

 

6、浮点数专家可能觉得我是不是有些本末倒置?如果说n固定的时候精度由e说了算,那么n不一样时的时候精度会不会有差别呢?废话当然有啊!

      V=(-1)^s * M * 2^E=(-1)^s * (1+f) * 2^E = (-1)^s * (1+m*2^(-n)) * 2^E = (1)^s * (2^E + m*2^(E-n)) ……可以看出,n每增加1,精度就会提高2倍。

事实上,e的范围是由k决定的,Bias也是由k决定的。当E和n确定时,我们能求出精度区间的参数2^(E-n),而事实上,衡量一种浮点数的精度高不高,是只取决于n的,n的位数越多,2^(E-n)越小,自然精度就越高;而k值,或者说E值,是决定浮点数可表述范围的,k越大,则精度区间的数量就越多,自然覆盖实数的范围就越广。

……(上面这段话是去年写的,现在看起来觉得当初写的好蹊跷——精度肿么可能和E无关呢?最小分数1/512不就是k=4得出来的么???反正大家自己去思考思考,有心得欢迎分享!)

从公式V=(1)^s * (2^E + m*2^(E-n))就能看出,2^(E-n)决定区间的精度递增分母,而2^E决定精度区间的数量

 

      还是上面的例子,比如18.0这个数,二进制小数表示:1.0010 * 2^4,如果用k=4,n=3的超单精浮点来表示,首先看式子可简化为1.001 * 2^4,别小瞧少个0,这说明有效数的位数n为3是可以精确表示它的,于是去掉首位1,有效数原位m就为001,然后根据2^4确定E=4,e=E+Bias=11,于是指数原位e就是1011,于是18.0的超单精浮点数表示法就为:0 1011 001。同理,如果是19呢?其二进制小数1.0011 * 2^4,很明显,有效数原位m为0011,因此n=3的超单精浮点肯定无法精确表示。

 

7、从上表还能看出,0  0110  111是<1数的极限,从0  0111  000开始,其值为1,就能出现整数了。那么出现整数的规律又是什么呢?当然是V>1。根据6推导出来的公式

V = (1)^s * (2^E + m*2^(E-n))便能看出,由于n永为正,则有2^E>m(MAX)*2^(E-n)。因此要想V>=1,必须E>=0。

 

8、当e=10时,E就=3,那么2^E刚好把M的8/8~15/8的分母给砍掉完了,于是出现了这样奇葩的精度区间,以自然数递增,其实他们是以2^0递增的。之后出现的整数递增,肯定按照2^(1)、2^(2)、2^(3)、这些精度递增了。统一来说,所有区间,都以2^(n-E)为分母进行递增,只是分母后面都<1了,所以每个精度区间就形成了以公差为2^(E-n)的等差数列,比如表中的末尾e=14,出现240-224=16=2^(E-n)=2^(7-3)。公差恰好是精度区间参数,是不是很好玩?

 

把精度玩得这么爽, 我们就来实践一下,如果我想知道,我的超单精浮点不能精确描述的最小正整数值是多少,怎么求?

有两种方法可以计算出来:

①推测法,我上面总结的那个表可不是吃素的,发现没?当E=n=3时,区间内V以自然数递增,范围是8~15,而下一个数16是2的整次方,肯定也能表达,于是不能精确描述的最小正整数肯定是17。至于8以前的自然数,其所处的精度区间参数明显小于2^0,因此肯定也能精确描述。

②方法1纯粹是观察法,无法推广,现在来考虑下,当e固定时,精度取决于n,而我们要求的是整数,一定是规格化数。那么寻找n+1位才能精确表示的整数,一定不能用n位精确表示(2的整次方数除外) ,比如二进制小数1.0101,这个数的有效位是4位,n+1,于是这个小数肯定无法精确表示,同样的,如果将它扩大2^4倍10101变成整数,它肯定也不能被精确表示,好了,怎么样才能让这个整数变得最小呢?那当然是10001!这不就是17的二进制码么?!注意,要求精度表达极限,必须考虑到小数点右边省略掉的那一位,否则容易误以为1001是最小整数,那就闹笑话了.……有些时候,我们必须用最原始的二进制小数点来考虑问题,1.0101如果用浮点表示,首位的1肯定是被去掉了的,而小数点右移4位肯定是通过指数位e表示的,剩下的0101肯定是有效数原位m表示的,详细算法见第4点举的例子。

当然,这个是规格化的情况,对于非规格化数,本来就没有首位1,比如0.0101。

remember,浮点数记录数值的目的,只是通过一种规则实现对二进制小数的优化描述,并非二进制小数本身。因此很有必要熟练的掌握两者之间的切换关系。

 

通过这个例子总结得知:

①对规格化浮点数的有效数位m,其实就对应二进制小数中去掉首位1和小数点后剩余的那部分

②对非规格化浮点数的有效数位m,其实就对应二进制小数中去掉小数点后剩余部分

③而指数位e用来决定其对应的二进制小数的小数点位置,通过Bias偏置使得小数点可左移和右移

 

     事实上,C中的单精度浮点float是32位,其中k=8,n=23;而双精度浮点double是64位,其中k=11、n=52,可见其表述的实数范围和小数精度是远远超过我定义的8位单精度类型。有兴趣的读者去肯教材。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值