数据的表示与运算-浮点数
前言:
计算机中,数字分为定点数和浮点数。相对于浮点数,定点数比较好理解,原码补码反码移码。而浮点数十分繁杂。
关于浮点数的繁杂,我觉得最好的解释就是,(William M. Kahan)因其在浮点数运算标准的制定上的突出贡献而获得图灵奖。(Kahan)也是浮点数(IEEE754)标准的主要设计师。
初识浮点数:
假如说我们现在想要表示光速这样一个数值,我们可以怎么做?
- (1:)采用整数方式把他写出来,那么就是(300...00m/s)。这样数字十分的长,与计算机不好保存。
- (2:)采用科学计数法,那么就是(3*10^{8}m/s),那么如果我现在想保存这个数字,那么我只需要记录三个信息,第一个是(3),第二个是(10),第三个是(8)。
两种方法比较:
-
很明显第一种方法需要我们用更多的存储空间来保存它,而对于科学计数法,我们并不需要记录那么多的数却能表示同样的数值。
-
对于计算机而言,只能认识(0/1)符号,这在硬件实现上更为方便简单。所以我们这时候可以把(10)这个底数给取消掉,计算机默认他是(2),这样我们就可以只用保存两个数字,来表示这样一个大的数字。
-
对于表示电子的质量,太阳系的直径,这样非常极端的数字时,科学计数法的优势显得更为明显。它更方便。
-
但我们也可以发现,如果说我要表示的数字不是(300...000),而是(29935...13)这样的数字,而我科学计数法采用(2.9*10^{8}),那么我势必丢掉一些精度。
-
在我的理解看来,浮点数的表示是用精度来换取表示范围。
-
所以这里我们似乎也能理解为什么在(c)语言中,(double)比(float)能表示更高的精度,因为(double)位数更高,他能表示更多的小数。
-
也能多少的理解浮点数为什么叫浮点数,因为随着我数字的指数不同,小数点的位置也在随之改变。
浮点数的表示:
通常,浮点数可以表示为:(N=r^E*M)。
其中(r)是阶码的底,通常为(2),且与尾数的基数相同。
(E)是阶码,(M)是尾数。
如下所示:
-
阶符 阶码的数值部分 数符 尾数的数值部分 (J_f) (J_1J_2,...,J_m) (S_f) (S_1S_2,...,S_n)
阶码是整数,阶符和阶码共同表示浮点数的表示范围以及小数点的实际位置;
数符表示正负,尾数的数值表示浮点数的精度。
浮点数规格化:
先看门见山讲一下什么叫规格化。
规格化规定尾数的最高数位必须是一个有效值。
通过以上的阅读,我们可以发现,要想让精度最大化,那么我们就需要让尾数部分尽可能的保存有效的数字。
比如说对于这两个数(二进制)
- (2^{10}*0.01)和(2^{01}*0.1).
这两个数是相等的,但是第二个数明显可以在尾数上少保存一位(0),所以这时候我们可以对浮点数进行规格化,让他能表示更高的精度。
所谓规格化,是指通过一定的操作改变浮点数的尾数和阶码的大小,让浮点数(非0)的尾数在最高位保证是一个有效值。
有如下两种方法:
- 左规:当浮点数运算结果为非规格化时需要进行规格化处理,将尾数算术左移一位,并将阶码减一(二进制)。(左规可能需要进行多次)
- 右规:当浮点数运算尾数出现溢出时,也就是双符号为出现了(01/10),需要将尾数算术右移一位并将阶码加一。右规只进行一次。
那么规格化的浮点数的尾数的范围就是(frac{1}{2}leq |M|leq 1)。
分析:
- 假设用原码来表示尾数:
- 正数:
- 数的最大值是(0.11...111),此时真值为(1-2^{-n})。
- 数的最小值时(0.10...000),此时真值为(frac{1}{2})。
- 绝对值的范围为([frac{1}{2},1-2^{-n}])。
- 负数:
- 数的最大值是(1.10...00),此时真值为(-frac{1}{2})。
- 数的最小值是(1.11...11),此时真值为(-(1-2^{-n}))。
- 绝对值的范围是([frac{1}{2},1-2^{-n}])。
- 正数:
- 假设用补码来表示尾数:
- 正数:
- 正数的补码与原码相同,不做分析。
- 负数:
- 负数的最大值为(1.011...1),最小值为(1.00...0)。
- 绝对值得范围为([frac{1}{2}+2^{-n},1])。
- 这里需要注意。尾数的最大值不是(1.10...00)这样的形式,因为(1.10...000)不是一个规格化数。
- 这里我查了一些资料,有两种说法我比较认可,第一个是对于(1.10000),我可以接着对他规格化到(1.00000);第二个是方便机器的设计,对于原码,我可以判断他的尾数最高位是不是(1)来判断他是否规格化,对于补码,我可以判断他的数符和尾数最高位是否相同来判断他是否规格化。
- 正数:
IEEE754标准:
按照(IEEE754)标准,浮点数表示格式如下:
-
数符 阶码(用移码表示) 尾数(用原码表示,隐藏最高位(1)) (m_s) (E) (M)
为了最大幅度的增大浮点数表示精度,我们尾数最高位如果为(1)我们将其隐藏。举个例子,假如说尾数是(1011),那么我们存储(011)。
(float)和(double)都是满足(IEEE754)标准的浮点数。
阶码以移码形式存在。对于短浮点数(float),偏置值为(127),对于长浮点数(double),偏置值为(1023)。
那么可以这么求:我先将(E)的看成补码形式求出其值,然后减去(127/1023)就是他的移码代表的值。
- ((-1)^s*1.M*2^{E-127})(短浮点数)。
- ((-1)^s*1.M*2^{E-1023})(长浮点数)。
浮点数的加减运算:
浮点数运算需要将阶码运算和尾数运算分隔开。且分成以下几步:
- 对阶
- 尾数加减
- 规格化
- 舍入
- 判断溢出:阶码溢出
接下来一一分析。
对阶:
对阶的目的是让两个操作数阶码相等。原则是小阶向大阶看齐的方法。将阶码小的数尾数右移,阶码加一知道阶码相等。当然因为右移需要舍弃数据,所以精度会受影响。
-
尾数加减:
- 对阶后进行定点数加减运算。
-
规格化:
- 按照上文所述的规格化进行左规或者右规。
-
舍入:
- 在对阶和右规的过程中,可能会尾数低位丢失,引起误差,常见的舍入方法有:
- (1:)(0)舍(1)入法:尾数右移的时候如果是(0),就舍去;如果是(1),就在尾数末尾(+1),这样有可能让尾数溢出,需要进行一次右规。(假设那个尾数全是1,加上就会一直进位进位到溢出)。
- (2:)恒置(1)法:尾数丢掉后不管是(1)还是(0),补上(1),这样可能会使尾数变大或者变小。
- 在对阶和右规的过程中,可能会尾数低位丢失,引起误差,常见的舍入方法有:
-
溢出判断:
最后一步需要判断溢出。
在浮点数规格化部分已经知道尾数双符号位出现(01,10),并不表示溢出,将此数右规即可。
浮点数的溢出是由阶码决定的。双符号阶码出现(01/10),这时候就溢出了。
- (10:)阶码小于最小阶码,按机器零处理。
- (01:)阶码大于最大阶码,进入中断处理。