4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 22

Elixirでフィボナッチ数列をいろいろ書いてみた Part. 4

Posted at

@mod_poppoさんのHaskellでフィボナッチ数列 〜Haskellで非実用的なコードを書いて悦に入るのはやめろ〜にインスピレーションを得て,Elixirでフィボナッチ数列をいろいろ書いてみるシリーズ記事の第4弾です.

フィボナッチ数列シリーズ

ビネ(Binet)の公式

F_n = \frac{1}{\sqrt{5}}\left( \left(\frac{1 + \sqrt{5}}{2} \right)^n - \left(\frac{1 - \sqrt{5}}{2} \right)^n\right)
iex> fn n -> 1.0 / :math.sqrt(5) * (:math.pow((1 + :math.sqrt(5)) / 2, n) - :math.pow((1 - :math.sqrt(5)) / 2, n)) end.(10)
55.000000000000014

とりあえず合っていそうです.

手始めに$\sqrt{5}$を多倍長整数とニュートン法を用いて求めてみたいと思います.

下記のアルゴリズムを採用します.

fn a ->
  Stream.unfold({2, 1}, fn
    {p, q} -> 
      d = {p * p + a * q * q, Bitwise.bsl(p * q, 1)}
      {d, d}
  end)
end

後で使いやすいように,次のように,漸化式として定義します.

fn {p, q} ->
  {p * p + a * q * q, Bitwise.bsl(p * q, 1)}
end

2進分解法を用いて $x^n$ を計算します.

fn x, n ->
  Stream.unfold(n, fn
      0 -> nil
      n -> {Bitwise.band(n, 1), Bitwise.bsr(n, 1)}
  end)
  |> Enum.reduce({1, x}, fn
    0, {r, x} -> {r, x * x}
    1, {r, x} -> {r * x, x * x}
  end)
  |> elem(0)
end

以上をビネ(Binet)の公式に当てはめます.

fib_benchee.exs
Mix.install([:benchee])

defmodule Fibonacci.Binet do
  def of(n) do
    Stream.unfold({2, 1}, fn {p, q} ->
      {
        {
          q * (power(q + p, n)- power(q - p, n)),
          p * power(Bitwise.bsl(q, 1), n)
        }, 
        {
          p * p + 5 * q * q, 
          Bitwise.bsl(p * q, 1)
        }
      }
    end)
    |> Stream.scan([], &[&1 | &2])
    |> Stream.drop(2)
    |> Stream.drop_while(fn [{p1, q1}, {p2, q2}, {p3, q3} | _] -> div(p1, q1) != div(p2, q2) and div(p1, q1) != div(p3, q3) end)
    |> Enum.at(0)
    |> hd()
    |> then(fn {p, q} -> div(p, q) end)
  end

  defp power(x, n) do
    Stream.unfold(n, fn
        0 -> nil
        n -> {Bitwise.band(n, 1), Bitwise.bsr(n, 1)}
    end)
    |> Enum.reduce({1, x}, fn
      0, {r, x} -> {r, x * x}
      1, {r, x} -> {r * x, x * x}
    end)
    |> elem(0)
  end
end

defmodule Fibonacci.Reduce do
  def of(n) do
    0..n
    |> Enum.reduce([], fn
      _, [] -> [0]
      _, [0] -> [1, 0]
      _, [m, n] -> [m + n, m]
    end)
    |> hd()
  end
end

IO.inspect(Fibonacci.Binet.of(1500) == Fibonacci.Reduce.of(1500))

Benchee.run(
  %{
    "Fibonacci by Enum.reduce" => fn input -> Enum.reduce(1..100, fn _, _ -> Fibonacci.Reduce.of(input) end) end,
    "Fibonacci with Binet method" => fn input -> Enum.reduce(1..100, fn _, _ -> Fibonacci.Binet.of(input) end) end
  },
  inputs: %{
    "1" => 1,
    "10" => 10,
    "100" => 100,
    "1000" => 1000
  }
)

実行結果

true
Operating System: macOS
CPU Information: Apple M3 Max
Number of Available Cores: 16
Available memory: 128 GB
Elixir 1.18.1
Erlang 27.2
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: 1, 10, 100, 1000
Estimated total run time: 56 s

Benchmarking Fibonacci by Enum.reduce with input 1 ...
Benchmarking Fibonacci by Enum.reduce with input 10 ...
Benchmarking Fibonacci by Enum.reduce with input 100 ...
Benchmarking Fibonacci by Enum.reduce with input 1000 ...
Benchmarking Fibonacci with Binet method with input 1 ...
Benchmarking Fibonacci with Binet method with input 10 ...
Benchmarking Fibonacci with Binet method with input 100 ...
Benchmarking Fibonacci with Binet method with input 1000 ...
Calculating statistics...
Formatting results...

##### With input 1 #####
Name                                  ips        average  deviation         median         99th %
Fibonacci by Enum.reduce         494.72 K        2.02 μs   ±585.40%        1.92 μs        2.67 μs
Fibonacci with Binet method       24.00 K       41.66 μs     ±6.22%       41.92 μs       48.29 μs

Comparison: 
Fibonacci by Enum.reduce         494.72 K
Fibonacci with Binet method       24.00 K - 20.61x slower +39.64 μs

##### With input 10 #####
Name                                  ips        average  deviation         median         99th %
Fibonacci by Enum.reduce         195.68 K        5.11 μs   ±136.15%        4.79 μs       13.08 μs
Fibonacci with Binet method        7.93 K      126.05 μs     ±4.44%      124.29 μs      150.17 μs

Comparison: 
Fibonacci by Enum.reduce         195.68 K
Fibonacci with Binet method        7.93 K - 24.67x slower +120.94 μs

##### With input 100 #####
Name                                  ips        average  deviation         median         99th %
Fibonacci by Enum.reduce          19.63 K      0.0509 ms     ±5.16%      0.0511 ms      0.0612 ms
Fibonacci with Binet method       0.110 K        9.06 ms     ±0.65%        9.05 ms        9.20 ms

Comparison: 
Fibonacci by Enum.reduce          19.63 K
Fibonacci with Binet method       0.110 K - 177.87x slower +9.01 ms

##### With input 1000 #####
Name                                  ips        average  deviation         median         99th %
Fibonacci by Enum.reduce           724.86      0.00138 s     ±3.40%      0.00139 s      0.00146 s
Fibonacci with Binet method         0.110         9.09 s     ±0.00%         9.09 s         9.09 s

Comparison: 
Fibonacci by Enum.reduce           724.86
Fibonacci with Binet method         0.110 - 6585.76x slower +9.08 s
1 10 100 1000
Fibonacci by Enum.reduce 2020 5110 50900 1380000
Fibonacci with Binet method 41660 126050 9060000 9090000000

Fibonacci by Enum.reduce の方が圧倒的に速いですね.

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?