计算斐波那契

时间:2010-12-01 18:43:31

标签: c#-4.0 fibonacci

我发送了这个非常好的非递归函数来计算斐波那契序列。

alt text

所以我编写了一些c#并且能够验证所有高达1474的数字是否正确。

尝试计算1475及以上时出现问题。我的c#数学技能不能达到找出不同方法的任务。那么,有人有更好的方法在c#中表达这个特定的数学函数吗?除了传统的递归函数方式之外?

顺便说一下,我开始使用BigInteger作为返回类型。但是当试图将(1 + Math.Sqrt(5)/ 2)提升到1475次幂时,问题确实存在。我只是没有看到我需要什么样的数据类型(也没有这个问题的机制)来让它回到Infinity以外的东西。

这是一个起点。

private Double FibSequence(Int32 input) {
    Double part1 = (1 / Math.Sqrt(5));
    Double part2 = Math.Pow(((1 + Math.Sqrt(5)) / 2), input);
    Double part3 = Math.Pow(((1 - Math.Sqrt(5)) / 2), input);

    return (part1 * part2) - (part1 * part3);
}

而且,不,这不是家庭作业。对于缓慢的一天来说只是一个“简单”的问题。

9 个答案:

答案 0 :(得分:14)

我不认为C#的数据类型具有足够的浮动精度和范围来处理这个问题。

如果你真的想沿着这条路走,你可以注意到共轭\Phi=\phi^{-1}=\phi-1=\frac{-1+\sqrt 5}{2}小于1,所以-\frac{(-\Phi)^n}{\sqrt 5}与舍入到最接近的整数相同,因此你可以简化你的找到\left\lfloor\frac{\phi^n}{\sqrt 5}+\frac12\right\rfloor的解决方案。然后使用二项式扩展,这样您只需要使用适当的 a b 计算\left\lfloor a+b\sqrt 5\right\rfloor(这是合理的,可以使用BigInteger精确计算)。如果你仍然回到Double这个,你仍然不会比1475更远,但你应该能够弄清楚如何做这个部分只有精确的整数数学☺

\frac{\phi^n}{\sqrt 5}=\frac{(1+\sqrt 5)^n}{2^n\sqrt 5}=\frac{\sum_{k=0}^n{n\choose k}\sqrt 5^k}{2^n\sqrt 5}
=\left(\sum_{k=0}^{\left\lfloor\frac{n-1}2\right\rfloor}\frac{{n\choose 2k+1}5^k}{2^n}\right)+\left(\sum_{k=0}^{\left\lfloor\frac n2\right\rfloor}\frac{{n\choose 2k}5^{k-1}}{2^n}\right)\sqrt 5


使用矩阵求幂计算Fibonacci数的另一种简洁方法是:

\left(\begin{matrix}1&1\1&0\end{matrix}\right)^n=\left(\begin{matrix}F_n&F_{n-1}\F_{n-1}&F_{n-2}\end{matrix}\right)

如果你聪明的话,这可以在O(log n)中完成。


我最终在Haskell中实现了这些。 fib1是矩阵求幂,fib2是闭式公式的精确整数转换,如上所述。它们各自的运行时看起来像这样,由Criterion编译时由GHC 7.0.3衡量: Matrix exponentiation runtime Closed-form runtime

import Control.Arrow
import Data.List
import Data.Ratio

newtype Matrix2 a = Matrix2 (a, a, a, a) deriving (Show, Eq)
instance (Num a) => Num (Matrix2 a) where
    Matrix2 (a, b, c, d) * Matrix2 (e, f, g, h) =
        Matrix2 (a*e+b*g, a*f+b*h, c*e+d*g, c*f+d*h)
    fromInteger x = let y = fromInteger x in Matrix2 (y, 0, 0, y)
fib1 n = let Matrix2 (_, x, _, _) = Matrix2 (1, 1, 1, 0) ^ n in x

binom n =
    scanl (\a (b, c)-> a*b `div` c) 1 $
    takeWhile ((/=) 0 . fst) $ iterate (pred *** succ) (n, 1)
evens (x:_:xs) = x : evens xs
evens xs = xs
odds (_:x:xs) = x : odds xs
odds _ = []
iterate' f x = x : (iterate' f $! f x)
powers b = iterate' (b *) 1
esqrt e n = x where
    (_, x):_ = dropWhile ((<=) e . abs . uncurry (-)) $ zip trials (tail trials)
    trials = iterate (\x -> (x + n / x) / 2) n
fib' n = (a, b) where
    d = 2 ^ n
    a = sum (zipWith (*) (odds $ binom n) (powers 5)) % d
    b = sum (zipWith (*) (evens $ binom n) (powers 5)) % d
fib2 n = numerator r `div` denominator r where
    (a, b) = fib' n
    l = lcm (denominator a) (denominator a)
    r = a + esqrt (1 % max 3 l) (b * b / 5) + 1 % 2

答案 1 :(得分:4)

using System;
using Nat = System.Numerics.BigInteger; // needs a reference to System.Numerics

class Program
{
    static void Main()
    {
        Console.WriteLine(Fibonacci(1000));
    }

    static Nat Fibonacci(Nat n)
    {
        if (n == 0) return 0;
        Nat _, fibonacci = MatrixPower(1, 1, 1, 0, Nat.Abs(n) - 1, out _, out _, out _);
        return n < 0 && n.IsEven ? -fibonacci : fibonacci;
    }

    /// <summary>Calculates matrix power B = A^n of a 2x2 matrix.</summary>
    /// <returns>b11</returns>
    static Nat MatrixPower(
        Nat a11, Nat a12, Nat a21, Nat a22, Nat n,
        out Nat b12, out Nat b21, out Nat b22)
    {
        if (n == 0)
        {
            b12 = b21 = 0; return b22 = 1;
        }

        Nat c12, c21, c22, c11 = MatrixPower(
            a11, a12, a21, a22,
            n.IsEven ? n / 2 : n - 1,
            out c12, out c21, out c22);

        if (n.IsEven)
        {
            a11 = c11; a12 = c12; a21 = c21; a22 = c22;
        }

        b12 = c11 * a12 + c12 * a22;
        b21 = c21 * a11 + c22 * a21;
        b22 = c21 * a12 + c22 * a22;
        return c11 * a11 + c12 * a21;
    }
}

答案 2 :(得分:3)

Double数据类型的上限值为1.7 x 10 ^ 308

1474的计算包括一步的值~1.1 x 10 ^ 308。

所以,到1475年,你肯定超过了Double所能代表的。不幸的是,C#唯一更大的原语,十进制(一个128位数)被设计成具有非常高的进动但是具有相对小的范围(仅高达大约10 ^ 28)。

如果没有设计可以处理大于10 ^ 308且具有一定程度的小数精度的数字的自定义数据类型,我看不到这样做的方法。也就是说,那里的人可能已经上过这样的课程,因为我可以想象它可以派上用场。

见double:http://msdn.microsoft.com/en-us/library/678hzkk9(v=VS.80).aspx

和十进制:http://msdn.microsoft.com/en-us/library/364x0z75(v=VS.80).aspx

答案 3 :(得分:2)

'Solver Foundation' library似乎包含一些“大”数字类型。它的Rational类型可能会为您提供所需的精度和范围。它表示理性为两个BigInteger值的比率。 (它带来了自己的BigInteger - 我猜它是在.NET 4发布之前编写的。)

理论上,它使它能够代表非常大的数字,但也代表了很高的精度。 (显然你的公式不涉及有理数,但浮点数也是这里的近似值。)

它提供了一种方法,可以将Rational提升为其他内容的力量:http://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational.power(v=VS.93).aspx

答案 4 :(得分:1)

正如您正确指出的那样,BigInteger没有实现Sqrt方法。 你可以自己实现它:

Calculate square root of a BigInteger (System.Numerics.BigInteger)

但是你的代码仍然存在精度问题。

答案 5 :(得分:1)

这里的许多答案表明复杂性可以最小化为O(log(n))。为什么不尝试log(n)方法的整数实现?

首先,请考虑从Fibonacchi的序列中给出两个术语:F(n)F(n+1)。逻辑上,较大的术语F(n+k)可以写为F(n)F(n+1)的线性函数

 F(n+k) = Ck1*F(n) + Ck2*F(n+1)

你可以只计算这些系数(仅取决于k),(有趣的是,它们也是斐波那契序列!)并使用它们更快地前进,然后再次计算它们以获得更大的{{{{ 1}}能够更快地前进,等等。

答案 6 :(得分:1)

最快(最脏)? :d

private Double dirty_math_function(Int32 input){
       Double part1 = (1 / Math.Sqrt(5));
       Double part2 = Math.Pow(((1 + Math.Sqrt(5)) / 2), input);
       Double part3 = Math.Pow(((1 - Math.Sqrt(5)) / 2), input);
       return (part1 * part2) - (part1 * part3);
 }

private Double FibSequence(Int32 input) {
  if(input < 1475)
       return dirty_math_function(input);
  else{
       return (FibSequence(input -1) + FibSequence(intput -2));
  }
}

答案 7 :(得分:0)

问题是(5 ^(1/2)^ 1475)容易溢出int。你要做的是编写一个“大数学”库来处理从内存中进行数学运算(逐位)而不是使用硬类型数据类型。它是一种痛苦,但我知道。查找正方形和乘法方法。

答案 8 :(得分:0)

  1. 这不是一个精确的公式,它只会给你估计的价值。并且,由于浮点运算限制为每个数字6-8个字节,因此偏差将随着数字的增加而增加。
  2. 为什么不在循环中使用大整数,它应该可以正常工作。远远好于浮点。