关于计算机浮点数

今天看到浮点数的计算,发现之前的计算机理论都忘光了,再学习一遍,重新整理一下。

一 浮点数表达方式: 这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。提示: 尾数有时也称为有效数字(Significand)。尾数实际上是有效数字的非正式说法。

  同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:

  d.dd...d × βe , (0 ≤ di < β)

  其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p(presion) 来表示。每个数字 d 介于 0 和基数β之间,包括 0。小数点左侧的数字不为 0。

(1)  基于规范表达的浮点数对应的具体值可由下面的表达式计算而得:(p是精度个数)

  ±(d0 + d1β-1 + ... + dp-1β-(p-1))βe , (0 ≤ di < β)

  对于十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解,也很直白。计算机内部的数值表达是基于二进制的。从上面的表达式,我们可以知道,二进制数同样可以有小数点,也 同样具有类似于十进制的表达方式。只是此时 β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。

(2)  比如二进制数 1001.101 相当于:精度为7

   1 × 2 3 + 0 × 22 + 0 × 21 + 1 × 20 + 1 × 2-1 + 0 × 2-2 + 1 × 2-3,对应于十进制的 9.625。

  其规范浮点数表达为 1.001101 × 23。

(3)  IEEE (美国电气和电子工程师学会)浮点数

  计算机中是用有限的连续字节保存浮点数的。

  IEEE定义了多种浮点格式,但最常见的是三种类型:单精度、双精度、扩展双精度,分别适用于不同的计算要求。一般而言,单精度适合一般计算,双精度适合科学计算,扩展双精度适合高精度计算。一个遵循IEEE 754标准的系统必须支持单精度类型(强制类型)、最好也支持双精度类型(推荐类型),至于扩展双精度类型可以随意。单精度(Single Precision)浮点数是32位(即4字节)的,双精度(Double Precision)浮点数是64位(即8字节)的。

  保存这些浮点数当然必须有特定的格式,Java 平台上的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。注意: Java 平台还支持该标准定义的两种扩展格式,即 float-extended-exponent 和 double-extended-exponent 扩展格式。这里将不作介绍,有兴趣的读者可以参考相应的参考资料。

  在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。

  具体的格式参见下面的表格:

  关于计算机浮点数_第1张图片

  需要特别注意的是,扩展双精度类型没有隐含位,因此它的有效位数与尾数位数一致,而单精度类型和双精度类型均有一个隐含位,因此它的有效位数比位数位数多一个。

       关于计算机浮点数_第2张图片

  IEEE754标准规定一个实数V可以用:  V=(-1)s×M×2^E的形式表示,说明如下:
  (1)符号s(sign)决定实数是正数(s=0)还是负数(s=1),对数值0的符号位特殊处理。
  (2)有效数字M是二进制小数,M的取值范围在1≤M<2或0≤M<1。
  (3)指数E(exponent)是2的幂,它的作用是对浮点数加权。

 

二 IEEE特殊值规定

NaN错误 
实数范围内发生对负数开平方时产生此错误
指数域全为1,尾数域不全为0,表示该错误
正负无穷大 
两个大数相乘产生的上溢,IEEE规定此时不是将结果舍入为可以保存的最大的浮点数(因为这个数可能离实际的结果相差太远而毫无意义),而是将其舍入为无穷。
指数域全1,尾数域全0,表示该值
正负0 
IEEE 标准的浮点数格式中,小数点左侧的1是隐藏的,而零显然需要尾数必须是零。因此零也就无法直接用这种格式表达而只能特殊处理
指数域全0,尾数域全0,表示该值
非规范化浮点数:±0.bbbb...∗2i±0.bbbb...∗2i 
两个极小数相减的差可能会下溢
指数域全0,尾数域不全0,表示此时采用非规范浮点数,非规范浮点数的指数偏差比规范浮点数大1。

 

三 范围和精度

 很多小数根本无法在二进制计算机中精确表示(比如最简单的 0.1)由于浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过程直到由此得到的尾数足以填满尾数域,之后对多余的位进行舍入。

  换句话说,除了我们之前讲到的精度问题之外,十进制到二进制的变换也并不能保证总是精确的,而只能是近似值。

  事实上,只有很少一部分十进制小数具有精确的二进制浮点数表达。再加上浮点数运算过程中的误差累积,结果是很多我们看来非常简单的十进制运算在计算机上却往往出人意料。这就是最常见的浮点运算的"不准确"问题。

  参见下面的 Java 示例:

System.out.print("34.6-34.0=" + (34.6f-34.0f));

  这段代码的输出结果如下:

34.6-34.0=0.5999985

  产生这个误差的原因是 34.6 无法精确的表达为相应的浮点数,而只能保存为经过舍入的近似值。这个近似值与 34.0 之间的运算自然无法产生精确的结果。

四 舍入

  值得注意的是,对于单精度数,由于我们只有 24 位的尾数(其中一位隐藏),所以可以表达的最大指数为 224 - 1 = 16,777,215。

  特别的,16,777,216 是偶数,所以我们可以通过将它除以 2 并相应地调整指数来保存这个数,这样 16,777,216 同样可以被精确的保存。相反,数值      16,777,217 则无法被精确的保存。由此,我们可以看到单精度的浮点数可以表达的十进制数值中,真正有效的数字不高于 8 位。

  事实上,对相对误差的数值分析结果显示有效的精度大约为 7.22 位。

  实例如下所示:

  关于计算机浮点数_第3张图片

  根 据标准要求,无法精确保存的值必须向最接近的可保存的值进行舍入。这有点像我们熟悉的十进制的四舍五入,即不足一半则舍,一半以上(包括一半)则进。不过 对于二进制浮点数而言,还多一条规矩,就是当需要舍入的值刚好是一半时,不是简单地进,而是在前后两个等距接近的可保存的值中,取其中最后一位有效数字为 零者。从上面的示例中可以看出,奇数都被舍入为偶数,且有舍有进。我们可以将这种舍入误差理解为"半位"的误差。所以,为了避免 7.22 对很多人造成的困惑,有些文章经常以 7.5 位来说明单精度浮点数的精度问题。

  提示: 这里采用的浮点数舍入规则有时被称为舍入到偶数(Round to Even)。相比简单地逢一半则进的舍入规则,舍入到偶数有助于从某些角度减小计算中产生的舍入误差累积问题。因此为 IEEE 标准所采用。

你可能感兴趣的:(关于计算机浮点数)