【问题标题】:Calculating Fibonacci Number accurately in C++?在 C++ 中准确计算斐波那契数?
【发布时间】:2012-03-11 00:32:04
【问题描述】:

我真的很困惑。我正在尝试计算斐波那契数,但随着它们变得越来越大,数字开始变得错误。我不知道为什么。

如何使用比内公式计算准确的斐波那契数,我的理解是这应该总是返回一个整数?

这是我一直在尝试的。

http://ideone.com/e6t6h

随着数字的增加,请查看。变得很奇怪?

这里我用 cout.precision(15); 打印出来

http://ideone.com/XREh2

这里我用 cout 打印出来

这里我使用了一个程序循环通过迭代来计算它。

这个比使用比内公式的更准确。

无论如何。任何人有任何我可以查看的代码可以计算 F(n),而无需使用 Binet 公式迭代 (n) 的每个级别?

【问题讨论】:

  • 这两个链接我不能在原版ideone.com/wfDyUideone.com/koFkg
  • 请尝试在这里显示相关信息(例如,你得到了哪些结果,哪些结果是正确的),如果可能的话,一个small 重现问题的代码示例。必须通过三个不同的 ideone 链接来寻找甚至弄清楚你在问什么并不鼓励答案。 :) 如果您在此处发布信息,那么您也不会遇到垃圾邮件防护问题!
  • 你需要学习how to use floating point。您正在用锤子敲击螺丝,它会弯曲并不奇怪。
  • 比奈公式仅适用于实数,不适用于此类的浮点近似。

标签: c++ fibonacci


【解决方案1】:

要使用比内公式准确计算斐波那契数,您需要准确解释 √5。由于 √5 是无理数,因此无法使用 doublefloat 准确表示,因此 Binet 的公式不适用于这些类型(但是,计算中的四舍五入会导致一些小输入的精确结果)。由于斐波那契数是整数,您可以使用 doublefloat 从 Binet 的公式中获得精确结果,以获得更多参数,然后通过四舍五入,

double binet(unsigned int n)
{
    static const double phi = (1 + sqrt(5))*0.5;
    double fib = (pow(phi,n) - pow(1-phi,n))/sqrt(5);
    return round(fib);
}

这将为几乎所有足够小的n 返回正确的结果,结果可以精确地表示为double。然而,这些并不多。 double 通常只有 53 位精度,因此只有小于 253 的斐波那契数可以精确表示为 double(加上一些较大的数可以被足够高的 2 次方整除) .最后一个小于 253 的斐波那契数是 F(77),但 F(78) 可以被 8 整除,因此也可以精确表示为具有 53 位精度的 double。但是,上面只对n <= 70产生了正确的结果,从71开始,舍入误差太大(顺便说一下,Binet公式使用doubles的结果在这里总是太大,所以使用floor而不是@ 987654334@ 也会为 F(71) 产生正确的结果,但不会进一步)。

对于标准数据类型,没有多少斐波那契数可以精确表示,最后一个适合(无符号)64 位类型的是 F(93);对于 128 位,最后一个是 F(186)。对于这么小的索引,直接的迭代算法几乎没有任何收获

unsigned long long fibonacci(unsigned int n)
{
    unsigned long long a = 0, b = 1;
    for(; n > 0; --n)
    {
        b += a;
        a = b-a;
    }
    return a;
}

除非你使用查找表

static const unsigned long long fibs[94] = { 0, 1, 1, 2, ... , 12200160415121876738ull };

为了获得准确的结果,必须将 √5(和/或 φ)视为一个符号常数,并使用它来评估公式。这相当于评估环中的公式

ℤ[φ] = { a + b*φ : a, b ∈ ℤ }

ℚ(√5) 中的代数整数,使用 φ² = 1 + φ 的事实。等价于比奈公式是

φ^n = F(n-1) + φ*F(n)

可用于通过在 O(log n) 步骤中重复平方来有效地计算斐波那契数(但请注意,F(n) 有 Θ(n) 位,因此位运算的数量不能低于 O (n))。比普通重复平方使用更有效的版本

φ^(2n) = (φ^n)² = (F(n-1) + φ*F(n))² = F(n-1)² + φ*2*F(n-1)*F(n) + φ²*F(n)²
       = (F(n-1)² + F(n)²) + φ*(2*F(n-1)*F(n) + F(n)²)

使用φ² = 1 + φ 查找F(2n) = 2*F(n)*F(n-1) + F(n)² = 2*F(n)*F(n+1) - F(n)² = F(n)*(F(n+1) + F(n-1))F(2n+1) = F(n)² + F(n+1)²。这些公式允许从 F(n) 和 F(n+1) 计算 F(2n)、F(2n+1) 和 F(2n+2),每个数字最多两次乘法和两次加法/减法,这给出了算法以 O(log n) 步计算 (F(n),F(n+1)) 对,只有两个数字作为状态(普通重复平方使用四个数字作为状态,需要更多的乘法)。

从左到右的迭代算法是

unsigned long long fib(unsigned int n){
    if (n == 0) return 0;
    unsigned int h = n/2, mask = 1;
    // find highest set bit in n, can be done better
    while(mask <= h) mask <<= 1;
    mask >>= 1;
    unsigned long long a = 1, b = 1, c; // a = F(k), b = F(k+1), k = 1 initially
    while(mask)
    {
        c = a*a+b*b;        // F(2k+1)
        if (n&mask)
        {
            b = b*(b+2*a);  // F(2k+2)
            a = c;          // F(2k+1)
        } else {
            a = a*(2*b-a);  // F(2k)
            b = c;          // F(2k+1)
        }
        mask >>= 1;
    }
    return a;
}

使用任意精度类型而不是unsigned long long,可以快速计算大斐波那契数。但当然,任意精度库通常带有自己优化的 Fibonacci 函数,因此自己实现它是没有实际意义的。

【讨论】:

  • 哇,大卫.. 很棒的答案.. 更重要的是,您以某种方式准确地解码了我的要求... ...您使用了 round(fib);是您创建的一个名为 round 的函数,它使用 if 语句和 ciel() / floor() 进行舍入吗?
  • round 是一个标准库函数,在 C 中来自 math.h,在 C++ 中来自 cmathmath.h,根据个人喜好。它将double 舍入到最接近的double 并带有一个整数值(我不确定,我想如果你#include &lt;cmath&gt; 你会得到一个重载的roundfloat 做同样的事情,在C 中,你会使用roundf)。
【解决方案2】:

一般来说,浮点数和双精度数并不是为了准确地表示数字而设计的。它们的目的是表示大范围内的实数。如果您确实想要无限精度,可以尝试查看http://gmplib.org/

【讨论】:

  • AFAIK GMP 库仅用于有限精度。您需要像 PARI 这样的东西才能在其精确表示中使用像 sqrt(5) 这样的数字。
  • 所以,我应该只使用递归方法计算所有迭代,直到我达到所需的“n”并使用无符号整数来存储它......这会准确吗?作为另一种方法,除非我使用外部库,否则由于无理数总是会失控吗?那么 cout
  • 固定和精确修改它的打印方式。确实,在您的代码中,数字足够小,因此错误很小。我认为 F(35) 应该开始给出浮动问题。同样,浮点数并不精确,因此显示精确值并不友好。不过,使用递归公式要慢得多。您可以尝试将结果转换为 int(或 F(47) 之后的数字的 long long int),然后打印它们。
【解决方案3】:

您是否尝试过包含 &lt;cmath&gt; 而不是 &lt;math.h&gt;,math.h 可能没有像 C 那样重载的 sqrt 版本

【讨论】:

    猜你喜欢
    • 2012-12-03
    • 2013-03-31
    • 2014-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-26
    • 2016-12-11
    • 1970-01-01
    相关资源
    最近更新 更多