数据的表示与运算-浮点数

数据的表示与运算-浮点数

前言:

计算机中,数字分为定点数和浮点数。相对于浮点数,定点数比较好理解,原码补码反码移码。而浮点数十分繁杂。

关于浮点数的繁杂,我觉得最好的解释就是,\(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:\)阶码大于最大阶码,进入中断处理。
posted @ 2019-11-22 00:08  zhaoxiaoyun  阅读(922)  评论(0编辑  收藏  举报