Escreva o mais rápido Fibonacci

10

Este é mais um desafio sobre os números de Fibonacci.

O objetivo é calcular o 20'000'000 th número Fibonacii o mais rápido possível. A saída decimal é aproximadamente 4 MiB grande; começa com:

28543982899108793710435526490684533031144309848579

A soma MD5 da saída é

fa831ff5dd57a830792d8ded4c24c2cb

Você deve enviar um programa que calcule o número durante a execução e coloque o resultado em stdout. O programa mais rápido, medido em minha própria máquina, vence.

Aqui estão algumas regras adicionais:

  • Você deve enviar o código-fonte e um executável binário em um Linux x64
  • O código fonte deve ser menor que 1 MiB; no caso de montagem, também é aceitável se apenas o binário for pequeno.
  • Você não deve incluir o número a ser calculado no seu binário, mesmo de maneira disfarçada. O número deve ser calculado em tempo de execução.
  • Meu computador possui dois núcleos; você tem permissão para usar paralelismo

Tirei uma pequena implementação da Internet, que é executada em cerca de 4,5 segundos. Não deve ser muito difícil superar isso, supondo que você tenha um bom algoritmo.

FUZxxl
fonte
11
Cara, qualquer coisa como Sage que tenha precisão indeterminada de flutuação executará essa coisa em menos de 1/10 de segundo. É apenas uma expressão simples comophi = (1+sqrt(5))/2
JBernardo 16/07
4
Podemos imprimir o número em hexadecimal?
Keith Randall
2
@Keith Nope. Isso faz parte das especificações.
FUZxxl
3
Como ele deve ser medido em sua CPU, é possível que tenhamos mais informações sobre isso, não é? Intel ou AMD? Tamanho dos caches L1 e de instrução? Extensões do conjunto de instruções?
JB
2
Enquanto eu calculo, sua string de início e MD5 são para o número 20'000'000th, não os meros 2'000'000th.
JB

Respostas:

4

C com GMP, 3,6s

Deuses, mas o GMP torna o código feio. Com um truque no estilo Karatsuba, consegui reduzir para duas multiplicações por etapa de duplicação. Agora que estou lendo a solução da FUZxxl, não sou a primeira a ter a idéia. Tenho mais alguns truques na manga ... talvez os experimente mais tarde.

#include <gmp.h>
#include <stdio.h>

#define DBL mpz_mul_2exp(u,a,1);mpz_mul_2exp(v,b,1);mpz_add(u,u,b);mpz_sub(v,a,v);mpz_mul(b,u,b);mpz_mul(a,v,a);mpz_add(a,b,a);
#define ADD mpz_add(a,a,b);mpz_swap(a,b);

int main(){
    mpz_t a,b,u,v;
    mpz_init(a);mpz_set_ui(a,0);
    mpz_init(b);mpz_set_ui(b,1);
    mpz_init(u);
    mpz_init(v);

    DBL
    DBL
    DBL ADD
    DBL ADD
    DBL
    DBL
    DBL
    DBL ADD
    DBL
    DBL
    DBL ADD
    DBL
    DBL ADD
    DBL ADD
    DBL
    DBL ADD
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL
    DBL /*Comment this line out for F(10M)*/

    mpz_out_str(stdout,10,b);
    printf("\n");
}

Construído com gcc -O3 m.c -o m -lgmp.

boothby
fonte
RI MUITO. Para além de uma nomeação identificador, que é exatamente a minha solução :)
JB
@JB: PRIMEIRO! : D
Boothby
Mantenha-o;) O próximo truque na manga irá beneficiar de Haskell mais do que de C.
JB
O primeiro truque na manga esbarrou em um bug do GHC. Drat. Vou ter que voltar ao segundo, que não é tão divertido de implementar remotamente, por isso levará tempo e motivação.
JB
3,6 segundos na minha máquina.
FUZxxl 19/07/11
11

Sábio

Hmm, você parece supor que o mais rápido será um programa compilado. Não é binário para você!

print fibonacci(2000000)

Na minha máquina, são necessários 0,10 cpu segundos, 0,15 segundos na parede.

editar: cronometrado no console, em vez do notebook

boothby
fonte
11
Minha idéia era não saber o quão rápido o seu CAS pode fazer isso, mas o quão rápido você pode codificar isso sozinho.
FUZxxl
11
Para constar, eu apenas coloco isso para ser um espertinho; você não disse para não usar builtins.
Boothby
5

Haskell

Esta é minha própria tentativa, embora eu não tenha escrito o algoritmo sozinho. Eu o copiei do haskell.org e o adaptei para usar Data.Vectorcom sua famosa fusão de fluxo:

import Data.Vector as V
import Data.Bits

main :: IO ()
main = print $ fib 20000000

fib :: Int -> Integer
fib n = snd . V.foldl' fib' (1,0) . V.dropWhile not $ V.map (testBit n) $ V.enumFromStepN (s-1) (-1) s
    where
        s = bitSize n
        fib' (f,g) p
            | p         = (f*(f+2*g),ss)
            | otherwise = (ss,g*(2*f-g))
            where ss = f*f+g*g

Isso leva cerca de 4,5 segundos quando compilado com o GHC 7.0.3 e os seguintes sinalizadores:

ghc -O3 -fllvm fib.hs
FUZxxl
fonte
Estranho ... eu precisava mudar 20000000 para 40000000 para imprimir o número esperado.
JB
Peguei vocês. Deve ser enumFromStepN (s-1)em vez deenumFromStepN s
JB
@JB Desculpe por toda essa confusão. Inicialmente, testei o programa com valores diferentes para obter um número razoavelmente grande e salvei a saída em arquivos diferentes. Mas de alguma maneira eu os confundi. Atualizei o número para corresponder ao resultado desejado.
FUZxxl 19/07
@ boothby Não, não alterei o número de fibonacci desejado, mas a saída de referência, que estava errada.
FUZxxl
Nota lateral: são cerca de 1,5s na minha máquina, mas nem o LLVM nem o Data.Vector parecem trazer qualquer vantagem significativa.
JB
4

VACA

 MoO moO MoO mOo MOO OOM MMM moO moO
 MMM mOo mOo moO MMM mOo MMM moO moO
 MOO MOo mOo MoO moO moo mOo mOo moo

Moo! (Demora um pouco. Beba um pouco de leite ...)

Timtech
fonte
11
Nota: Embora este realmente funciona, ele provavelmente nunca chegar a 20 milhões ...
Timtech
2

Mathematica, interpretado:

First@Timing[Fibonacci[2 10^6]]

Cronometrado:

0.032 secs on my poor man's laptop.

E, claro, não binário.

Dr. belisarius
fonte
Não imprime para stdout.
Boothby
@boothby Errado. Ele grava na saída padrão se você usar a interface da linha de comandos. Veja, por exemplo, stackoverflow.com/questions/6542537/…
Dr. belisarius
Não, estou usando a interface da linha de comandos, versão 6.0. Mesmo usando -batchoutput, ele apenas imprime informações de tempo e não o número de Fibonacci.
Boothby
Desculpe, não consigo reproduzir, pois não tenho o mathematica.
FUZxxl 19/07/11
5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... É executado em tempo constante com relação à velocidade da sua conexão com a Internet. ;-)
ESultanik
2

Ocaml, 0.856s no meu laptop

Requer a biblioteca zarith. Eu usei o Big_int, mas ele é lento em comparação com o zarith. Demorou 10 minutos com o mesmo código! Passava a maior parte do tempo imprimindo o número maldito (mais ou menos 9 minutos e meio)!

module M = Map.Make
  (struct
    type t = int
    let compare = compare
   end)

let double b = Z.shift_left b 1
let ( +. ) b1 b2 = Z.add b1 b2
let ( *. ) b1 b2 = Z.mul b1 b2

let cache = ref M.empty 
let rec fib_log n =
  if n = 0
  then Z.zero
  else if n = 1
  then Z.one
  else if n mod 2 = 0
  then
    let f_n_half = fib_log_cached (n/2)
    and f_n_half_minus_one = fib_log_cached (n/2-1)
    in f_n_half *. (f_n_half +. double f_n_half_minus_one)
  else
    let f_n_half = fib_log_cached (n/2)
    and f_n_half_plus_one = fib_log_cached (n/2+1)
    in (f_n_half *. f_n_half) +.
    (f_n_half_plus_one *. f_n_half_plus_one)
and fib_log_cached n =
    try M.find n !cache
    with Not_found ->
      let res = fib_log n
      in cache := M.add n res !cache;
      res

let () =
  let res = fib_log 20_000_000 in
  Z.print res; print_newline ()

Não acredito na diferença que a biblioteca fez!

ReyCharles
fonte
11
Para comparação, a solução da @ boothby leva 0,875s para executar no meu laptop. Parece que a diferença é negligenciável. Além disso, aparentemente meu laptop é rápido : o
ReyCharles 12/12
1

Haskell

No meu sistema, isso é executado quase tão rápido quanto a resposta do FUZxxl (~ 18 segundos em vez de ~ 17 segundos).

main = print $ fst $ fib2 20000000

-- | fib2: Compute (fib n, fib (n+1)).
--
-- Having two adjacent Fibonacci numbers lets us
-- traverse up or down the series efficiently.
fib2 :: Int -> (Integer, Integer)

-- Guard against negative n.
fib2 n | n < 0 = error "fib2: negative index"

-- Start with a few base cases.
fib2 0 = (0, 1)
fib2 1 = (1, 1)
fib2 2 = (1, 2)
fib2 3 = (2, 3)

-- For larger numbers, derive fib2 n from fib2 (n `div` 2)
-- This takes advantage of the following identity:
--
--    fib(n) = fib(k)*fib(n-k-1) + fib(k+1)*fib(n-k)
--             where n > k
--               and k ≥ 0.
--
fib2 n =
    let (a, b) = fib2 (n `div` 2)
     in if even n
        then ((b-a)*a + a*b, a*a + b*b)
        else (a*a + b*b, a*b + b*(a+b))
Joey Adams
fonte
Agradável. Eu amo Haskell.
quer
Eu executei isso em ghci. Fiquei bastante impressionado. Haskell é ótimo para esses tipos de problemas de código matemático.
Undreren
1

C, algoritmo ingênuo

Era curioso, e eu não tinha usado o gmp antes ... então:

#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>

int main(int argc, char *argv[]){
    int n = (argc>1)?atoi(argv[1]):0;

    mpz_t temp,prev,result;
    mpz_init(temp);
    mpz_init_set_ui(prev, 0);
    mpz_init_set_ui(result, 1);

    for(int i = 2; i <= n; i++) {
        mpz_add(temp, result, prev);
        mpz_swap(temp, result);
        mpz_swap(temp, prev);
    }

    printf("fib(%d) = %s\n", n, mpz_get_str (NULL, 10, result));

    return 0;
}

fib (1 milhão) leva cerca de 7 segundos ... então esse algoritmo não vence a corrida.

coelho bebê
fonte
1

Eu implementei o método de multiplicação de matrizes (do sicp, http://sicp.org.ua/sicp/Exercise1-19 ) no SBCL, mas leva cerca de 30 segundos para concluir. Eu o portado para C usando GMP, e ele retorna o resultado correto em cerca de 1,36 segundos na minha máquina. É tão rápido quanto a resposta de Boothby.

#include <gmp.h>
#include <stdio.h>

int main()
{
  int n = 20000000;

  mpz_t a, b, p, q, psq, qsq, twopq, bq, aq, ap, bp;
  int count = n;

  mpz_init_set_si(a, 1);
  mpz_init_set_si(b, 0);
  mpz_init_set_si(p, 0);
  mpz_init_set_si(q, 1);
  mpz_init(psq);
  mpz_init(qsq);
  mpz_init(twopq);
  mpz_init(bq);
  mpz_init(aq);
  mpz_init(ap);
  mpz_init(bp);

  while(count > 0)
    {
      if ((count % 2) == 0)
        {
          mpz_mul(psq, p, p);
          mpz_mul(qsq, q, q);
          mpz_mul(twopq, p, q);
          mpz_mul_si(twopq, twopq, 2);

          mpz_add(p, psq, qsq);    // p -> (p * p) + (q * q)
          mpz_add(q, twopq, qsq);  // q -> (2 * p * q) + (q * q) 
          count/=2;
        }

      else
       {
          mpz_mul(bq, b, q);
          mpz_mul(aq, a, q);
          mpz_mul(ap, a, p);
          mpz_mul(bp, b, p);

          mpz_add(a, bq, aq);      // a -> (b * q) + (a * q)
          mpz_add(a, a, ap);       //              + (a * p)

          mpz_add(b, bp, aq);      // b -> (b * p) + (a * q)

          count--;
       }

    }

  gmp_printf("%Zd\n", b);
  return 0;
}
Erik Haliewicz
fonte
1

Java: 8 segundos para calcular, 18 segundos para escrever

public static BigInteger fibonacci1(int n) {
    if (n < 0) explode("non-negative please");
    short charPos = 32;
    boolean[] buf = new boolean[32];
    do {
        buf[--charPos] = (n & 1) == 1;
        n >>>= 1;
    } while (n != 0);
    BigInteger a = BigInteger.ZERO;
    BigInteger b = BigInteger.ONE;
    BigInteger temp;
    do {
        if (buf[charPos++]) {
            temp = b.multiply(b).add(a.multiply(a));
            b = b.multiply(a.shiftLeft(1).add(b));
            a = temp;
        } else {
            temp = b.multiply(b).add(a.multiply(a));
            a = a.multiply(b.shiftLeft(1).subtract(a));
            b = temp;
        }
    } while (charPos < 32);
    return a;
}

public static void main(String[] args) {
    BigInteger f;
    f = fibonacci1(20000000);
    // about 8 seconds
    System.out.println(f.toString());
    // about 18 seconds
}
averykhoo
fonte
0

Vai

É embaraçosamente lento. No meu computador, leva um pouco menos de 3 minutos. São apenas 120 chamadas recursivas (depois de adicionar o cache). Observe que isso pode usar muita memória (como 1,4 GiB)!

package main

import (
    "math/big"
    "fmt"
)

var cache = make(map[int64] *big.Int)

func fib_log_cache(n int64) *big.Int {
    if res, ok := cache[n]; ok {
        return res
    }
    res := fib_log(n)
    cache[n] = res
    return res
}

func fib_log(n int64) *big.Int {
    if n <= 1 {
        return big.NewInt(n)
    }

    if n % 2 == 0 {
        f_n_half := fib_log_cache(n/2)
        f_n_half_minus_one := fib_log_cache(n/2-1)
        res := new(big.Int).Lsh(f_n_half_minus_one, 1)
        res.Add(f_n_half, res)
        res.Mul(f_n_half, res)
        return res
    }
    f_n_half := fib_log_cache(n/2)
    f_n_half_plus_one := fib_log_cache(n/2+1)
    res := new(big.Int).Mul(f_n_half_plus_one, f_n_half_plus_one)
    tmp := new(big.Int).Mul(f_n_half, f_n_half)
    res.Add(res, tmp)
    return res
}

func main() {
    fmt.Println(fib_log(20000000))
}
ReyCharles
fonte
Tentei paralelizá-lo (antes de adicionar o cache) usando rotinas go e ele começou a usar 19 GiB de memória: /
ReyCharles 12/12/12 ReyCharles
-4

pseudo código (não sei o que vocês estão usando)

product = 1
multiplier = 3 // 3 is fibonacci sequence, but this can be any number, 
      // generating an infinite amount of sequences
y = 28 // the 2^x-1 term, so 2^28-1=1,284,455,535th term
for (int i = 1; int < y; i++) {
  product= sum*multiplier-1
  multiplier= multiplier^2-2
}
multiplier=multiplier-product // 2^28+1 1,284,455,537th 

Meu computador levou 56 horas para executar esses dois termos. Meu computador é meio ruim. Vou ter o número em um arquivo de texto em 22 de outubro. 1,2 GB é um pouco grande para ser compartilhado na minha conexão.

Thomas Olson
fonte
11
Estou confuso com sua resposta. Pseudo-código? E ainda assim você tem horários? Publique o código! Linguagem não importa!
Boothby
Isso, e a saída só é suposto ser 4 milhões ou mais dígitos ...
Wug