准确评估1/1 + 1/2 + ... 1 / n行

时间:2009-08-08 12:08:12

标签: c++ sum ieee-754

我需要评估行的总和:1/1 + 1/2 + 1/3 + ... + 1 / n。考虑到在C ++评估中并不完全准确,求和的顺序起着重要作用。 1 / n + 1 /(n-1)+ ... + 1/2 + 1/1表达式给出更准确的结果。 所以我需要找出求和的顺序,它提供了最大的准确性。 我甚至不知道从哪里开始。 首选的实现语言是C ++。 对不起我的英文,如果有任何错误。

8 个答案:

答案 0 :(得分:14)

对于大n,你最好使用渐近公式,比如http://en.wikipedia.org/wiki/Harmonic_number上的公式;

alt text

另一种方法是使用exp-log转换。基本上是:

H_n = 1 + 1/2 + 1/3 + ... + 1 / n = log(exp(1 + 1/2 + 1/3 + ... + 1 / n))= log(exp (1)* exp(1/2)* exp(1/3)* ... * exp(1 / n))。

标准库可以非常快速准确地计算指数和对数。使用乘法,你应该得到更准确的结果。

如果这是你的作业,你需要使用简单的添加,你最好从最小的一个添加到最大的一个,正如其他人建议的那样。

答案 1 :(得分:5)

缺乏准确性的原因是float,double和long double类型的精度。他们只存储这么多“十进制”的地方。因此,将一个非常小的值添加到一个较大的值没有任何影响,小的术语在较大的一个中“丢失”。

你总结的系列有一个“长尾”,从某种意义上说,小的术语应该是一个很大的贡献。但是如果按降序求和,那么一段时间后每个新的小项都没有效果(甚至在此之前,它的大部分小数位将被丢弃)。一旦你达到这一点,你可以增加十亿个术语,如果你一次一个,它仍然没有效果。

我认为按升序求和应该为这类序列提供最佳精度,尽管有可能存在一些奇怪的极端情况,由于舍入到(1/2)的幂而导致的误差可能恰好使得更接近回答一些额外的订单比其他。不过,你可能无法真正预测到这一点。

答案 2 :(得分:5)

  

我甚至不知道从哪里开始。

此处:What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 3 :(得分:3)

实际上,如果您正在对大N进行求和,那么从最小到最大的顺序添加并不是最好的方法 - 您仍然可能会遇到这样一种情况,即您添加的数字相对于总之,以产生准确的结果。

以这种方式查看问题:无论排序如何,您都有N个总结,并且您希望总误差最小。因此,您应该能够通过最小化每个求和的误差来获得最小的总误差 - 并且通过添加尽可能彼此接近的值来最小化求和中的误差。我相信遵循那条逻辑链给你一个部分和的二叉树:

Sum[0,i] = value[i]

Sum[1,i/2] = Sum[0,i] + Sum[0,i+1]

Sum[j+1,i/2] = Sum[j,i] + Sum[j,i+1]

等等,直到你得到一个答案。

当然,当N不是2的幂时,你最终会在每个阶段留下残羹剩饭,你需要在下一阶段进行总结。

(StackOverflow的边缘当然太小,无法包含这是最佳的证据。部分原因是因为我没有花时间来证明它。但它确实适用于任何N,无论多大都是如此增加的数值几乎相同。嗯,除了log(N)之外,它们都是最差的2次幂,而且与N相比,这个数字很小。)

答案 4 :(得分:2)

答案 5 :(得分:1)

除非你使用一些准确的封闭形式表示,否则从小到大的有序求和可能是最准确的简单解决方案(我不清楚为什么log-exp会有所帮助 - 这是一个巧妙的技巧,但你据我所知,在这里没有赢得任何东西。

通过实现一段时间后,总和将变为“量化”,你可以进一步获得精确度:实际上,当你有2位数的精度时,增加1.3到41会产生42,而不是42.3 - 但你几乎达到了精度通过维持“错误”术语加倍。这称为Kahan Summation。您将计算错误项(42-41-1.3 == -0.3)并在下一次添加中更正0.3,然后再将其添加到下一项中。

除了小到大的订购之外,Kahan Summation可能会像您需要的那样准确。我严重怀疑你对谐波系列有什么需要更好 - 毕竟,即使经过2 ^ 45次迭代(疯狂很多),你仍然只能处理至少1/2 ^ 45大的数字,并且对于一个数量级为45的幂(< 2 ^ 6)的总和,对于51次幂的一个数量级的差异 - 即使你加入“错误”的顺序,甚至仍然可以在双精度变量中表示。 / p>

如果你从小到大,并使用Kahan Summation,太阳可能会在今天的处理器达到错误百分比之前熄灭 - 而且由于单个术语错误,你将遇到其他棘手的准确性问题首先无论如何缩放(因为无论如何,无法将2 ^ 53或更大的数量的数字准确地表示为双精度。)

答案 6 :(得分:0)

我不确定总和的顺序起着重要的作用,我之前没有听说过。我想你想在浮点运算中这样做,所以首先要考虑更多内联(1.0 / 1.0 + 1.0 / 2.0 + 1.0 / 3.0) - 否则编译器会进行整数除法

确定评估顺序,可能是for循环还是括号?

e.g。

float f = 0.0;
for (int i=n; i>0; --i) 
{
    f += 1.0/static_cast<float>(i);
}
哦,忘了说,编译器通常会有开关来确定浮点评估模式。这可能与你在求和顺序上的说法有关 - 在Visual C +中这些可以在代码生成编译设置中找到,在g ++中有选项-float来处理这个

实际上,另一个人是对的 - 你应该先按最小分量顺序进行求和;所以 1 / n + 1 /(n-1).. 1/1

这是因为浮点数的精度与比例相关联,如果从1开始,则相对于1.0,您将具有23位精度。如果从较小的数字开始,精度相对于较小的数字,那么相对于1xe-200或其他任何东西,你将获得23位精度。然后随着数字越大,将发生舍入误差,但整体误差将小于另一个方向

答案 7 :(得分:0)

由于你的所有数字都是有理数的,最简单的(也可能是最快的,因为它将不得不做更少的浮点运算)将是有理数的计算(2个整数p,q的元组),然后最后只做一个浮点分区。

更新要有效地使用此技术,您需要使用bigint进行p&amp; q,因为它们增长得相当快......

Lisp中的一个快速原型,内置了有理数表明:

(defun sum_harmonic (n acc)
  (if (= n 0) acc (sum_harmonic (- n 1) (+ acc (/ 1 n)))))

(sum_harmonic 10 0)
7381/2520
[2.9289682]

(sum_harmonic 100 0)
14466636279520351160221518043104131447711/278881500918849908658135235741249214272
[5.1873775]

(sum_harmonic 1000 0)

53362913282294785045591045624042980409652472280384260097101349248456268889497101
75750609790198503569140908873155046809837844217211788500946430234432656602250210
02784256328520814055449412104425101426727702947747127089179639677796104532246924
26866468888281582071984897105110796873249319155529397017508931564519976085734473
01418328401172441228064907430770373668317005580029365923508858936023528585280816
0759574737836655413175508131522517/712886527466509305316638415571427292066835886
18858930404520019911543240875811114994764441519138715869117178170195752565129802
64067621009251465871004305131072686268143200196609974862745937188343705015434452
52373974529896314567498212823695623282379401106880926231770886197954079124775455
80493264757378299233527517967352480424636380511370343312147817468508784534856780
21888075373249921995672056932029099390891687487672697950931603520000
[7.485471]

所以,下一个更好的选择可能是保留浮点列表并减少每个步骤中两个最小数字的总和......