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