lib/cldr/utils/math.ex

defmodule Cldr.Math do
  @moduledoc """
  Math helper functions for number formatting.
  """

  import Kernel, except: [div: 2]
  alias Cldr.Digits
  require Integer

  @type rounding ::
          :down
          | :half_up
          | :half_even
          | :ceiling
          | :floor
          | :half_down
          | :up

  @type number_or_decimal :: number | %Decimal{}
  @type normalised_decimal :: {%Decimal{}, integer}
  @default_rounding 3
  @default_rounding_mode :half_even
  @zero Decimal.new(0)
  @one Decimal.new(1)
  @two Decimal.new(2)
  @ten Decimal.new(10)

  @doc """
  Adds two numbers together.

  The numbers can be integers, floats or Decimals.
  The type of the return will be Decimal if the
  either of the arguments is a Decimal.

  If both arguments are integers, the result will
  be an integer. If either of the arguments is a float,
  the result will be a float.

  """
  @doc since: "2.25.0"
  def add(%Decimal{} = num_1, %Decimal{} = num_2) do
    Decimal.add(num_1, num_2)
  end

  def add(%Decimal{} = num_1, num_2) when is_integer(num_2) do
    Decimal.add(num_1, num_2)
  end

  def add(%Decimal{} = num_1, num_2) when is_float(num_2) do
    Decimal.add(num_1, Decimal.from_float(num_2))
  end

  def add(num_1, %Decimal{} = num_2) when is_integer(num_1) do
    Decimal.add(num_1, num_2)
  end

  def add(num_1, %Decimal{} = num_2) when is_float(num_1) do
    Decimal.add(Decimal.from_float(num_1), num_2)
  end

  def add(num_1, num_2) when is_number(num_1) and is_number(num_2) do
    num_1 + num_2
  end

  @doc """
  Subtracts one number from another.

  The numbers can be integers, floats or Decimals.
  The type of the return will be Decimal if the
  either of the arguments is a Decimal.

  If both arguments are integers, the result will
  be an integer. If either of the arguments is a float,
  the result will be a float.

  """
  @doc since: "2.25.0"
  def sub(%Decimal{} = num_1, %Decimal{} = num_2) do
    Decimal.sub(num_1, num_2)
  end

  def sub(%Decimal{} = num_1, num_2) when is_integer(num_2) do
    Decimal.sub(num_1, num_2)
  end

  def sub(%Decimal{} = num_1, num_2) when is_float(num_2) do
    Decimal.sub(num_1, Decimal.from_float(num_2))
  end

  def sub(num_1, %Decimal{} = num_2) when is_integer(num_1) do
    Decimal.sub(num_1, num_2)
  end

  def sub(num_1, %Decimal{} = num_2) when is_float(num_1) do
    Decimal.sub(Decimal.from_float(num_1), num_2)
  end

  def sub(num_1, num_2) when is_number(num_1) and is_number(num_2) do
    num_1 - num_2
  end

  @doc """
  Multiplies two numbers together.

  The numbers can be integers, floats or Decimals.
  The type of the return will be Decimal if the
  either of the arguments is a Decimal.

  If both arguments are integers, the result will
  be an integer. If either of the arguments is a float,
  the result will be a float.

  """
  @doc since: "2.25.0"
  def mult(%Decimal{} = num_1, %Decimal{} = num_2) do
    Decimal.mult(num_1, num_2)
  end

  def mult(%Decimal{} = num_1, num_2) when is_integer(num_2) do
    Decimal.mult(num_1, num_2)
  end

  def mult(%Decimal{} = num_1, num_2) when is_float(num_2) do
    Decimal.mult(num_1, Decimal.from_float(num_2))
  end

  def mult(num_1, %Decimal{} = num_2) when is_integer(num_1) do
    Decimal.mult(num_1, num_2)
  end

  def mult(num_1, %Decimal{} = num_2) when is_float(num_1) do
    Decimal.mult(Decimal.from_float(num_1), num_2)
  end

  def mult(num_1, num_2) when is_number(num_1) and is_number(num_2) do
    num_1 * num_2
  end

  @doc """
  Divides one number by the other.

  The numbers can be integers, floats or Decimals.
  The type of the return will be Decimal if the
  either of the arguments is a Decimal.

  If both arguments are numbers, the resulting type
  will be a be a Decimal.

  """
  @doc since: "2.25.0"
  def div(%Decimal{} = num_1, %Decimal{} = num_2) do
    Decimal.div(num_1, num_2)
  end

  def div(%Decimal{} = num_1, num_2) when is_integer(num_2) do
    Decimal.div(num_1, num_2)
  end

  def div(%Decimal{} = num_1, num_2) when is_float(num_2) do
    Decimal.div(num_1, Decimal.from_float(num_2))
  end

  def div(num_1, %Decimal{} = num_2) when is_integer(num_1) do
    Decimal.div(num_1, num_2)
  end

  def div(num_1, %Decimal{} = num_2) when is_float(num_1) do
    Decimal.div(Decimal.from_float(num_1), num_2)
  end

  def div(num_1, num_2) when is_number(num_1) and is_number(num_2) do
    Decimal.from_float(num_1 / num_2)
  end


  # Decimal.integer? only on 2.x but we support 1.x
  # so we have to check the hard way

  def maybe_integer(%Decimal{} = a) do
    Decimal.to_integer(a)
  rescue
    FunctionClauseError ->
      a
    ArgumentError ->
      a
  end

  def maybe_integer(a) when is_float(a) do
    case trunc(a) do
      b when a == b -> b
      _b -> a
    end
  end

  def maybe_integer(a) when is_integer(a) do
    a
  end

  @doc false
  @deprecated "Use Cldr.Decimal.compare/2"
  def decimal_compare(d1, d2) do
    Cldr.Decimal.compare(d1, d2)
  end

  @doc """
  Returns the default number of rounding digits.
  """
  @spec default_rounding :: integer
  def default_rounding do
    @default_rounding
  end

  @doc """
  Returns the default rounding mode for rounding operations.
  """
  @spec default_rounding_mode :: atom
  def default_rounding_mode do
    @default_rounding_mode
  end

  @doc """
  Check if a `number` is within a `range`.

  * `number` is either an integer or a float.

  When an integer, the comparison is made using the standard Elixir `in`
  operator.

  When `number` is a float the comparison is made using the `>=` and `<=`
  operators on the range endpoints. Note the comparison for a float is only for
  floats that have no fractional part. If a float has a fractional part then
  `within` returns `false`.

  *Since this function is only provided to support plural rules, the float
  comparison is only useful if the float has no fractional part.*

  ## Examples

      iex> Cldr.Math.within(2.0, 1..3)
      true

      iex> Cldr.Math.within(2.1, 1..3)
      false

  """
  @spec within(number, integer | Range.t()) :: boolean
  def within(number, range) when is_integer(number) do
    number in range
  end

  # When checking if a decimal is in a range it is only
  # valid if there are no decimal places
  def within(number, first..last) when is_float(number) do
    number == trunc(number) && number >= first && number <= last
  end

  @doc """
  Calculates the modulo of a number (integer, float or Decimal).

  Note that this function uses `floored division` whereas the builtin `rem`
  function uses `truncated division`. See `Decimal.rem/2` if you want a
  `truncated division` function for Decimals that will return the same value as
  the BIF `rem/2` but in Decimal form.

  See [Wikipedia](https://en.wikipedia.org/wiki/Modulo_operation) for an
  explanation of the difference.

  ## Examples

      iex> Cldr.Math.mod(1234.0, 5)
      4.0

      iex> Cldr.Math.mod(Decimal.new("1234.456"), 5)
      Decimal.new("4.456")

      iex> Cldr.Math.mod(Decimal.new("123.456"), Decimal.new("3.4"))
      Decimal.new("1.056")

      iex> Cldr.Math.mod Decimal.new("123.456"), 3.4
      Decimal.new("1.056")

  """
  @spec mod(number_or_decimal, number_or_decimal) :: number_or_decimal

  def mod(number, modulus) when is_float(number) and is_number(modulus) do
    number - Float.floor(number / modulus) * modulus
  end

  def mod(number, modulus) when is_integer(number) and is_integer(modulus) do
    modulo =
      number
      |> Integer.floor_div(modulus)
      |> Kernel.*(modulus)

    number - modulo
  end

  def mod(number, modulus) when is_integer(number) and is_number(modulus) do
    modulo =
      number
      |> Kernel./(modulus)
      |> Float.floor()
      |> Kernel.*(modulus)

    number - modulo
  end

  def mod(%Decimal{} = number, %Decimal{} = modulus) do
    modulo =
      number
      |> Decimal.div(modulus)
      |> Decimal.round(0, :floor)
      |> Decimal.mult(modulus)

    Decimal.sub(number, modulo)
  end

  def mod(%Decimal{} = number, modulus) when is_integer(modulus) do
    mod(number, Decimal.new(modulus))
  end

  def mod(%Decimal{} = number, modulus) when is_float(modulus) do
    mod(number, Decimal.from_float(modulus))
  end

  @doc """
  Returns the adjusted modulus of `x` and `y`.
  """
  @spec amod(number_or_decimal, number_or_decimal) :: number_or_decimal
  def amod(x, y) do
    case mod = mod(x, y) do
      %Decimal{} = decimal_mod ->
        if Cldr.Decimal.compare(decimal_mod, @zero) == :eq, do: y, else: mod

      _ ->
        if mod == 0, do: y, else: mod
    end
  end

  @doc """
  Returns the remainder and dividend of two integers.
  """
  @spec div_mod(integer, integer) :: {integer, integer}
  def div_mod(int1, int2) when is_integer(int1) and is_integer(int2) do
    div = Kernel.div(int1, int2)
    mod = int1 - div * int2
    {div, mod}
  end

  @doc """
  Returns the adjusted remainder and dividend of two
  integers.

  This version will return the divisor if the remainder
  would otherwise be zero.

  """
  @spec div_amod(integer, integer) :: {integer, integer}
  def div_amod(int1, int2) when is_integer(int1) and is_integer(int2) do
    {div, mod} = div_mod(int1, int2)

    if mod == 0 do
      {div - 1, int2}
    else
      {div, mod}
    end
  end

  @doc """
  Convert a Decimal to a float

  * `decimal` must be a Decimal

  This is very likely to lose precision - lots of numbers won't
  make the round trip conversion.  Use with care.  Actually, better
  not to use it at all.

  """
  @spec to_float(%Decimal{}) :: float
  def to_float(%Decimal{sign: sign, coef: coef, exp: exp}) do
    sign * coef * 1.0 * power_of_10(exp)
  end

  @doc """
  Rounds a number to a specified number of significant digits.

  This is not the same as rounding fractional digits which is performed
  by `Decimal.round/2` and `Float.round`

  * `number` is a float, integer or Decimal

  * `n` is the number of significant digits to which the `number` should be
    rounded

  ## Examples

      iex> Cldr.Math.round_significant(3.14159, 3)
      3.14

      iex> Cldr.Math.round_significant(10.3554, 1)
      10.0

      iex> Cldr.Math.round_significant(0.00035, 1)
      0.0004

      iex> Cldr.Math.round_significant(Decimal.from_float(3.342742283480345e27), 7)
      Decimal.new("3.342742E+27")

  ## Notes about precision

  Since floats cannot accurately represent all decimal
  numbers, so rounding to significant digits for a float cannot
  always return the expected results. For example:

      => Cldr.Math.round_significant(3.342742283480345e27, 7)
      Expected result:  3.342742e27
      Actual result: 3.3427420000000003e27

  Use of `Decimal` numbers avoids this issue:

      => Cldr.Math.round_significant(Decimal.from_float(3.342742283480345e27), 7)
      Expected result:  #Decimal<3.342742E+27>
      Actual result: #Decimal<3.342742E+27>

  ## More on significant digits

  * 3.14159 has six significant digits (all the numbers give you useful
    information)

  * 1000 has one significant digit (only the 1 is interesting; you don't know
    anything for sure about the hundreds, tens, or units places; the zeroes may
    just be placeholders; they may have rounded something off to get this value)

  * 1000.0 has five significant digits (the ".0" tells us something interesting
    about the presumed accuracy of the measurement being made: that the
    measurement is accurate to the tenths place, but that there happen to be
    zero tenths)

  * 0.00035 has two significant digits (only the 3 and 5 tell us something; the
    other zeroes are placeholders, only providing information about relative
    size)

  * 0.000350 has three significant digits (that last zero tells us that the
    measurement was made accurate to that last digit, which just happened to
    have a value of zero)

  * 1006 has four significant digits (the 1 and 6 are interesting, and we have
    to count the zeroes, because they're between the two interesting numbers)

  * 560 has two significant digits (the last zero is just a placeholder)

  * 560.0 has four significant digits (the zero in the tenths place means that
    the measurement was made accurate to the tenths place, and that there just
    happen to be zero tenths; the 5 and 6 give useful information, and the
    other zero is between significant digits, and must therefore also be
    counted)

  Many thanks to [Stackoverflow](http://stackoverflow.com/questions/202302/rounding-to-an-arbitrary-number-of-significant-digits)

  """
  @spec round_significant(number_or_decimal, integer) :: number_or_decimal
  def round_significant(number, n) when is_number(number) and n <= 0 do
    number
  end

  def round_significant(number, n) when is_number(number) and n > 0 do
    sign = if number < 0, do: -1, else: 1
    number = abs(number)
    d = Float.ceil(:math.log10(number))
    power = n - d

    magnitude = :math.pow(10,power)
    rounded = Float.round(number * magnitude) / magnitude

    sign *
      if is_integer(number) do
        trunc(rounded)
      else
        rounded
      end
  end

  if Code.ensure_loaded?(Decimal) and function_exported?(Decimal, :negate, 1) do
    def round_significant(%Decimal{sign: sign} = number, n) when sign < 0 and n > 0 do
      round_significant(Decimal.abs(number), n)
      |> Decimal.negate()
    end
  else
    def round_significant(%Decimal{sign: sign} = number, n) when sign < 0 and n > 0 do
      round_significant(Decimal.abs(number), n)
      |> Decimal.minus()
    end
  end

  def round_significant(%Decimal{sign: sign} = number, n) when sign > 0 and n > 0 do
    d =
      number
      |> log10
      |> Decimal.round(0, :ceiling)

    power =
      n
      |> Decimal.new()
      |> Decimal.sub(d)
      |> Decimal.to_integer

    magnitude = power(@ten, power)

    number
    |> Decimal.mult(magnitude)
    |> Decimal.round(0)
    |> Decimal.div(magnitude)
  end

  @doc """
  Return the natural log of a number.

  * `number` is an integer, a float or a Decimal

  * For integer and float it calls the BIF `:math.log10/1` function.

  * For Decimal the log is rolled by hand.

  ## Examples

      iex> Cldr.Math.log(123)
      4.812184355372417

      iex> Cldr.Math.log(Decimal.new(9000))
      Decimal.new("9.103886231350952380952380952")

  """
  def log(number) when is_number(number) do
    :math.log(number)
  end

  @ln10 Decimal.from_float(2.30258509299)
  def log(%Decimal{} = number) do
    {mantissa, exp} = coef_exponent(number)
    exp = Decimal.new(exp)
    ln1 = Decimal.mult(exp, @ln10)

    sqrt_mantissa = sqrt(mantissa)
    y = Decimal.div(Decimal.sub(sqrt_mantissa, @one), Decimal.add(sqrt_mantissa, @one))

    ln2 =
      y
      |> log_polynomial([3, 5, 7])
      |> Decimal.add(y)
      |> Decimal.mult(@two)

    Decimal.add(Decimal.mult(@two, ln2), ln1)
  end

  defp log_polynomial(%Decimal{} = value, iterations) do
    Enum.reduce(iterations, @zero, fn i, acc ->
      i = Decimal.new(i)

      value
      |> power(i)
      |> Decimal.div(i)
      |> Decimal.add(acc)
    end)
  end

  @doc """
  Return the log10 of a number.

  * `number` is an integer, a float or a Decimal

    * For integer and float it calls the BIF `:math.log10/1` function.

    * For `Decimal`, `log10` is is rolled by hand using the identify `log10(x) =
    ln(x) / ln(10)`

  ## Examples

      iex> Cldr.Math.log10(100)
      2.0

      iex> Cldr.Math.log10(123)
      2.089905111439398

      iex> Cldr.Math.log10(Decimal.new(9000))
      Decimal.new("3.953767554157656512064441441")

  """
  @spec log10(number_or_decimal) :: number_or_decimal
  def log10(number) when is_number(number) do
    :math.log10(number)
  end

  def log10(%Decimal{} = number) do
    Decimal.div(log(number), @ln10)
  end

  @doc """
  Raises a number to a integer power.

  Raises a number to a power using the the binary method. There is one
  exception for Decimal numbers that raise `10` to some power. In this case the
  power is calculated by shifting the Decimal exponent which is quite efficient.

  For further reading see
  [this article](http://videlalvaro.github.io/2014/03/the-power-algorithm.html)

  > This function works only with integer exponents!

  ## Examples

      iex> Cldr.Math.power(10, 2)
      100

      iex> Cldr.Math.power(10, 3)
      1000

      iex> Cldr.Math.power(10, 4)
      10000

      iex> Cldr.Math.power(2, 10)
      1024

  """

  # Decimal number and decimal n
  @spec power(number_or_decimal, number_or_decimal) :: number_or_decimal
  def power(%Decimal{} = _number, %Decimal{coef: n}) when n == 0 do
    @one
  end

  def power(%Decimal{} = number, %Decimal{sign: sign} = n) when sign < 1 do
    Decimal.div(@one, do_power(number, Decimal.abs(n), mod(Decimal.abs(n), @two)))
  end

  def power(%Decimal{} = number, %Decimal{coef: n}) when n == 1 do
    number
  end

  def power(%Decimal{} = number, %Decimal{} = n) do
    do_power(number, n, mod(n, @two))
  end

  # Decimal number and integer/float n
  def power(%Decimal{} = _number, n) when n == 0 do
    @one
  end

  def power(%Decimal{} = number, n) when n == 1 do
    number
  end

  def power(%Decimal{} = number, n) when n > 1 do
    do_power(number, n, mod(n, 2))
  end

  def power(%Decimal{} = number, n) when n < 0 do
    Decimal.div(@one, do_power(number, abs(n), mod(abs(n), 2)))
  end

  # n is between 0 and 1
  def power(%Decimal{} = number, n) do
    do_power(number, n, mod(number, n))
  end

  # For integers and floats
  def power(number, n) when n == 0 do
    if is_integer(number), do: 1, else: 1.0
  end

  def power(number, n) when n == 1 do
    number
  end

  def power(number, n) when n > 1 do
    do_power(number, n, mod(n, 2))
  end

  def power(number, n) when n < 1 do
    1 / do_power(number, abs(n), mod(abs(n), 2))
  end

  # Decimal number and decimal n
  defp do_power(%Decimal{} = number, %Decimal{coef: coef}, %Decimal{coef: mod})
       when mod == 0 and coef == 2 do
    Decimal.mult(number, number)
  end

  defp do_power(%Decimal{} = number, %Decimal{coef: coef} = n, %Decimal{coef: mod})
       when mod == 0 and coef != 2 do
    power(power(number, Decimal.div(n, @two)), @two)
  end

  defp do_power(%Decimal{} = number, %Decimal{} = n, _mod) do
    Decimal.mult(number, power(number, Decimal.sub(n, @one)))
  end

  # Decimal number but integer n
  defp do_power(%Decimal{} = number, 1, 1) do
    number
  end

  defp do_power(%Decimal{} = number, n, mod)
       when is_number(n) and mod == 0 and n == 2 do
    Decimal.mult(number, number)
  end

  defp do_power(%Decimal{} = number, n, mod)
       when is_number(n) and mod == 0 and n != 2 do
    power(power(number, n / 2), 2)
  end

  defp do_power(%Decimal{} = number, n, _mod)
       when is_number(n)  and n > 1 do
    Decimal.mult(number, power(number, n - 1))
  end

  # Escape hatch for when the exponent < 1
  defp do_power(%Decimal{} = number, n, _mod) when n < 1 do
    number
    |> Decimal.to_float()
    |> :math.pow(n)
  end

  # integer/float number and integer/float n
  defp do_power(number, n, mod)
       when is_number(n) and mod == 0 and n == 2 do
    number * number
  end

  defp do_power(number, n, mod)
       when is_number(n) and mod == 0 and n != 2 do
    power(power(number, n / 2), 2)
  end

  defp do_power(number, n, _mod) when is_number(number) and is_number(n) do
    if Kernel.round(n) != n do
      :math.pow(number, n)
    else
      number * power(number, n - 1)
    end
  end

  # Precompute powers of 10 up to 10^326
  # FIXME: duplicating existing function in Float, which only goes up to 15.
  @doc false
  Enum.reduce(0..326, 1, fn x, acc ->
    def power_of_10(unquote(x)), do: unquote(acc)
    acc * 10
  end)

  def power_of_10(n) when n < 0 do
    1 / power_of_10(abs(n))
  end

  @doc """
  Raises one number to an exponent.

  """
  defdelegate pow(n, m), to: __MODULE__, as: :power

  @doc """
  Returns a tuple representing a number in a normalized form with
  the mantissa in the range `0 < m < 10` and a base 10 exponent.

  * `number` is an integer, float or Decimal

  ## Examples

      Cldr.Math.coef_exponent(Decimal.new(1.23004))
      {Decimal.new("1.23004"), 0}

      Cldr.Math.coef_exponent(Decimal.new(465))
      {Decimal.new("4.65"), 2}

      Cldr.Math.coef_exponent(Decimal.new(-46.543))
      {Decimal.new("-4.6543"), 1}

  """

  # An integer should be returned as a float mantissa
  @spec coef_exponent(number_or_decimal) :: {number_or_decimal, integer}
  def coef_exponent(number) when is_integer(number) do
    {mantissa_digits, exponent} = coef_exponent_digits(number)
    {Digits.to_float(mantissa_digits), exponent}
  end

  # All other numbers are returned as the same type as the parameter
  def coef_exponent(number) do
    {mantissa_digits, exponent} = coef_exponent_digits(number)
    {Digits.to_number(mantissa_digits, number), exponent}
  end

  @doc """
  Returns a tuple representing a number in a normalized form with
  the mantissa in the range `0 < m < 10` and a base 10 exponent.

  The mantissa is represented as tuple of the form `Digits.t`.

  * `number` is an integer, float or Decimal

  ## Examples

      Cldr.Math.coef_exponent_digits(Decimal.new(1.23004))
      {{[1, 2, 3, 0], 1, 1}, 0}

      Cldr.Math.coef_exponent_digits(Decimal.new(465))
      {{[4, 6, 5], 1, 1}, -1}

      Cldr.Math.coef_exponent_digits(Decimal.new(-46.543))
      {{[4, 6, 5, 4], 1, -1}, 1}

  """
  @spec coef_exponent_digits(number_or_decimal) :: {Digits.t(), integer()}
  def coef_exponent_digits(number) do
    {digits, place, sign} = Digits.to_digits(number)
    {{digits, 1, sign}, place - 1}
  end

  @doc """
  Calculates the square root of a Decimal number using Newton's method.

  * `number` is an integer, float or Decimal.  For integer and float,
  `sqrt` is delegated to the erlang `:math` module.

  We convert the Decimal to a float and take its
  `:math.sqrt` only to get an initial estimate.
  The means typically we are only two iterations from
  a solution so the slight hack improves performance
  without sacrificing precision.

  ## Examples

      iex> Cldr.Math.sqrt(Decimal.new(9))
      Decimal.new("3.0")

      iex> Cldr.Math.sqrt(Decimal.new("9.869"))
      Decimal.new("3.141496458696078173887197038")

  """
  @precision 0.0001
  @decimal_precision Decimal.from_float(@precision)
  def sqrt(number, precision \\ @precision)

  def sqrt(%Decimal{sign: sign} = number, _precision)
      when sign == -1 do
    raise ArgumentError, "bad argument in arithmetic expression #{inspect(number)}"
  end

  # Get an initial estimate of the sqrt by using the built in `:math.sqrt`
  # function.  This means typically its only two iterations to get the default
  # the sqrt at the specified precision.
  def sqrt(%Decimal{} = number, precision)
      when is_number(precision) do
    initial_estimate =
      number
      |> to_float
      |> :math.sqrt()
      |> Decimal.from_float()

    decimal_precision =
      if is_integer(precision) do
        Decimal.new(precision)
      else
        Decimal.from_float(precision)
      end

    do_sqrt(number, initial_estimate, @decimal_precision, decimal_precision)
  end

  def sqrt(number, _precision) do
    :math.sqrt(number)
  end

  defp do_sqrt(
         %Decimal{} = number,
         %Decimal{} = estimate,
         %Decimal{} = old_estimate,
         %Decimal{} = precision
       ) do
    diff =
      estimate
      |> Decimal.sub(old_estimate)
      |> Decimal.abs()

    if Cldr.Decimal.compare(diff, old_estimate) == :lt || Cldr.Decimal.compare(diff, old_estimate) == :eq do
      estimate
    else
      Decimal.div(number, Decimal.mult(@two, estimate))

      new_estimate =
        Decimal.add(
          Decimal.div(estimate, @two),
          Decimal.div(number, Decimal.mult(@two, estimate))
        )

      do_sqrt(number, new_estimate, estimate, precision)
    end
  end

  @doc """
  Calculate the nth root of a number.

  * `number` is an integer or a Decimal

  * `nth` is a positive integer

  ## Examples

      iex> Cldr.Math.root Decimal.new(8), 3
      Decimal.new("2.0")

      iex> Cldr.Math.root Decimal.new(16), 4
      Decimal.new("2.0")

      iex> Cldr.Math.root Decimal.new(27), 3
      Decimal.new("3.0")

  """
  def root(%Decimal{} = number, nth) when is_integer(nth) and nth > 0 do
    guess =
      number
      |> to_float()
      |> :math.pow(1 / nth)
      |> Decimal.from_float()

    do_root(number, Decimal.new(nth), guess)
  end

  def root(number, nth) when is_number(number) and is_integer(nth) and nth > 0 do
    guess = :math.pow(number, 1 / nth)
    do_root(number, nth, guess)
  end

  @root_precision 0.0001
  defp do_root(number, nth, root) when is_number(number) do
    delta = 1 / nth * (number / :math.pow(root, nth - 1)) - root

    if delta > @root_precision do
      do_root(number, nth, root + delta)
    else
      root
    end
  end

  @decimal_root_precision Decimal.from_float(@root_precision)
  defp do_root(%Decimal{} = number, %Decimal{} = nth, %Decimal{} = root) do
    d1 = Decimal.div(@one, nth)
    d2 = Decimal.div(number, power(root, Decimal.sub(nth, @one)))
    d3 = Decimal.sub(d2, root)
    delta = Decimal.mult(d1, d3)

    if Cldr.Decimal.compare(delta, @decimal_root_precision) == :gt do
      do_root(number, nth, Decimal.add(root, delta))
    else
      root
    end
  end

  @rounding_modes [:down, :up, :ceiling, :floor, :half_even, :half_up, :half_down]
  @doc false
  def rounding_modes do
    @rounding_modes
  end

  # Originally adapted from https://github.com/ewildgoose/elixir-float_pp
  # Thanks for making this like @ewildgoose

  @doc """
  Round a number to an arbitrary precision using one of several rounding algorithms.

  Rounding algorithms are based on the definitions given in IEEE 754, but also
  include 2 additional options (effectively the complementary versions):

  ## Arguments

  * `number` is a `float`, `integer` or `Decimal`

  * `places` is an integer number of places to round to

  * `mode` is the rounding mode to be applied.  The
    default is `:half_even`

  ## Rounding algorithms

  Directed roundings:

  * `:down` - Round towards 0 (truncate), eg 10.9 rounds to 10.0

  * `:up` - Round away from 0, eg 10.1 rounds to 11.0. (Non IEEE algorithm)

  * `:ceiling` - Round toward +∞ - Also known as rounding up or ceiling

  * `:floor` - Round toward -∞ - Also known as rounding down or floor

  Round to nearest:

  * `:half_even` - Round to nearest value, but in a tiebreak, round towards the
    nearest value with an even (zero) least significant bit, which occurs 50%
    of the time. This is the default for IEEE binary floating-point and the recommended
    value for decimal.

  * `:half_up` - Round to nearest value, but in a tiebreak, round away from 0.
    This is the default algorithm for Erlang's Kernel.round/2

  * `:half_down` - Round to nearest value, but in a tiebreak, round towards 0
    (Non IEEE algorithm)

  ## Notes

  * When the `number` is a `Decimal`, the results are identical
    to `Decimal.round/3` (delegates to `Decimal` in these cases)

  * When the `number` is a `float`, `places` is `0` and `mode`
    is `:half_up` then the result is the same as `Kernel.trunc/1`

  * The results of rounding for `floats` may not return the same
    result as `Float.round/2`. `Float.round/2` operates on the
    binary representation. This implementation operates on
    a decimal representation.

  """
  def round(number, places \\ 0, mode \\ :half_even)

  def round(%Decimal{} = number, places, mode) do
    Decimal.round(number, places, mode)
  end

  def round(number, places, mode) when is_integer(number) do
    number
    |> Decimal.new()
    |> Decimal.round(places, mode)
    |> Decimal.to_integer()
  end

  def round(number, places, mode) when is_float(number) do
    number
    |> Digits.to_digits()
    |> round_digits(%{decimals: places, rounding: mode})
    |> Digits.to_number(number)
  end

  @doc false
  def round_scientific(number, places, mode) when is_float(number) do
    number
    |> Digits.to_digits()
    |> round_digits(%{scientific: places, rounding: mode})
    |> Digits.to_number(number)
  end

  # The next function heads operate on decomposed numbers returned
  # by Digits.to_digits.

  # scientific/decimal rounding are the same, we are just varying which
  # digit we start counting from to find our rounding point
  defp round_digits(digits_t, options)

  # Passing true for decimal places avoids rounding and uses whatever is necessary
  defp round_digits(digits_t, %{scientific: true}), do: digits_t
  defp round_digits(digits_t, %{decimals: true}), do: digits_t

  # rounded away all the decimals... return 0
  # NOTE THESE IMPLY THAT ANY NUMBER LESS THAN ZERO THAT SHOULD ROUND TO 1
  # WILL RETURN 0 which is not what we want!

  # defp round_digits(_, %{scientific: dp}) when dp <= 0, do: {[0], 1, 1}
  # defp round_digits({_, place, _}, %{decimals: dp}) when dp + place <= 0, do: {[0], 1, 1}

  defp round_digits({_, place, _}, %{decimals: dp}) when dp + place <= 0 and place < 0 do
    {[0], 1, 1}
  end

  defp round_digits({_, place, _} = digits_t, %{decimals: dp} = options) when dp + place <= 0 do
    # IO.inspect dp + place, label: "Round at"
    {digits, place, sign} = do_round(digits_t, dp, options)
    {List.flatten(digits), place, sign}
  end

  defp round_digits(digits_t = {_, place, _}, options = %{decimals: dp}) do
    {digits, place, sign} = do_round(digits_t, dp + place - 1, options)
    {List.flatten(digits), place, sign}
  end

  defp round_digits(digits_t, options = %{scientific: dp}) do
    {digits, place, sign} = do_round(digits_t, dp, options)
    {List.flatten(digits), place, sign}
  end

  defp do_round({digits, place, sign}, round_at, %{rounding: rounding}) do
    case Enum.split(digits, round_at) do
      {l, [least_sig | [tie | rest]]} ->
        # IO.inspect {l, [least_sig | [tie | rest]]}, label: "Case 1"
        case do_incr(l, least_sig, increment?(sign == 1, least_sig, tie, rest, rounding)) do
          [:rollover | digits] -> {digits, place + 1, sign}
          digits -> {digits, place, sign}
        end

      {[] = l, [least_sig | []]} ->
        # IO.inspect  {l, [least_sig | []]}, label: "Case 2"
        case do_incr(l, least_sig, increment?(sign == 1, least_sig, 0, [], rounding)) do
          [:rollover | digits] -> {digits, place + 1, sign}
          digits -> {digits, place, sign}
        end

      {l, [least_sig | []]} ->
        # IO.inspect  {l, [least_sig | []]}, label: "Case 4"
        {[l, least_sig], place, sign}

      {l, []} ->
        # IO.inspect {l, []}, label: "Case 3"
        {l, place, sign}
    end
  end

  # Helper functions for round/2-3
  defp do_incr(l, least_sig, false), do: [l, least_sig]
  defp do_incr(l, least_sig, true) when least_sig < 9, do: [l, least_sig + 1]
  # else need to cascade the increment
  defp do_incr(l, 9, true) do
    l
    |> Enum.reverse()
    |> cascade_incr
    |> Enum.reverse([0])
  end

  # cascade an increment of decimal digits which could be rolling over 9 -> 0
  defp cascade_incr([9 | rest]), do: [0 | cascade_incr(rest)]
  defp cascade_incr([d | rest]), do: [d + 1 | rest]
  defp cascade_incr([]), do: [1, :rollover]

  @spec increment?(boolean, non_neg_integer | nil, non_neg_integer | nil, list(), atom()) ::
          boolean
  defp increment?(positive, least_sig, tie, rest, round)

  # Directed rounding towards 0 (truncate)
  defp increment?(_, _ls, _tie, _, :down), do: false
  # Directed rounding away from 0 (non IEEE option)
  defp increment?(_, _ls, nil, _, :up), do: false
  defp increment?(_, _ls, _tie, _, :up), do: true

  # Directed rounding towards +∞ (rounding up / ceiling)
  defp increment?(true, _ls, tie, _, :ceiling) when tie != nil, do: true
  defp increment?(_, _ls, _tie, _, :ceiling), do: false

  # Directed rounding towards -∞ (rounding down / floor)
  defp increment?(false, _ls, tie, _, :floor) when tie != nil, do: true
  defp increment?(_, _ls, _tie, _, :floor), do: false

  # Round to nearest - tiebreaks by rounding to even
  # Default IEEE rounding, recommended default for decimal
  defp increment?(_, ls, 5, [], :half_even) when Integer.is_even(ls), do: false
  defp increment?(_, _ls, tie, _rest, :half_even) when tie >= 5, do: true
  defp increment?(_, _ls, _tie, _rest, :half_even), do: false

  # Round to nearest - tiebreaks by rounding away from zero (same as Elixir Kernel.round)
  defp increment?(_, _ls, tie, _rest, :half_up) when tie >= 5, do: true
  defp increment?(_, _ls, _tie, _rest, :half_up), do: false

  # Round to nearest - tiebreaks by rounding towards zero (non IEEE option)
  defp increment?(_, _ls, 5, [], :half_down), do: false
  defp increment?(_, _ls, tie, _rest, :half_down) when tie >= 5, do: true
  defp increment?(_, _ls, _tie, _rest, :half_down), do: false
end