购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.1 浮点运算简介

实数的数量是无限的,而浮点表示形式的位数却是有限的,能够表示的值的数量也是有限的。如果特定的浮点格式无法精确地表示某个实数值,就用能够精确表示的最接近该实数的近似值代替。本节介绍浮点格式的原理,帮助读者更深入地理解这种近似造成的影响。

整型和定点格式都存在一些问题。整型完全无法表示小数,只能表示0~2 n -1或-2 n-1 ~2 n-1 -1的整数。定点格式可以表示小数,但牺牲了整数部分能够表示的范围。这是动态范围(Dynamic Range)造成的问题之一,而浮点格式要解决的就是这个问题。

我们以一种简单的16位无符号定点格式为例,这种格式的小数部分和整数部分各占8位。整数部分可以表示0~255的值,小数部分可以表示0及2 -8 ~1的小数(精度大约是2 -8 )。如果位串计算只用到小数值0.0、0.25、0.5和0.75,那么小数部分只需要2位来表示,而剩下的6位全都浪费了。那么能不能利用这些浪费的位,将整数部分的范围从0~255扩展到0~16,383?当然可以,而且这正是浮点表示形式的基本原理。

浮点数值的(二进制)小数点可以根据实际需要在数字之间浮动。如果一个16位二进制数值的小数部分只需要两位精度,那么二进制(小数)点就可以浮动到第1位和第2位之间,而整数部分将占用第2~15位。浮点格式还需要一个额外的字段来指定小数点在数字中的位置,这个字段相当于科学记数法中的指数。

大多数浮点格式使用一部分位来表示尾数,使用余下的几位来表示阶码。尾数(Mantissa)通常是有限范围内(比如0~1)的一个基值。阶码(Exponent)则是一个乘数,将其应用到尾数上之后就可以得到超出尾数范围的值。尾数/阶码组合的最大优势是扩展了浮点格式可以表示的范围。但是,浮点格式将数字分为两部分意味着它能表示的数字只是有一定位数的有效数字(Significant Digit)。如果浮点格式的最大阶码和最小阶码之差大于尾数的有效数字位数(一般都是这样),那么浮点格式就无法将所有该格式下的最小值和最大值之间的整数准确地表示出来。

我们用简化的十迚制(Decimal)浮点格式来说明有限精度算术的影响。这种浮点格式的尾数的有效数字有三位,还有两位是十进制阶码。如图4-1所示,尾数和阶码均为带符号值。

img

图4-1 简化的浮点格式

0.00~9.99×10 99 的全部值都可以用这种特殊的浮点表示形式近似地表示。但是,这种格式却无法准确地表示这个范围内的全部(整数)值(那需要100位精度)。例如,只能将9,876,543,210近似地表示为9.88×10 9 (在编程语言中记为9.88e+9,本书中的浮点数大多采用这种记法)。

同样的值会被浮点格式编码为多种表示(位模式不同),因此浮点格式无法像整型那样使表示的值全都是不同的准确值。例如,对于图4-1中简化的十进制浮点格式来说,1.00e+1和0.10e+2就是一个数值的两种不同表示。浮点格式能够表示的数值总量是有限的。因此,只要一个值存在两种可能的表示形式,浮点格式能够表示的特定数值就会少一个。

此外,浮点格式是一种科学记数法,其运算在某种程度上更加复杂。采用科学记数法的两个数相加或相减时,必须把它们的阶码对齐(调整数值让阶码相等)。例如,当1.23e1和4.56e0相加时,应当先将4.56e0转换为0.456e1再相加。得到的结果1.686e1超出了当前格式三位有效数字的限制,因此必须舍入(Round)或截断(Truncate)。一般来说,舍入的结果最准确,所以舍入后得到结果1.69e1。精度(Precision,即计算中保留的有效数字个数或位数)损失会影响准确性(Accuracy,即计算的正确性)。

这个例子当中的结果能够舍入,是因为在计算过程中保留了四位有效数字。如果浮点运算过程中只能保留三位有效数字,则较小的数字的最后一位会被截断(舍弃),这样得到的计算结果是1.68e1,误差更大。因此,为了提高准确性,计算过程中需要使用额外的数字。这些额外的数字称为保护数字(Guard Digit,对二进制格式来说就是保护位,Guard Bit)。保护数字能够极大地提高长串连续计算的准确性。

单次计算中损失的精度通常是可以承受的。但是,连续浮点运算中累积的精度误差将极大地影响计算本身。以1.23e3和1.00e0相加为例。先把两个数字的阶码调整到一样,再进行加法运算:1.23e3+0.001e3。这两个数字的和舍入后仍为1.23e3。在只能保留三位有效数字的前提下,与非常小的数值进行一次加法运算,结果没有变化,这似乎没什么问题。但是,假设我们将1.23e3和1.00e0相加10次。1.23e3和1.00e0第一次相加得到1.23e3。第二次、第三次、第四次一直到第十次相加,得到结果都是一样的。但如果我们先将10个1.00e0相加,再将结果(1.00e1)和1.23e3相加,将得到不一样的结果1.24e3。这引出了有限精度运算的第一条重要规则:

求值的顺序会影响结果的准确性。

相加或相减的数字的数量级越接近(即阶码大小接近),得到的结果越准确。如果要进行连续的加减法运算,应先对运算进行分组,以便先执行值数量级接近的加减法运算,再执行值数量级相差较大的加减法运算。

假精度(False Precision)是浮点数加减法的另一个问题。以1.23e0-1.22e0为例。得到的结果0.01e0在数学上和1.00e-2等价,但后一种形式明确地表达了最后两位数字(千分位和万分位)都为0的含义。可惜计算最后得到的结果只有一位有效数字(百分位),而且一些FPU或浮点软件包实际上还可能会在低位插入随机数字(或位)。这引出了有限精度运算的第二条重要规则:

将同符号的两个数相减或者将不同符号的两个数相加,得到的结果可能达不到该浮点格式能够表示的最高精度。

浮点数的乘除法不需要在运算前对齐阶码,因此也就不存在加减法中的问题。乘法只需要将阶码相加并将尾数相乘(除法则需要将阶码相减并将尾数相除)。如果运算中只有乘除法,结果不会有太大的误差。但是,数值中已经存在的精度误差会被乘除法放大。例如,本来应该计算1.24e0乘以2,如果算成了1.23e0乘以2,则乘积的精度会更低。这引出了有限精度运算的第三条重要规则:

连续迚行加、减、乘、除运算时,应当首先迚行乘法和除法运算。

在做连续运算时,往往可以通过正常的代数变换重新调整运算的顺序,先进行乘法和除法运算。例如,计算下面这个表达式:

x×(y+z)

一般的做法是先对 y z 求和,再将结果乘以 x 。但是,如果先将表达式做下面这样的变换,计算结果的精度会更高:

x×y+x×z

现在,就可以先进行乘法运算了。

乘法和除法运算也有一些其他问题。先将两个非常大或非常小的数相乘时,可能会发生上溢(Overflow)或下溢(Underflow)。用一个小数除以一个大数或者用一个大数除以一个小数时,也会发生类似的情况。这引出了有限精度运算的第四条规则:

当迚行多个数字乘除法运算时,先迚行相同量级的乘除法运算。

浮点数的比较非常容易出问题。任何运算都自带误差(包括将输入的字符串转换为浮点值),因此绝对不能比较两个浮点值是否相等。虽然不同的运算得到的(数学)结果可能是相同的,但其浮点表示的最小有效位数可能不同。例如,1.31e0和1.69e0 相加得到3.00e0。类似地,1.50e0和1.50e0相加也得到3.00e0。但是,如果比较(1.31e0+1.69e0)与(1.50e0+1.50e0)是否相等,可能得出不相等的结果。两个看似等价的浮点计算得到的结果不一定完全相等,所以直接比较相等性可能出错。当且仅当两个操作数的位(或数字)全部相同时,直接比较相等性才能得到期望的结果。

在比较浮点数是否相等时需要先给出可以接受的误差(或公差),再检查其中一个值是否在另一个值的误差范围内,像这样:

img

下面这种形式的语句更高效:

img

error 的值应该比计算中出现的最大误差略大一些,具体值取决于计算使用的浮点格式和要比较的值的量级。因此,有限精度运算的最后一条规则如下:

在迚行两个浮点数是否相等的比较时,比较的始终是两个值的差是否小于某个很小的误差值。

浮点数的相等性比较是所有入门的编程教材都绕不开的问题。同样的问题在小于或大于比较中也存在,却很少有人关注。假设连续浮点计算的结果误差范围是± error ,那浮点表示形式能够达到比 error 更高的精度。如果将这个结果与其他累积误差更小(小于± error )的计算结果进行比较,则当这两个值非常接近时,小于或大于比较可能会产生错误的结果。

假设采用简化的十进制浮点表示形式的一组连续计算得到了结果1.25,精确到±0.05(但实际值可能是1.20~1.30之间的某个值),而另外一组连续计算得到了结果1.27,精确到该浮点表示形式可以达到的最高精度(也就是说,舍入前的实际值在1.265~1.275)。将第一组计算的结果(1.25)直接与第二组计算的结果进行比较(1.27),结论是第一组计算的结果更小。然而,该结论可能是错误的,因为考虑误差,第一组计算实际的结果可能在1.27~1.30(不包括)之间。

唯一合理的比较是检查两个值是否在对方可以接受的误差之内,如果是,则两个值被视为相等(不会认为一个值小一个值大)。两个值在可接受的误差范围内不相等,才可以比较大小。这被称为吝啬算法(Miserly Approach),即尽量不要进行两个值的小于或大于比较。

另一种可行的方法是贪婪算法(Eager Approach),即尽可能地让相等性比较的结果为true。给定两个需要比较的值和一个可以接受的误差,贪婪算法如下:

img

别忘了( B + error )这样的计算本身也会受误差的影响,这取决于值B和 error 的相对量级,而这种误差可能会影响比较的最终结果。

注意 :由于篇幅所限,本书仅讨论了在使用浮点值的过程中可能会产生的一些主要问题,还有浮点运算不能被当作实数运算的原因。如果需要了解更多关于浮点数的详细信息,请查阅数值分析和科学计算的相关文献。无论使用哪种语言迚行浮点运算,都请花一些时间研究一下有限精度运算对计算的影响。 cKG8JTz6ngpW9EMgfqrZ72IGHGa0V3oWU9s8HUgybP2qE7B/6jAIg+GHC0vsUwtU

点击中间区域
呼出菜单
上一章
目录
下一章
×