Fibonacci matrix-exponentiation
The Fibonacci sequence defined with matrix-exponentiation:
F0 = 0 F1 = 1
- Task
Write a function using matrix-exponentiation to generate Fibonacci number for n = 232 and n = 264.
Display only the 20 first digits and 20 last digits of each Fibonacci number.
- Extra
Generate the same Fibonacci numbers using Binet's formula below or another method.
- Related tasks
Go
This uses matrix exponentiation to calculate the (2^32)nd Fibonacci number which has more than 897 million digits! To improve performance, I've used a GMP wrapper rather than Go's native 'big.Int' type.
I have not attempted to calculate the (2^64)nd Fibonacci number which appears to be well out of reach. <lang go>package main
import (
"fmt" big "github.com/ncw/gmp" "time"
)
type vector = []*big.Int type matrix []vector
var (
zero = new(big.Int) one = big.NewInt(1)
)
func (m1 matrix) mul(m2 matrix) matrix {
rows1, cols1 := len(m1), len(m1[0]) rows2, cols2 := len(m2), len(m2[0]) if cols1 != rows2 { panic("Matrices cannot be multiplied.") } result := make(matrix, rows1) temp := new(big.Int) for i := 0; i < rows1; i++ { result[i] = make(vector, cols2) for j := 0; j < cols2; j++ { result[i][j] = new(big.Int) for k := 0; k < rows2; k++ { temp.Mul(m1[i][k], m2[k][j]) result[i][j].Add(result[i][j], temp) } } } return result
}
func identityMatrix(n uint64) matrix {
if n < 1 { panic("Size of identity matrix can't be less than 1") } ident := make(matrix, n) for i := uint64(0); i < n; i++ { ident[i] = make(vector, n) for j := uint64(0); j < n; j++ { ident[i][j] = new(big.Int) if i == j { ident[i][j].Set(one) } } } return ident
}
func (m matrix) pow(n *big.Int) matrix {
le := len(m) if le != len(m[0]) { panic("Not a square matrix") } switch { case n.Cmp(zero) == -1: panic("Negative exponents not supported") case n.Cmp(zero) == 0: return identityMatrix(uint64(le)) case n.Cmp(one) == 0: return m } pow := identityMatrix(uint64(le)) base := m e := new(big.Int).Set(n) temp := new(big.Int) for e.Cmp(zero) > 0 { temp.And(e, one) if temp.Cmp(one) == 0 { pow = pow.mul(base) } e.Rsh(e, 1) base = base.mul(base) } return pow
}
func fibonacci(n *big.Int) *big.Int {
if n.Cmp(zero) == 0 { return zero } m := matrix{{one, one}, {one, zero}} m = m.pow(n.Sub(n, one)) return m[0][0]
}
func main() {
start := time.Now() n := new(big.Int) n.Lsh(one, 32) s := fibonacci(n).String() fmt.Printf("The digits of the 2^32nd Fibonacci number (%d) are:\n", len(s)) fmt.Printf(" First twenty : %s\n", s[0:20]) fmt.Printf(" Final twenty : %s\n", s[len(s)-20:]) fmt.Printf("\nTook %s\n", time.Since(start))
}</lang>
- Output:
Timings are for an Intel Core i7 8565U machine, using Go 1.13.7 on Ubuntu 18.04:
The digits of the 2^32nd Fibonacci number (897595080) are: First twenty : 61999319689381859818 Final twenty : 39623735538208076347 Took 11m5.991187993s
Although Go supports big.Float, the precision needed to calculate the (2^32)nd Fibonacci number makes the use of Binet's formula impractical. I have therefore used the same method as the Julia entry for my alternative approach which is more than twice as quick as the matrix exponentiation method.
<lang go>package main
import (
"fmt" big "github.com/ncw/gmp" "time"
)
var (
zero = new(big.Int) one = big.NewInt(1) two = big.NewInt(2) three = big.NewInt(3)
)
func lucas(n *big.Int) *big.Int {
var inner func(n *big.Int) (*big.Int, *big.Int) inner = func(n *big.Int) (*big.Int, *big.Int) { if n.Cmp(zero) == 0 { return zero, one } t, q, r := new(big.Int), new(big.Int), new(big.Int) u, v := inner(t.Rsh(n, 1)) t.And(n, two) q.Sub(t, one) u.Mul(u, u) v.Mul(v, v) t.And(n, one) if t.Cmp(one) == 0 { t.Sub(u, q) t.Mul(two, t) r.Mul(three, v) return u.Add(u, v), r.Sub(r, t) } else { t.Mul(three, u) r.Add(v, q) r.Mul(two, r) return r.Sub(r, t), u.Add(u, v) } } t, q, l := new(big.Int), new(big.Int), new(big.Int) u, v := inner(t.Rsh(n, 1)) l.Mul(two, v) l.Sub(l, u) // Lucas function t.And(n, one) if t.Cmp(one) == 0 { q.And(n, two) q.Sub(q, one) t.Mul(v, l) return t.Add(t, q) } return u.Mul(u, l)
}
func main() {
start := time.Now() n := new(big.Int) n.Lsh(one, 32) s := lucas(n).String() fmt.Printf("The digits of the 2^32nd Fibonacci number (%d) are:\n", len(s)) fmt.Printf(" First twenty : %s\n", s[0:20]) fmt.Printf(" Final twenty : %s\n", s[len(s)-20:]) fmt.Printf("\nTook %s\n", time.Since(start))
}</lang>
- Output:
The digits of the 2^32nd Fibonacci number (897595080) are: First twenty : 61999319689381859818 Final twenty : 39623735538208076347 Took 5m2.722505927s
Julia
Because Julia uses the GMP library for its BigInt type, a BigInt cannot be larger than about 2^(2^37). This prevents generation of the 2^64-th fibonacchi number, due to BigInt overflow. The Binet method actually overflows even with the 2^32-nd fibonacchi number, so the Lucas method is used as the alternative method.
<lang julia>const b = [big"1" 1; 1 0] matrixfibonacci(n) = n == 0 ? 0 : n == 1 ? 1 : (b^(n+1))[2,2]
binetfibonacci(n) = ((1+sqrt(big"5"))^n-(1-sqrt(big"5"))^n)/(sqrt(big"5")*big"2"^n) # not used
- Explanation of the below: inner -> F(n/2), F(n/2 - 1), L(n) = F(n) + 2F(n-1), and L(n/2) * F(n/2) = F(n)
function lucasfibonacci(n)
function inner(n) if n == 0 return big"0", big"1" end u, v = inner(n >> 1) q = (n & 2) - 1 u *= u v *= v return isodd(n) ? (BigInt(u + v), BigInt(3 * v - 2 * (u - q))) : (BigInt(2 * (v + q) - 3 * u), BigInt(u + v)) end u, v = inner(n >> 1) l = 2*v - u # the lucas function if isodd(n) q = (n & 2) - 1 return v * l + q end return u * l
end
m2s(bits) = string(matrixfibonacci(big"2"^bits)) l2s(bits) = string(lucasfibonacci(big"2"^bits)) firstlast(s) = (length(s) < 44 ? s : s[1:20] * "..." * s[end-20+1:end])
println("N", " "^23, "Matrix", " "^40, "Lucas\n", "-"^94) println("2^32 ", rpad(firstlast(m2s(32)), 45), rpad(firstlast(l2s(32)), 45))
- println("2^64 ", rpad(firstlast(m2s(64)), 45), rpad(firstlast(l2s(64)), 45))
</lang>
- Output:
N Matrix Lucas ------------------------------------------------------------------------------------------------- 2^32 61999319689381859818...39623735538208076347 61999319689381859818...39623735538208076347
Perl
<lang perl>use strict; use warnings;
use Math::AnyNum qw(:overload fibmod floor); use Math::MatrixLUP;
sub fibonacci {
my $M = Math::MatrixLUP->new([ [1, 1], [1,0] ]); (@{$M->pow(shift)})[0][1]
}
for my $n (16, 32) {
my $f = fibonacci(2**$n); printf "F(2^$n) = %s ... %s\n", substr($f,0,20), $f % 10**20;
}
sub binet_approx {
my($n) = @_; use constant PHI => sqrt(1.25) + 0.5 ; use constant IHP => -(sqrt(1.25) - 0.5); (log(PHI)*$n - log(PHI-IHP))
}
sub nth_fib_first_k_digits {
my($n,$k) = @_; my $f = binet_approx($n); floor(exp($f - log(10)*(floor($f / log(10) + 1))) * 10**$k)
}
sub nth_fib_last_k_digits {
my($n,$k) = @_; fibmod($n, 10**$k);
}
print "\n"; for my $n (16, 32, 64) {
my $first20 = nth_fib_first_k_digits(2**$n, 20); my $last20 = nth_fib_last_k_digits(2**$n, 20); printf "F(2^$n) = %s ... %s\n", $first20, $last20;
}</lang>
- Output:
F(2^16) = 73199214460290552832 ... 97270190955307463227 F(2^32) = 61999319689381859818 ... 39623735538208076347 F(2^16) = 73199214460290552832 ... 97270190955307463227 F(2^32) = 61999319689381859818 ... 39623735538208076347 F(2^64) = 11175807536929528424 ... 17529800348089840187
Sidef
Computing the n-th Fibonacci number, using matrix-exponentiation (this function is also built-in): <lang ruby>func fibonacci(n) {
([[1,1],[1,0]]**n)[0][1]
}
say 15.of(fibonacci) #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]</lang>
First and last 20 digits of the n-th Fibonacci number: <lang ruby>for n in (16, 32) {
var f = fibonacci(2**n)
with (20) {|k| var a = (f // 10**(f.ilog10 - k + 1)) var b = (f % 10**k) say "F(2^#{n}) = #{a} ... #{b}" }
}</lang>
- Output:
F(2^16) = 73199214460290552832 ... 97270190955307463227 F(2^32) = 61999319689381859818 ... 39623735538208076347
More efficient approach, using Binet's formula for computing the first k digits, combined with the built-in method fibmod(n,m) for computing the last k digits: <lang ruby>func binet_approx(n) {
const PHI = (1.25.sqrt + 0.5) const IHP = -(1.25.sqrt - 0.5) (log(PHI)*n - log(PHI-IHP))
}
func nth_fib_first_k_digits(n,k) {
var f = binet_approx(n) floor(exp(f - log(10)*(floor(f / log(10) + 1))) * 10**k)
}
func nth_fib_last_k_digits(n,k) {
fibmod(n, 10**k)
}
for n in (16, 32, 64) {
var first20 = nth_fib_first_k_digits(2**n, 20) var last20 = nth_fib_last_k_digits(2**n, 20) say "F(2^#{n}) = #{first20} ... #{last20}"
}</lang>
- Output:
F(2^16) = 73199214460290552832 ... 97270190955307463227 F(2^32) = 61999319689381859818 ... 39623735538208076347 F(2^64) = 11175807536929528424 ... 17529800348089840187