Напишіть найшвидший Фібоначчі


10

Це ще одна проблема щодо чисел Фібоначчі.

Мета - обчислити 20'000'000 - е число Фібонацій як можна швидше. Десятковий вихід становить близько 4 МіБ; він починається з:

28543982899108793710435526490684533031144309848579

Сума результату MD5 становить

fa831ff5dd57a830792d8ded4c24c2cb

Ви повинні подати програму, яка обчислює кількість під час запуску та ставить результат stdout. Виграє найшвидша програма, виміряна на моїй власній машині.

Ось кілька додаткових правил:

  • Ви повинні надіслати вихідний код та двійковий код, який можна виконувати в Linux x64
  • Вихідний код повинен бути коротшим за 1 МіБ, у разі складання він також прийнятний, якщо лише двійковий файл малий.
  • Ви не повинні включати число, яке потрібно обчислити у вашому двійковому коді, навіть замасковано. Кількість має бути обчислена під час виконання.
  • Мій комп'ютер має два ядра; вам дозволяється використовувати паралелізм

Я взяв невелику реалізацію з Інтернету, яка працює приблизно за 4,5 секунди. Перемогти це не повинно бути дуже складно, якщо припустити, що у вас хороший алгоритм.


1
Чувак, що завгодно, як мудрець, що має невизначену поплавкову точність, запустить цю річ за меншу відстань 1/10 секунди. Це просто простий вираз, якphi = (1+sqrt(5))/2
JBernardo

4
Чи можемо ми вивести число в шістнадцятковій формі?
Кіт Рендалл

2
@Keith Nope. Це частина специфікації.
FUZxxl

3
Оскільки це слід виміряти на вашому процесорі, ми можемо також мати трохи більше інформації про нього, чи не так? Intel чи AMD? Розмір L1 та кеш-інструкцій? Розширення набору інструкцій?
JB

2
Як я обчислюю, ваш початковий рядок і MD5 призначені для 20'000'000-го числа, а не просто 2'000'000-го.
JB

Відповіді:


4

C з GMP, 3,6s

Боги, але GMP робить код некрасивим. Завдяки трюку в стилі Карацуби, мені вдалося скоротити до 2 множень за крок подвоєння. Тепер, коли я читаю рішення FUZxxl, я не вперше виникла ідея. У мене ще кілька хитрощів у рукаві ... можливо, я спробую їх пізніше.

#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");
}

Побудований с gcc -O3 m.c -o m -lgmp.


ЛОЛ. Крім ідентифікатора, який називає, це саме моє рішення :)
JB

@JB: ПЕРШИЙ! : D
кабінка

Тримайте це;) Наступний трюк в моєму рукаві виграє від Haskell більше, ніж від C.
JB

Перший трюк в моєму рукаві натрапив на помилку GHC. Драт. Мені доведеться повернутися до другого, який віддалено не так цікаво реалізовувати, тому це потребує часу та мотивації.
JB

3,6 сек на моїй машині.
FUZxxl

11

Мудрець

Гм, ти, здається, припускаєш, що найшвидше збирається складена програма. Жодної бінарної для вас!

print fibonacci(2000000)

На моїй машині це займає 0,10 cpu секунд, 0,15 стінних секунд.

редагувати: приурочено до консолі, а не ноутбука


1
Моя ідея полягала не в тому, щоб знати, наскільки швидко ваш CAS може це зробити, а наскільки швидко ви можете самі це кодувати.
FUZxxl

11
Для запису я просто поставив це як розумник; ви не казали не використовувати вбудовані.
кабінка

5

Хаскелл

Це моя власна спроба, хоча я алгоритм не писав сам. Я швидше скопіював його з haskell.org і адаптував його для використання Data.Vectorзі своїм відомим потоком злиття:

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

На компіляцію з GHC 7.0.3 та наступними прапорами потрібно приблизно 4,5 секунди:

ghc -O3 -fllvm fib.hs

Дивно ... Мені потрібно було змінити 20000000 на 40000000, щоб змусити його надрукувати очікуване число.
JB

Готча. Має бути enumFromStepN (s-1)замістьenumFromStepN s
JB

@JB Вибачте за всю цю плутанину. Я спочатку тестував програму з різними значеннями, щоб отримати досить велику кількість і зберігав вихід у різні файли. Але деякі, як я їх переплутав. Я оновив номер, щоб відповідати бажаному результату.
FUZxxl

@boothby Ні, я не змінив потрібний номер точок, а скоріше опорний вихід, який був неправильним.
FUZxxl

Бічна примітка: на моїй машині це приблизно 1,5 секунди, але ні LLVM не Data.Vector, здається, не приносить суттєвої переваги.
JB

4

КОРО

 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

Му! (Мине час. Випий трохи молока ...)


1
Примітка: Хоча це справді спрацьовує, воно, ймовірно, ніколи не досягне 20 000 000 ...
Timtech

2

Математика, інтерпретується:

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

Приурочено:

0.032 secs on my poor man's laptop.

І звичайно, ніякого бінарного.


Не друкується в stdout.
кабінка

@boothby Неправильно. Він пише на стандартний вихід, якщо ви використовуєте інтерфейс командного рядка. Дивіться, наприклад , stackoverflow.com/questions/6542537 / ...
Dr. Велизарий

Ні, я використовую інтерфейс командної лінії, версія 6.0. Навіть використовуючи -batchoutput, він друкує лише інформацію про час, а не число Фібоначчі.
кабінка

Вибачте, не можу відтворити, оскільки у мене немає математики.
FUZxxl

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... Він працює в постійний час щодо швидкості вашого інтернет-з'єднання. ;-)
ESultanik

2

Ocaml, 0,856s на моєму ноутбуці

Потрібна зарітська бібліотека. Я використовував Big_int, але це собака повільно порівняно із Зарітом. Минуло 10 хвилин з тим самим кодом! Більшість часу було витрачено на друкування проклятого номера (9½ хвилин або близько того)!

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 ()

Я не можу повірити, скільки різниць зробила бібліотека!


1
Для порівняння @ рішення кабінки займає 0,875 секунди на моєму ноутбуці. Здається, різниця незначна. Також, мабуть, мій ноутбук швидкий : o
ReyCharles

1

Хаскелл

У моїй системі це працює майже так само швидко, як відповідь FUZxxl (~ 18 секунд замість ~ 17 секунд).

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))

Приємно. Я люблю Хаскелл.
Арлен

Я керував цим у ghci. Я був дуже вражений. Haskell чудово підходить для таких типів задач з математичним кодом.
Undreren

1

C, наївний алгоритм

Було цікаво, і я раніше не використовував gmp ... так:

#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 мільйон) займає близько 7 секунд ... так що цей алгоритм не виграє гонку.


1

Я реалізував метод множення матриці (від sicp, http://sicp.org.ua/sicp/Exercise1-19 ) у SBCL, але для завершення потрібно 30 секунд. Я переніс його на C за допомогою GMP, і він повертає правильний результат приблизно на 1,36 секунди на моїй машині. Це приблизно так само швидко, як відповідь кабінки.

#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;
}

1

Java: 8 секунд для обчислення, 18 секунд для запису

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
}

0

Іди

Це бентежно повільно. На моєму комп’ютері це займає трохи менше 3 хвилин. Це лише 120 рекурсивних дзвінків (після додавання кешу). Зауважте, що для цього може бути багато пам'яті (наприклад, 1,4 ГБ)!

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))
}

Я спробував паралелізувати його (перед тим, як додати кеш-пам'ять), використовуючи підпрограми go, і він почав використовувати пам'ять 19 Гб: /
ReyCharles

-4

псевдо-код (я не знаю, чим ви користуєтесь)

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 

На ці два терміни мені знадобилося 56 годин. Мій комп'ютер начебто хитрий. Я отримаю номер у текстовому файлі 22 жовтня. 1,2 концерту - це трохи великий обмін для мого підключення.


1
Мене бентежить ваша відповідь. Псевдокод? І все ж у вас є таймінги? Опублікуйте код! Мова не має значення!
виставка

Це, а вихід повинен становити лише 4 мільйони або близько цифр ...
Wug
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.