Viết Fibonacci nhanh nhất


10

Đây là một thách thức khác về các số Fibonacci.

Mục tiêu là để tính toán số Fibonacii thứ 20.000 càng nhanh càng tốt. Sản lượng thập phân khoảng 4 MiB lớn; nó bắt đầu với:

2854398289910879371043552649068453303114430984857979

Tổng MD5 của đầu ra là

fa831ff5dd57a830792d8ded4c24c2cb

Bạn phải gửi một chương trình tính toán số lượng trong khi chạy và đặt kết quả stdout. Chương trình nhanh nhất, được đo trên máy của tôi, sẽ thắng.

Dưới đây là một số quy tắc bổ sung:

  • Bạn phải gửi mã nguồn và mã nhị phân có thể chạy trên Linux x64
  • Mã nguồn phải ngắn hơn 1 MiB, trong trường hợp lắp ráp, nó cũng được chấp nhận nếu chỉ có nhị phân là nhỏ.
  • Bạn không được bao gồm số được tính trong nhị phân của mình, ngay cả trong một kiểu ngụy trang. Số lượng phải được tính toán trong thời gian chạy.
  • Máy tính của tôi có hai lõi; bạn được phép sử dụng song song

Tôi đã thực hiện một triển khai nhỏ từ Internet chạy trong khoảng 4,5 giây. Không nên rất khó để đánh bại điều này, giả sử rằng bạn có một thuật toán tốt.


1
Anh bạn, bất cứ thứ gì như Sage có độ chính xác nổi không xác định sẽ chạy thứ đó trong 1/10 giây. Đó chỉ là một biểu hiện đơn giản nhưphi = (1+sqrt(5))/2
JBernardo

4
Chúng ta có thể xuất số trong hex không?
Keith Randall

2
@Keith Không. Đó là một phần của thông số kỹ thuật.
FUZxxl

3
Vì nó được đo trên CPU của bạn , chúng tôi cũng có thể có thêm một số thông tin về nó, phải không? Intel hay AMD? Kích thước của L1 và bộ đệm hướng dẫn? Hướng dẫn tập mở rộng?
JB

2
Khi tôi tính toán, chuỗi bắt đầu và MD5 của bạn dành cho số thứ 2000, chứ không phải chỉ là 2000.000.
JB

Câu trả lời:


4

C với GMP, 3.6s

Các vị thần, nhưng GMP làm cho mã xấu xí. Với thủ thuật kiểu Karatsuba, tôi đã cố gắng giảm xuống còn 2 lần cho mỗi bước nhân đôi. Bây giờ tôi đang đọc giải pháp của FUZxxl, tôi không phải là người đầu tiên có ý tưởng. Tôi đã có thêm một vài mánh khóe nữa ... có lẽ tôi sẽ thử chúng sau.

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

Được xây dựng với gcc -O3 m.c -o m -lgmp.


CƯỜI LỚN. Ngoài cách đặt tên định danh, đó chính xác là giải pháp của tôi :)
JB

@JB: ĐẦU TIÊN! : D
boothby

Giữ nó;) Thủ thuật tiếp theo trong tay áo của tôi sẽ được hưởng lợi từ Haskell nhiều hơn từ C.
JB

Đầu tiên lừa lên tay áo của tôi bị lỗi trong một lỗi GHC. Drat. Tôi sẽ phải quay lại cái thứ hai, điều này không thú vị khi thực hiện, vì vậy sẽ mất thời gian và động lực.
JB

3,6 giây trên máy của tôi.
FUZxxl

11

Hiền nhân

Hmm, bạn dường như cho rằng nhanh nhất sẽ là một chương trình được biên dịch. Không có nhị phân cho bạn!

print fibonacci(2000000)

Trên máy của tôi, phải mất 0,10 giây cpu, 0,15 giây giây.

chỉnh sửa: hẹn giờ trên bàn điều khiển, thay vì sổ ghi chép


1
Ý tưởng của tôi là không biết, CAS của bạn có thể làm điều này nhanh đến mức nào, mà là bạn có thể tự viết mã này nhanh đến mức nào.
FUZxxl

11
Đối với hồ sơ, tôi chỉ cần đưa nó lên thành một người thông minh; bạn đã không nói không sử dụng nội dung.
gian hàng

5

Haskell

Đây là cố gắng của riêng tôi, mặc dù tôi không tự mình viết thuật toán. Tôi thay vì sao chép nó từ haskell.org và điều chỉnh nó để sử dụng Data.Vectorvới phản ứng tổng hợp dòng nổi tiếng của nó:

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

Quá trình này mất khoảng 4,5 giây khi được biên dịch với GHC 7.0.3 và các cờ sau:

ghc -O3 -fllvm sợi.hs

Thật kỳ lạ ... Tôi cần thay đổi 20000000 thành 40000000 để có được nó để in số lượng dự kiến.
JB

Gotcha. Nên enumFromStepN (s-1)thay thếenumFromStepN s
JB

@JB Xin lỗi vì tất cả sự nhầm lẫn này. Ban đầu tôi đã thử nghiệm chương trình với các giá trị khác nhau để có được một số lượng lớn hợp lý và lưu kết quả đầu ra vào các tệp khác nhau. Nhưng một số cách tôi nhầm lẫn chúng. Tôi đã cập nhật số lượng để phù hợp với kết quả mong muốn.
FUZxxl

@boothby Không, tôi không thay đổi số lượng mong muốn của Wikipedia, mà là đầu ra tham chiếu, đã sai.
FUZxxl

Lưu ý bên lề: đó là khoảng 1,5 giây trên máy của tôi, nhưng cả LLVM và Data.Vector dường như không mang lại bất kỳ lợi thế đáng kể nào.
JB

4

COW

 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

Mày! (Mất một lúc. Uống một ít sữa ...)


1
Lưu ý: Mặc dù điều này thực sự hoạt động, nhưng nó có thể sẽ không bao giờ đạt tới 20.000.000 ...
TimTech

2

Toán học, giải thích:

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

Thời gian:

0.032 secs on my poor man's laptop.

Và tất nhiên, không có nhị phân.


Không in ra stdout.
gian hàng

@boothby Sai. Nó ghi vào đầu ra tiêu chuẩn nếu bạn sử dụng giao diện dòng lệnh. Xem ví dụ stackoverflow.com/questions/6542537/
Ấn

Không, tôi đang sử dụng giao diện dòng lệnh, phiên bản 6.0. Ngay cả khi sử dụng -batchoutput, nó chỉ in thông tin về thời gian chứ không phải số Fibonacci.
gian hàng

Xin lỗi, không thể sao chép vì tôi không có toán học.
FUZxxl

5
curl 'http://www.wolframalpha.com/input/?i=Fibonacci%5B2+10^6%5D' | grep 'Decimal approximation:' | sed ... Nó chạy trong thời gian liên tục liên quan đến tốc độ kết nối Internet của bạn. ;-)
ESultanik

2

Ocaml, 0.856s trên máy tính xách tay của tôi

Yêu cầu thư viện zarith. Tôi đã sử dụng Big_int nhưng nó là con chó chậm so với zarith. Phải mất 10 phút với cùng một mã! Hầu hết thời gian được dành để in số chết tiệt (9½ phút hoặc lâu hơn)!

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

Tôi không thể tin được thư viện đã tạo ra bao nhiêu sự khác biệt!


1
Để so sánh, giải pháp của @ boothby mất 0.875 giây để chạy trên máy tính xách tay của tôi. Có vẻ như sự khác biệt là không thể bỏ qua. Ngoài ra, rõ ràng máy tính xách tay của tôi rất nhanh : o
ReyCharles

1

Haskell

Trên hệ thống của tôi, điều này chạy gần như nhanh như câu trả lời của FUZxxl (~ 18 giây thay vì ~ 17 giây).

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

Đẹp. Tôi yêu Haskell.
Arlen

Tôi chạy cái này ở ghci. Tôi đã rất ấn tượng. Haskell là tuyệt vời cho các loại vấn đề mã toán học.
Hoàn trả

1

C, thuật toán ngây thơ

Đã tò mò và tôi đã không sử dụng gmp trước đây ... vì vậy:

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

sợi (1 triệu) mất khoảng 7 giây ... vì vậy thuật toán này sẽ không thắng trong cuộc đua.


1

Tôi đã triển khai phương pháp nhân ma trận (từ sicp, http://sicp.org.ua/sicp/Exercise1-19 ) trong SBCL nhưng phải mất khoảng 30 giây để hoàn thành. Tôi đã chuyển nó sang C bằng cách sử dụng GMP và nó trả về kết quả chính xác trong khoảng 1,36 giây trên máy của tôi. Nó nhanh như câu trả lời của 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;
}

1

Java: 8 giây để tính toán, 18 giây để viết

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

Đi

Thật là chậm chạp. Trên máy tính của tôi mất ít hơn 3 phút. Tuy nhiên, đó chỉ là 120 cuộc gọi đệ quy (sau khi thêm bộ đệm). Lưu ý rằng điều này có thể sử dụng rất nhiều bộ nhớ (như 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))
}

Tôi đã thử song song hóa nó (trước khi thêm bộ đệm) bằng cách sử dụng các thói quen đi và nó bắt đầu sử dụng 19 GiB bộ nhớ: /
ReyCharles

-4

mã giả (tôi không biết các bạn đang sử dụng cái gì)

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 

Máy tính của tôi mất 56 giờ để thực hiện hai điều khoản đó. Máy tính của tôi là loại crappy. Tôi sẽ có số trong một tệp văn bản vào ngày 22 tháng 10. Hợp đồng biểu diễn 1,2 là một chút lớn để chia sẻ trên kết nối của tôi.


1
Tôi bối rối trước câu trả lời của bạn. Mã giả? Vậy mà bạn có hẹn giờ? Gửi mã! Ngôn ngữ không thành vấn đề!
gian hàng

Điều đó, và đầu ra chỉ được coi là 4 triệu chữ số hoặc hơn ...
Wug
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.