如果我想获取浮点数列表的乘积,那么最坏情况/平均情况下的精度损失是通过添加它们的日志然后获取总和而不是仅仅乘以它们来实现的。有没有这种情况实际上更准确?
答案 0 :(得分:8)
如果a
和b
是浮点数,则不存在任何溢出或下溢的恶作剧,则产品a*b
将被计算为1/2 ulp的相对误差。
在乘以N
double
链之后,相对误差的粗略约束因此导致最多因子(1-epsilon / 2) -N < / sup>,大约是exp(epsilon N
/ 2)。我想你在平均情况下可能会出现大约epsilon sqrt(N
)的偏差。 (首先,这是关于N epsilon。)
此策略更可能发生指数溢出和下溢;由于次正规的四舍五入,你更有可能得到无穷大,零和NaN以及不精确的值。
另一种方法在这个意义上更加强大,但是在直接方法不会导致溢出或下溢的情况下,它会慢得多,而且更糟糕。这是对标准双打的非常非常粗略的分析,其中N至少比2 53 小几个数量级:
你总是可以获取有限浮点数的对数并得到一个有限的浮点数,所以我们在那里很酷。您可以直接添加N
个浮点数来获得N
epsilon最坏情况“相对”错误和sqrt(N)epsilon预期的“相对”错误,或使用Kahan summation来得到大约3 epsilon最坏情况的“相对”错误。吓唬报价是“相对的”,因为误差是相对于你总结的事物的绝对值之和。
请注意,没有有限double
具有对数,其绝对值大于710左右。这意味着我们使用Kahan求和计算的对数和的绝对误差至多为2130 N epsilon。当我们对对数和的取幂进行取幂时,我们从正确的答案得到的东西最多为exp(2130 N epsilon)。
log-sum-exp方法的病理示例:
int main() {
double foo[] = {0x1.000000000018cp1023, 0x1.0000000000072p-1023};
double prod = 1;
double sumlogs = 0;
for (int i = 0; i < sizeof(foo) / sizeof(*foo); i++) {
prod *= foo[i];
sumlogs += log(foo[i]);
}
printf("%a %a\n", foo[0], foo[1]);
printf("%a %a %a\n", prod, exp(sumlogs), prod - exp(sumlogs));
}
在我的平台上,我得到了0x1.fep-44的差异。我确信有更糟糕的例子。