7

我最近收到的一项家庭作业要求我们采用在计算机中执行时可能会造成精度损失的表达式,并对其进行更改以避免这种损失。

不幸的是,这样做的方向还不是很清楚。通过观看各种示例,我知道有一些方法可以做到这一点:使用泰勒级数,如果涉及平方根则使用共轭,或者当两个分数相减时找到一个公分母。

但是,我很难准确地注意到何时会发生精度损失。到目前为止,我唯一确定的是,当您减去两个接近相同的数字时,会发生精度损失,因为高位数字很重要,并且您会从四舍五入中丢失这些数字。

我的问题是我应该寻找哪些其他常见情况,以及哪些被认为是接近它们的“好”方法?

例如,这里有一个问题:

f(x) = tan(x) − sin(x)  when x ~ 0

从这三个选择中评估这个的最佳和最差算法是什么:

(a) (1/ cos(x) − 1) sin(x),
(b) (x^3)/2
(c) tan(x)*(sin(x)^2)/(cos(x) + 1).

我知道当 x 接近于零时,tan(x) 和 sin(x) 几乎相同。我不明白这些算法如何或为什么在解决问题方面更好或更差。

4

4 回答 4

5

通常使用的另一个经验法则是:当添加一长串数字时,从最接近零的数字开始添加,并以最大的数字结束。

解释为什么这是好的有点棘手。当您将小数添加到大数时,它们有可能会被完全丢弃,因为它们小于当前大数尾数中的最低位。以这种情况为例:

a = 1,000,000;
do 100,000,000 time:
   a += 0.01;

如果 0.01 小于最低尾数位,则循环不执行任何操作,最终结果为 a == 1,000,000 但如果您这样做:

a = 0;
do 100,000,000 time:
   a += 0.01;
a += 1,000,000;

比低数字缓慢增长,您更有可能最终得到接近 == 2,000,000 的值,这是正确的答案。
这当然是一个极端的例子,但我希望你明白这一点。

于 2009-02-02T03:50:57.180 回答
4

当我还是本科生的时候,我不得不上一门数字课,这非常痛苦。总之,IEEE 754是现代 CPU 通常实现的浮点标准。了解它的基础知识很有用,因为这让您对不该做什么有很多直觉。对它的简化解释是,计算机以类似于 base-2 科学记数法的方式存储浮点数,其中指数和尾数具有固定数量的数字(位)。这意味着一个数字的绝对值越大,表示它的精度就越低。对于 IEEE 754 中的 32 位浮点数,一半可能的位模式表示介于 -1 和 1 之间,即使高达约 10^38 的数字可以用 32 位浮点数表示。对于大于 2^24(约 1670 万)的值,32 位浮点数不能准确表示所有整数。

这对您来说意味着您通常希望避免以下情况:

  1. 当预期最终答案很小时,中间值很大。
  2. 在大数中添加/减去小数。例如,如果你写了类似的东西:

    for(浮点索引 = 17000000;索引 < 17000001;索引++){}

此循环永远不会终止,因为 17,000,000 + 1 向下舍入为 17,000,000。如果你有类似的东西:

float foo = 10000000 - 10000000.0001

由于舍入误差,foo 的值将是 0,而不是 -0.0001。

于 2009-02-02T03:53:35.740 回答
2

我的问题是我应该寻找哪些其他常见情况,以及哪些被认为是接近它们的“好”方法?

有几种方法可能会导致严重甚至灾难性的精度损失。

最重要的原因是浮点数的位数有限,例如双精度数有 53 位。这意味着如果您有“无用”的数字,这些数字不是解决方案的一部分,但必须存储,您就会失去精度。

例如(我们使用十进制类型进行演示):

2.5987650000000000000000000000100 -

2.5987650000000000000000000000099

有趣的部分是 100-99 = 1 的答案。由于 2.598765 在两种情况下都相等,因此不会改变结果,但会浪费 8 位。更糟糕的是,由于计算机不知道这些数字是无用的,它被迫存储它并在其后填入 21 个零,浪费了所有 29 个数字。不幸的是,没有办法因差异而规避它,但还有其他情况,例如 exp(x)-1,它是物理学中经常出现的函数。

0 附近的 exp 函数几乎是线性的,但它强制将 1 作为前导数字。所以 12 位有效数字 exp(0.001)-1 = 1.00100050017 - 1 = 1.00050017e-3

如果我们使用函数 expm1(),则使用泰勒级数:

1 + x +x^2/2 +x^3/6 ... -1 =

x +x^2/2 +x^3/6 =: expm1(x)

expm1(0.001) = 1.00500166667e-3

好多了。

第二个问题是具有非常陡峭斜率的函数,例如 pi/2 附近 x 的切线。tan(11) 的斜率为 50000,这意味着之前由舍入误差引起的任何小偏差都会被放大 50000 倍!或者,如果结果接近 0/0,则您有奇点,这意味着它可以具有任何值。

在这两种情况下,您都创建了一个替代函数,只是简化了原始函数。强调不同的解决方法是没有用的,因为如果不进行培训,您将根本无法“看到”问题。

一本非常适合学习和培训的书:Forman S. Acton:Real Computing made real

于 2009-12-08T15:04:09.993 回答
1

要避免的另一件事是减去几乎相等的数字,因为这也会导致对舍入误差的敏感性增加。对于接近 0 的值,cos(x) 将接近 1,因此 1/cos(x) - 1 是您希望尽可能避免的减法之一,所以我会说 (a) 应该避免.

于 2009-02-02T04:32:47.143 回答