lib/prime.ex

defmodule Prime do
  @moduledoc """
  The main module you'll interact with.

  This module provides two functions, `generate/1` and `test/1`,
  where the former essentially depends on the latter. That is,
  `generate/1` generates a prime by means of yielding a random
  number using `Enum.random/1` and, if the number gained is proven
  by `test/1` to be a prime, it returns the prime, and if not
  it recursively invokes `generate/1` until `Enum.random/1` successfully
  returns a prime.

  [Prime number theorem](https://en.wikipedia.org/wiki/Prime_number_theorem)
  asserts that there are approximately `n/ln(n)` prime numbers between
  1 and n. Therefore we can assume the probability for the randomly chosen
  number between 1 and n to be the prime number is `1/ln(n)`.

  `ln(2^128)` approximately equals to 89. This means you'll only need
  less than 100 attempts to find a prime number between 1 and `2^128`.
  That's why it's just ok to make a recursion with no apparent base case.
  Note, however, that the probablistic nature again reside in the `test/1`,
  hence there is actually a little more than `1/ln(n)` steps to generate a prime.
  See the doc of `Prime.Fermat.test/1` for details.
  """

  alias Prime.MillerRabin

  @doc """
  Returns a random prime number of given bit size.
  """
  @spec generate(bit_size :: pos_integer()) :: pos_integer()
  def generate(bit_size) when is_number(bit_size) and 0 < bit_size do
    inf = 2
    sup = Bitwise.<<<(1, bit_size) - 1

    x = Enum.random(inf..sup)

    if test(x) do
      x
    else
      generate(bit_size)
    end
  end

  @doc """
  Tests if the given number is a prime.

  ## Examples

      iex> Prime.test(3)
      true

      iex> Prime.test(42)
      false

      iex> Prime.test(561)
      false

      iex> Prime.test(1_000_000_007)
      true

  """
  @spec test(pos_integer()) :: boolean()
  def test(n) do
    MillerRabin.test(n)
  end
end