【问题标题】:Accurate evaluation of 1/1 + 1/2 + ... 1/n row精确评估 1/1 + 1/2 + ... 1/n 行
【发布时间】:2023-03-28 14:53:01
【问题描述】:

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

【问题讨论】:

  • 您是要计算特定的 n 还是 n=infinity。如果是后者,只需使用 x = 2; :-)
  • @Pax:这个系列的无穷和是无限的。
  • 还有一件事我忘记了:如何计算 sum 的确切“准确”值?它也没有定义。
  • @Pax:不,总和是发散的,所以它必须针对特定的 n。你在考虑 1+1/2+1/4+...+1/n^2
  • 啊,抱歉,我以为是你想要的 1,2,4,8,... 序列,而不是 1,2,3,4,...

标签: c++ sum ieee-754


【解决方案1】:

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

另一种方法是使用 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))。

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

如果这是你的作业并且你需要使用简单的加法,你最好按照其他人的建议从最小的一个到最大的相加。

【讨论】:

  • 如果允许的话,这些解决方案比我的要好得多。
  • 啊伽玛。永远贴近我的心!
  • 呃,这是一篇文章中的两个答案。赞成票用于含伽马的公式。 log(exp...) 这件事没有帮助,因为它在累积许多值的同时也有丢失位的问题。
【解决方案2】:

精度不够的原因是float、double、long double类型的精度。他们只存储这么多“小数”位。所以把一个很小的值加到一个很大的值上是没有效果的,小的项在较大的项中“丢失”了。

您要求和的系列有一条“长尾”,即小项加起来应该贡献大。但是如果你按降序求和,那么一段时间后,每个新的小项都将不起作用(即使在此之前,它的大部分小数位都会被丢弃)。一旦达到这一点,您可以再添加 10 亿个术语,如果您一次添加一个,它仍然没有效果。

我认为按升序求和应该为此类系列提供最佳精度,尽管可能存在一些奇怪的极端情况,即由于四舍五入 (1/2) 的幂而导致的错误可能恰好会给出更接近的结果回答一些添加命令而不是其他命令。不过,你可能无法真正预测到这一点。

【讨论】:

  • 问题是系列发散了,所以即使您按升序添加术语,您也可能会遇到每次添加每个新术语都没有效果的地步。
【解决方案3】:

【讨论】:

【解决方案4】:

实际上,如果您正在对大 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,无论多么大,因为所有加法是添加几乎相同数量级的值。嗯,在最坏的非 2 次幂情况下,除了 log(N) 之外的所有值,与 N 相比,这几乎是微不足道的。)

【讨论】:

    【解决方案5】:

    【讨论】:

    • 其实我需要写“我自己的程序”。因为这是某种教育任务。
    • 是的,但是您可以使用 apfloats 并根据需要获得尽可能高的精度,而不是使用低精度双精度。
    【解决方案6】:

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

    您可以通过意识到一段时间后,总和将变得“量化”来进一步提高精度:实际上,当您有 2 位精度时,将 1.3 与 41 相加得到 42,而不是 42.3 - 但您几乎可以达到一个精度通过保持“错误”术语加倍。这称为Kahan Summation。您将计算错误项 (42-41-1.3 == -0.3) 并在下一次添加中通过在下一项添加 0.3 来纠正它,然后再次添加它。

    除了从小到大的排序之外,Kahan 求和可能会尽可能准确。我严重怀疑您是否需要更好的谐波级数 - 毕竟,即使经过 2^45 次迭代(疯狂的很多),您仍然只能处理至少 1/2^45 大的数字,并且一个数量级为 45 (

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

    【讨论】:

    • (请注意,如果您使用 Kahan Summation,您可能需要小心优化设置,否则编译器可能会将其重写为遗忘)。
    【解决方案7】:

    我不确定求和顺序是否起重要作用,我以前没听说过。我猜你想在浮点运算中做到这一点,所以第一件事是考虑更多内联 (1.0/1.0 + 1.0/2.0+1.0/3.0) - 否则编译器将进行整数除法

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

    例如

    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 位精度。那么随着数字变大会出现舍入误差,但总体误差会小于另一个方向

    【讨论】:

    • 使用浮点数学,将 1e-19 加到 1 并查看结果。
    • 是的,您想总结所有小项,这样加 1 的效果就不太可能将它们移出浮点数的尾数部分。
    • 任务定义告诉它扮演角色。当然,在浮点数中。我“以数学方式”写了这一行。是的,我考虑过简单的循环,但它看起来不是很好,因为有大量这样的排列。
    【解决方案8】:

    由于你所有的数字都是有理数,最简单的(也可能是最快的,因为它需要做更少的浮点运算)是用有理数(2 个整数 p,q 的元组)进行计算,然后最后只做一个浮点除法。

    更新要有效地使用此技术,您需要将 bigints 用于 p & 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]
    

    因此,下一个更好的选择可能是保留浮点列表并减少它,将每个步骤中的两个最小数字相加...

    【讨论】:

    • 这可能很棘手。我认为您需要使分母成为所有数字 1 到 n 的乘积,以便能够正确添加它们。这意味着n!这将很快用完 32 位 int。
    • 你没试过,是吗?分母增长很快,是n!对于给定的 n。您必须使用一些 biginteger 实现。
    • 那是真的...我认为可能存在可以简化分数的公约数...我将在内置分数的 lisp 中尝试它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 2020-12-11
    • 2012-03-21
    相关资源
    最近更新 更多