1975 lines
58 KiB
Elixir
1975 lines
58 KiB
Elixir
defmodule Decimal.Macros do
|
||
@moduledoc false
|
||
|
||
defmacro doc_since(version) do
|
||
if Version.match?(System.version(), ">= 1.7.0") do
|
||
quote do
|
||
@doc since: unquote(version)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
defmodule Decimal do
|
||
@moduledoc """
|
||
Decimal arithmetic on arbitrary precision floating-point numbers.
|
||
|
||
A number is represented by a signed coefficient and exponent such that: `sign
|
||
* coefficient * 10 ^ exponent`. All numbers are represented and calculated
|
||
exactly, but the result of an operation may be rounded depending on the
|
||
context the operation is performed with, see: `Decimal.Context`. Trailing
|
||
zeros in the coefficient are never truncated to preserve the number of
|
||
significant digits unless explicitly done so.
|
||
|
||
There are also special values such as NaN (not a number) and ±Infinity.
|
||
-0 and +0 are two distinct values.
|
||
Some operation results are not defined and will return NaN.
|
||
This kind of NaN is quiet, any operation returning a number will return
|
||
NaN when given a quiet NaN (the NaN value will flow through all operations).
|
||
The other kind of NaN is signalling which is the value that can be reached
|
||
in `result` field on `Decimal.Error` when the result is NaN. Any operation
|
||
given a signalling NaN return will signal `:invalid_operation`.
|
||
|
||
Exceptional conditions are grouped into signals, each signal has a flag and a
|
||
trap enabler in the context. Whenever a signal is triggered it's flag is set
|
||
in the context and will be set until explicitly cleared. If the signal is trap
|
||
enabled `Decimal.Error` will be raised.
|
||
|
||
## Specifications
|
||
|
||
* [IBM's General Decimal Arithmetic Specification](http://speleotrove.com/decimal/decarith.html)
|
||
* [IEEE standard 854-1987](http://754r.ucbtest.org/standards/854.pdf)
|
||
|
||
This implementation follows the above standards as closely as possible. But at
|
||
some places the implementation diverges from the specification. The reasons
|
||
are different for each case but may be that the specification doesn't map to
|
||
this environment, ease of implementation or that API will be nicer. Still, the
|
||
implementation is close enough that the specifications can be seen as
|
||
additional documentation that can be used when things are unclear.
|
||
|
||
The specification models the sign of the number as 1, for a negative number,
|
||
and 0 for a positive number. Internally this implementation models the sign as
|
||
1 or -1 such that the complete number will be `sign * coefficient *
|
||
10 ^ exponent` and will refer to the sign in documentation as either *positive*
|
||
or *negative*.
|
||
|
||
There is currently no maximum or minimum values for the exponent. Because of
|
||
that all numbers are "normal". This means that when an operation should,
|
||
according to the specification, return a number that "underflow" 0 is returned
|
||
instead of Etiny. This may happen when dividing a number with infinity.
|
||
Additionally, overflow, underflow and clamped may never be signalled.
|
||
"""
|
||
|
||
import Bitwise
|
||
import Kernel, except: [abs: 1, div: 2, max: 2, min: 2, rem: 2, round: 1]
|
||
import Decimal.Macros
|
||
|
||
@power_of_2_to_52 4_503_599_627_370_496
|
||
|
||
@typedoc """
|
||
The coefficient of the power of `10`. Non-negative because the sign is stored separately in `sign`.
|
||
|
||
* `non_neg_integer` - when the `t` represents a number, instead of one of the special values below.
|
||
* `:qNaN` - a quiet NaN was produced by a previous operation. Quiet NaNs propagate quietly, unlike signaling NaNs
|
||
that return errors (based on the `Decimal.Context`).
|
||
* `:sNaN` - signalling NaN that indicated an error occurred that should stop the next operation with an error
|
||
(based on the `Decimal.Context`).
|
||
* `:inf` - Infinity.
|
||
|
||
"""
|
||
@type coefficient :: non_neg_integer | :qNaN | :sNaN | :inf
|
||
|
||
@typedoc """
|
||
The exponent to which `10` is raised.
|
||
"""
|
||
@type exponent :: integer
|
||
|
||
@typedoc """
|
||
|
||
* `1` for positive
|
||
* `-1` for negative
|
||
|
||
"""
|
||
@type sign :: 1 | -1
|
||
|
||
@type signal ::
|
||
:invalid_operation
|
||
| :division_by_zero
|
||
| :rounded
|
||
| :inexact
|
||
|
||
@typedoc """
|
||
Rounding algorithm.
|
||
|
||
See `Decimal.Context` for more information.
|
||
"""
|
||
@type rounding ::
|
||
:down
|
||
| :half_up
|
||
| :half_even
|
||
| :ceiling
|
||
| :floor
|
||
| :half_down
|
||
| :up
|
||
|
||
@typedoc """
|
||
This implementation models the `sign` as `1` or `-1` such that the complete number will be: `sign * coef * 10 ^ exp`.
|
||
|
||
* `coef` - the coefficient of the power of `10`.
|
||
* `exp` - the exponent of the power of `10`.
|
||
* `sign` - `1` for positive, `-1` for negative.
|
||
|
||
"""
|
||
@type t :: %__MODULE__{
|
||
sign: sign,
|
||
coef: coefficient,
|
||
exp: exponent
|
||
}
|
||
|
||
@type decimal :: t | integer | String.t()
|
||
|
||
defstruct sign: 1, coef: 0, exp: 0
|
||
|
||
@context_key :"$decimal_context"
|
||
|
||
defmodule Error do
|
||
@moduledoc """
|
||
The exception that all decimal operations may raise.
|
||
|
||
## Fields
|
||
|
||
* `signal` - the signalled error, additional signalled errors will be found
|
||
in the context.
|
||
* `reason` - the reason for the error.
|
||
* `result` - the result of the operation signalling the error.
|
||
|
||
Rescuing the error to access the result or the other fields of the error is
|
||
discouraged and should only be done for exceptional conditions. It is more
|
||
pragmatic to set the appropriate traps on the context and check the flags
|
||
after the operation if the result needs to be inspected.
|
||
"""
|
||
|
||
defexception [:message, :signal, :reason, :result]
|
||
|
||
def exception(opts) do
|
||
reason = if opts[:reason], do: ": " <> opts[:reason]
|
||
message = "#{opts[:signal]}#{reason}"
|
||
|
||
struct(__MODULE__, [message: message] ++ opts)
|
||
end
|
||
end
|
||
|
||
defmodule Context do
|
||
@moduledoc """
|
||
The context is kept in the process dictionary. It can be accessed with
|
||
`Decimal.get_context/0` and `Decimal.set_context/1`.
|
||
|
||
The default context has a precision of 28, the rounding algorithm is
|
||
`:half_up`. The set trap enablers are `:invalid_operation` and
|
||
`:division_by_zero`.
|
||
|
||
## Fields
|
||
|
||
* `precision` - maximum number of decimal digits in the coefficient. If an
|
||
operation result has more digits it will be rounded to `precision`
|
||
digits with the rounding algorithm in `rounding`.
|
||
* `rounding` - the rounding algorithm used when the coefficient's number of
|
||
exceeds `precision`. Strategies explained below.
|
||
* `flags` - a list of signals that for which the flag is sent. When an
|
||
exceptional condition is signalled its flag is set. The flags are sticky
|
||
and will be set until explicitly cleared.
|
||
* `traps` - a list of set trap enablers for signals. When a signal's trap
|
||
enabler is set the condition causes `Decimal.Error` to be raised.
|
||
|
||
## Rounding algorithms
|
||
|
||
* `:down` - round toward zero (truncate). Discarded digits are ignored,
|
||
result is unchanged.
|
||
* `:half_up` - if the discarded digits is greater than or equal to half of
|
||
the value of a one in the next left position then the coefficient will be
|
||
incremented by one (rounded up). Otherwise (the discarded digits are less
|
||
than half) the discarded digits will be ignored.
|
||
* `:half_even` - also known as "round to nearest" or "banker's rounding". If
|
||
the discarded digits is greater than half of the value of a one in the
|
||
next left position then the coefficient will be incremented by one
|
||
(rounded up). If they represent less than half discarded digits will be
|
||
ignored. Otherwise (exactly half), the coefficient is not altered if it's
|
||
even, or incremented by one (rounded up) if it's odd (to make an even
|
||
number).
|
||
* `:ceiling` - round toward +Infinity. If all of the discarded digits are
|
||
zero or the sign is negative the result is unchanged. Otherwise, the
|
||
coefficient will be incremented by one (rounded up).
|
||
* `:floor` - round toward -Infinity. If all of the discarded digits are zero
|
||
or the sign is positive the result is unchanged. Otherwise, the sign is
|
||
negative and coefficient will be incremented by one.
|
||
* `:half_down` - if the discarded digits is greater than half of the value
|
||
of a one in the next left position then the coefficient will be
|
||
incremented by one (rounded up). Otherwise (the discarded digits are half
|
||
or less) the discarded digits are ignored.
|
||
* `:up` - round away from zero. If all discarded digits are zero the
|
||
coefficient is not changed, otherwise it is incremented by one (rounded
|
||
up).
|
||
|
||
This table shows the results of rounding operations for all the rounding
|
||
algorithms:
|
||
|
||
Rounding algorithm | 5.5 | 2.5 | 1.6 | 1.1 | 1.0 | -1.0 | -1.1 | -1.6 | -2.5 | -5.5
|
||
:----------------- | :-- | :-- | :-- | :-- | :-- | :--- | :--- | :--- | :--- | :---
|
||
`:up` | 6 | 3 | 2 | 2 | 1 | -1 | -2 | -2 | -3 | -6
|
||
`:down` | 5 | 2 | 1 | 1 | 1 | -1 | -1 | -1 | -2 | -5
|
||
`:ceiling` | 6 | 3 | 2 | 2 | 1 | -1 | -1 | -1 | -2 | -5
|
||
`:floor` | 5 | 2 | 1 | 1 | 1 | -1 | -2 | -2 | -3 | -6
|
||
`:half_up` | 6 | 3 | 2 | 1 | 1 | -1 | -1 | -2 | -3 | -6
|
||
`:half_down` | 5 | 2 | 2 | 1 | 1 | -1 | -1 | -2 | -2 | -5
|
||
`:half_even` | 6 | 2 | 2 | 1 | 1 | -1 | -1 | -2 | -2 | -6
|
||
|
||
"""
|
||
@type t :: %__MODULE__{
|
||
precision: pos_integer,
|
||
rounding: Decimal.rounding(),
|
||
flags: [Decimal.signal()],
|
||
traps: [Decimal.signal()]
|
||
}
|
||
|
||
defstruct precision: 28,
|
||
rounding: :half_up,
|
||
flags: [],
|
||
traps: [:invalid_operation, :division_by_zero]
|
||
end
|
||
|
||
defmacrop error(flags, reason, result, context \\ nil) do
|
||
quote bind_quoted: binding() do
|
||
case handle_error(flags, reason, result, context) do
|
||
{:ok, result} -> result
|
||
{:error, error} -> raise Error, error
|
||
end
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns `true` if number is NaN, otherwise `false`.
|
||
"""
|
||
@spec nan?(t) :: boolean
|
||
def nan?(%Decimal{coef: :sNaN}), do: true
|
||
def nan?(%Decimal{coef: :qNaN}), do: true
|
||
def nan?(%Decimal{}), do: false
|
||
|
||
@doc """
|
||
Returns `true` if number is ±Infinity, otherwise `false`.
|
||
"""
|
||
@spec inf?(t) :: boolean
|
||
def inf?(%Decimal{coef: :inf}), do: true
|
||
def inf?(%Decimal{}), do: false
|
||
|
||
@doc """
|
||
Returns `true` if argument is a decimal number, otherwise `false`.
|
||
"""
|
||
@spec decimal?(any) :: boolean
|
||
def decimal?(%Decimal{}), do: true
|
||
def decimal?(_), do: false
|
||
|
||
@doc """
|
||
The absolute value of given number. Sets the number's sign to positive.
|
||
"""
|
||
@spec abs(t) :: t
|
||
def abs(%Decimal{coef: :sNaN} = num), do: error(:invalid_operation, "operation on NaN", num)
|
||
def abs(%Decimal{coef: :qNaN} = num), do: %{num | sign: 1}
|
||
def abs(%Decimal{} = num), do: context(%{num | sign: 1})
|
||
|
||
@doc """
|
||
Adds two numbers together.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If one number is -Infinity and the other +Infinity `:invalid_operation` will
|
||
be signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.add(1, "1.1")
|
||
#Decimal<2.1>
|
||
|
||
iex> Decimal.add(1, "Inf")
|
||
#Decimal<Infinity>
|
||
|
||
"""
|
||
@spec add(decimal, decimal) :: t
|
||
def add(%Decimal{coef: :sNaN} = num1, %Decimal{}),
|
||
do: error(:invalid_operation, "operation on NaN", num1)
|
||
|
||
def add(%Decimal{}, %Decimal{coef: :sNaN} = num2),
|
||
do: error(:invalid_operation, "operation on NaN", num2)
|
||
|
||
def add(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: num1
|
||
|
||
def add(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def add(%Decimal{coef: :inf, sign: sign} = num1, %Decimal{coef: :inf, sign: sign} = num2) do
|
||
if num1.exp > num2.exp do
|
||
num1
|
||
else
|
||
num2
|
||
end
|
||
end
|
||
|
||
def add(%Decimal{coef: :inf}, %Decimal{coef: :inf}),
|
||
do: error(:invalid_operation, "adding +Infinity and -Infinity", %Decimal{coef: :NaN})
|
||
|
||
def add(%Decimal{coef: :inf} = num1, %Decimal{}), do: num1
|
||
|
||
def add(%Decimal{}, %Decimal{coef: :inf} = num2), do: num2
|
||
|
||
def add(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
|
||
{coef1, coef2} = add_align(coef1, exp1, coef2, exp2)
|
||
coef = sign1 * coef1 + sign2 * coef2
|
||
exp = Kernel.min(exp1, exp2)
|
||
sign = add_sign(sign1, sign2, coef)
|
||
context(%Decimal{sign: sign, coef: Kernel.abs(coef), exp: exp})
|
||
end
|
||
|
||
def add(num1, num2), do: add(decimal(num1), decimal(num2))
|
||
|
||
@doc """
|
||
Subtracts second number from the first. Equivalent to `Decimal.add/2` when the
|
||
second number's sign is negated.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If one number is -Infinity and the other +Infinity `:invalid_operation` will
|
||
be signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.sub(1, "0.1")
|
||
#Decimal<0.9>
|
||
|
||
iex> Decimal.sub(1, "Inf")
|
||
#Decimal<-Infinity>
|
||
|
||
"""
|
||
@spec sub(decimal, decimal) :: t
|
||
def sub(%Decimal{} = num1, %Decimal{sign: sign} = num2) do
|
||
add(num1, %{num2 | sign: -sign})
|
||
end
|
||
|
||
def sub(num1, num2) do
|
||
sub(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Compares two numbers numerically. If the first number is greater than the second
|
||
`#Decimal<1>` is returned, if less than `#Decimal<-1>` is returned. Otherwise,
|
||
if both numbers are equal `#Decimal<0>` is returned. If either number is a quiet
|
||
NaN, then that number is returned.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.compare("1.0", 1)
|
||
#Decimal<0>
|
||
|
||
iex> Decimal.compare("Inf", -1)
|
||
#Decimal<1>
|
||
|
||
"""
|
||
@spec compare(decimal, decimal) :: t
|
||
def compare(%Decimal{coef: :qNaN} = num1, _num2), do: num1
|
||
|
||
def compare(_num1, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def compare(%Decimal{coef: :inf, sign: sign}, %Decimal{coef: :inf, sign: sign}),
|
||
do: %Decimal{sign: 1, coef: 0}
|
||
|
||
def compare(%Decimal{coef: :inf, sign: sign1}, %Decimal{coef: :inf, sign: sign2})
|
||
when sign1 < sign2,
|
||
do: %Decimal{sign: -1, coef: 1}
|
||
|
||
def compare(%Decimal{coef: :inf, sign: sign1}, %Decimal{coef: :inf, sign: sign2})
|
||
when sign1 > sign2,
|
||
do: %Decimal{sign: 1, coef: 1}
|
||
|
||
def compare(%Decimal{coef: :inf, sign: sign}, _num2), do: %Decimal{sign: sign, coef: 1}
|
||
|
||
def compare(_num1, %Decimal{coef: :inf, sign: sign}), do: %Decimal{sign: sign * -1, coef: 1}
|
||
|
||
def compare(%Decimal{} = num1, %Decimal{} = num2) do
|
||
case sub(num1, num2) do
|
||
%Decimal{coef: 0} -> %Decimal{sign: 1, coef: 0}
|
||
%Decimal{sign: sign} -> %Decimal{sign: sign, coef: 1}
|
||
end
|
||
end
|
||
|
||
def compare(num1, num2) do
|
||
compare(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Compares two numbers numerically. If the first number is greater than the second
|
||
`:gt` is returned, if less than `:lt` is returned, if both numbers are equal
|
||
`:eq` is returned.
|
||
|
||
Neither number can be a NaN. If you need to handle quiet NaNs, use `compare/2`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.cmp("1.0", 1)
|
||
:eq
|
||
|
||
iex> Decimal.cmp("Inf", -1)
|
||
:gt
|
||
|
||
"""
|
||
@spec cmp(decimal, decimal) :: :lt | :eq | :gt
|
||
def cmp(num1, num2) do
|
||
case compare(num1, num2) do
|
||
%Decimal{coef: 1, sign: -1} -> :lt
|
||
%Decimal{coef: 0} -> :eq
|
||
%Decimal{coef: 1, sign: 1} -> :gt
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Compares two numbers numerically and returns `true` if they are equal,
|
||
otherwise `false`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.equal?("1.0", 1)
|
||
true
|
||
|
||
iex> Decimal.equal?(1, -1)
|
||
false
|
||
|
||
"""
|
||
@spec equal?(decimal, decimal) :: boolean
|
||
def equal?(num1, num2) do
|
||
case compare(num1, num2) do
|
||
%Decimal{sign: 1, coef: 0, exp: 0} -> true
|
||
_ -> false
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Compares two numbers numerically and returns `true` if they are equal,
|
||
otherwise `false`. If one of the operands is a quiet NaN this operation
|
||
will always return `false`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.eq?("1.0", 1)
|
||
true
|
||
|
||
iex> Decimal.eq?(1, -1)
|
||
false
|
||
|
||
"""
|
||
doc_since("1.8.0")
|
||
@spec eq?(decimal, decimal) :: boolean
|
||
def eq?(%Decimal{coef: :qNaN}, _num2), do: false
|
||
def eq?(_num1, %Decimal{coef: :qNaN}), do: false
|
||
def eq?(num1, num2), do: cmp(num1, num2) == :eq
|
||
|
||
@doc """
|
||
Compares two numbers numerically and returns `true` if the the first argument
|
||
is greater than the second, otherwise `false`. If one the operands is a
|
||
quiet NaN this operation will always return `false`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.gt?("1.3", "1.2")
|
||
true
|
||
|
||
iex> Decimal.gt?("1.2", "1.3")
|
||
false
|
||
|
||
"""
|
||
doc_since("1.8.0")
|
||
@spec gt?(decimal, decimal) :: boolean
|
||
def gt?(%Decimal{coef: :qNaN}, _num2), do: false
|
||
def gt?(_num1, %Decimal{coef: :qNaN}), do: false
|
||
def gt?(num1, num2), do: cmp(num1, num2) == :gt
|
||
|
||
@doc """
|
||
Compares two numbers numerically and returns `true` if the the first number is
|
||
less than the second number, otherwise `false`. If one of the operands is a
|
||
quiet NaN this operation will always return `false`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.lt?("1.1", "1.2")
|
||
true
|
||
|
||
iex> Decimal.lt?("1.4", "1.2")
|
||
false
|
||
|
||
"""
|
||
doc_since("1.8.0")
|
||
@spec lt?(decimal, decimal) :: boolean
|
||
def lt?(%Decimal{coef: :qNaN}, _num2), do: false
|
||
def lt?(_num1, %Decimal{coef: :qNaN}), do: false
|
||
def lt?(num1, num2), do: cmp(num1, num2) == :lt
|
||
|
||
@doc """
|
||
Divides two numbers.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If both numbers are ±Infinity `:invalid_operation` is signalled.
|
||
* If both numbers are ±0 `:invalid_operation` is signalled.
|
||
* If second number (denominator) is ±0 `:division_by_zero` is signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.div(3, 4)
|
||
#Decimal<0.75>
|
||
|
||
iex> Decimal.div("Inf", -1)
|
||
#Decimal<-Infinity>
|
||
|
||
"""
|
||
@spec div(decimal, decimal) :: t
|
||
def div(%Decimal{coef: :sNaN} = num1, %Decimal{}),
|
||
do: error(:invalid_operation, "operation on NaN", num1)
|
||
|
||
def div(%Decimal{}, %Decimal{coef: :sNaN} = num2),
|
||
do: error(:invalid_operation, "operation on NaN", num2)
|
||
|
||
def div(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: num1
|
||
|
||
def div(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def div(%Decimal{coef: :inf}, %Decimal{coef: :inf}),
|
||
do: error(:invalid_operation, "±Infinity / ±Infinity", %Decimal{coef: :NaN})
|
||
|
||
def div(%Decimal{sign: sign1, coef: :inf} = num1, %Decimal{sign: sign2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
%{num1 | sign: sign}
|
||
end
|
||
|
||
def div(%Decimal{sign: sign1, exp: exp1}, %Decimal{sign: sign2, coef: :inf, exp: exp2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
# TODO: Subnormal
|
||
# exponent?
|
||
%Decimal{sign: sign, coef: 0, exp: exp1 - exp2}
|
||
end
|
||
|
||
def div(%Decimal{coef: 0}, %Decimal{coef: 0}),
|
||
do: error(:invalid_operation, "0 / 0", %Decimal{coef: :NaN})
|
||
|
||
def div(%Decimal{sign: sign1}, %Decimal{sign: sign2, coef: 0}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
error(:division_by_zero, nil, %Decimal{sign: sign, coef: :inf})
|
||
end
|
||
|
||
def div(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
|
||
if coef1 == 0 do
|
||
context(%Decimal{sign: sign, coef: 0, exp: exp1 - exp2}, [])
|
||
else
|
||
prec10 = pow10(get_context().precision)
|
||
{coef1, coef2, adjust} = div_adjust(coef1, coef2, 0)
|
||
{coef, adjust, _rem, signals} = div_calc(coef1, coef2, 0, adjust, prec10)
|
||
|
||
context(%Decimal{sign: sign, coef: coef, exp: exp1 - exp2 - adjust}, signals)
|
||
end
|
||
end
|
||
|
||
def div(num1, num2) do
|
||
div(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Divides two numbers and returns the integer part.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If both numbers are ±Infinity `:invalid_operation` is signalled.
|
||
* If both numbers are ±0 `:invalid_operation` is signalled.
|
||
* If second number (denominator) is ±0 `:division_by_zero` is signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.div_int(5, 2)
|
||
#Decimal<2>
|
||
|
||
iex> Decimal.div_int("Inf", -1)
|
||
#Decimal<-Infinity>
|
||
|
||
"""
|
||
@spec div_int(decimal, decimal) :: t
|
||
def div_int(%Decimal{coef: :sNaN} = num1, %Decimal{}),
|
||
do: error(:invalid_operation, "operation on NaN", num1)
|
||
|
||
def div_int(%Decimal{}, %Decimal{coef: :sNaN} = num2),
|
||
do: error(:invalid_operation, "operation on NaN", num2)
|
||
|
||
def div_int(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: num1
|
||
|
||
def div_int(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def div_int(%Decimal{coef: :inf}, %Decimal{coef: :inf}),
|
||
do: error(:invalid_operation, "±Infinity / ±Infinity", %Decimal{coef: :NaN})
|
||
|
||
def div_int(%Decimal{sign: sign1, coef: :inf} = num1, %Decimal{sign: sign2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
%{num1 | sign: sign}
|
||
end
|
||
|
||
def div_int(%Decimal{sign: sign1, exp: exp1}, %Decimal{sign: sign2, coef: :inf, exp: exp2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
# TODO: Subnormal
|
||
# exponent?
|
||
%Decimal{sign: sign, coef: 0, exp: exp1 - exp2}
|
||
end
|
||
|
||
def div_int(%Decimal{coef: 0}, %Decimal{coef: 0}),
|
||
do: error(:invalid_operation, "0 / 0", %Decimal{coef: :NaN})
|
||
|
||
def div_int(%Decimal{sign: sign1}, %Decimal{sign: sign2, coef: 0}) do
|
||
div_sign = if sign1 == sign2, do: 1, else: -1
|
||
error(:division_by_zero, nil, %Decimal{sign: div_sign, coef: :inf})
|
||
end
|
||
|
||
def div_int(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
div_sign = if sign1 == sign2, do: 1, else: -1
|
||
|
||
cond do
|
||
compare(%{num1 | sign: 1}, %{num2 | sign: 1}) == %Decimal{sign: -1, coef: 1} ->
|
||
%Decimal{sign: div_sign, coef: 0, exp: exp1 - exp2}
|
||
|
||
coef1 == 0 ->
|
||
context(%{num1 | sign: div_sign})
|
||
|
||
true ->
|
||
case integer_division(div_sign, coef1, exp1, coef2, exp2) do
|
||
{:ok, result} ->
|
||
result
|
||
|
||
{:error, error, reason, num} ->
|
||
error(error, reason, num)
|
||
end
|
||
end
|
||
end
|
||
|
||
def div_int(num1, num2) do
|
||
div_int(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Remainder of integer division of two numbers. The result will have the sign of
|
||
the first number.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If both numbers are ±Infinity `:invalid_operation` is signalled.
|
||
* If both numbers are ±0 `:invalid_operation` is signalled.
|
||
* If second number (denominator) is ±0 `:division_by_zero` is signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.rem(5, 2)
|
||
#Decimal<1>
|
||
|
||
"""
|
||
@spec rem(decimal, decimal) :: t
|
||
def rem(%Decimal{coef: :sNaN} = num1, %Decimal{}),
|
||
do: error(:invalid_operation, "operation on NaN", num1)
|
||
|
||
def rem(%Decimal{}, %Decimal{coef: :sNaN} = num2),
|
||
do: error(:invalid_operation, "operation on NaN", num2)
|
||
|
||
def rem(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: num1
|
||
|
||
def rem(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def rem(%Decimal{coef: :inf}, %Decimal{coef: :inf}),
|
||
do: error(:invalid_operation, "±Infinity / ±Infinity", %Decimal{coef: :NaN})
|
||
|
||
def rem(%Decimal{sign: sign1, coef: :inf}, %Decimal{}), do: %Decimal{sign: sign1, coef: 0}
|
||
|
||
def rem(%Decimal{sign: sign1}, %Decimal{coef: :inf} = num2) do
|
||
# TODO: Subnormal
|
||
# exponent?
|
||
%{num2 | sign: sign1}
|
||
end
|
||
|
||
def rem(%Decimal{coef: 0}, %Decimal{coef: 0}),
|
||
do: error(:invalid_operation, "0 / 0", %Decimal{coef: :NaN})
|
||
|
||
def rem(%Decimal{sign: sign1}, %Decimal{coef: 0}),
|
||
do: error(:division_by_zero, nil, %Decimal{sign: sign1, coef: 0})
|
||
|
||
def rem(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
|
||
cond do
|
||
compare(%{num1 | sign: 1}, %{num2 | sign: 1}) == %Decimal{sign: -1, coef: 1} ->
|
||
%{num1 | sign: sign1}
|
||
|
||
coef1 == 0 ->
|
||
context(%{num2 | sign: sign1})
|
||
|
||
true ->
|
||
div_sign = if sign1 == sign2, do: 1, else: -1
|
||
|
||
case integer_division(div_sign, coef1, exp1, coef2, exp2) do
|
||
{:ok, result} ->
|
||
sub(num1, mult(num2, result))
|
||
|
||
{:error, error, reason, num} ->
|
||
error(error, reason, num)
|
||
end
|
||
end
|
||
end
|
||
|
||
def rem(num1, num2) do
|
||
rem(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Integer division of two numbers and the remainder. Should be used when both
|
||
`div_int/2` and `rem/2` is needed. Equivalent to: `{Decimal.div_int(x, y),
|
||
Decimal.rem(x, y)}`.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If both numbers are ±Infinity `:invalid_operation` is signalled.
|
||
* If both numbers are ±0 `:invalid_operation` is signalled.
|
||
* If second number (denominator) is ±0 `:division_by_zero` is signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.div_rem(5, 2)
|
||
{Decimal.new(2), Decimal.new(1)}
|
||
|
||
"""
|
||
@spec div_rem(decimal, decimal) :: {t, t}
|
||
def div_rem(%Decimal{coef: :sNaN} = num1, %Decimal{}) do
|
||
{
|
||
error(:invalid_operation, "operation on NaN", num1),
|
||
error(:invalid_operation, "operation on NaN", num1)
|
||
}
|
||
end
|
||
|
||
def div_rem(%Decimal{}, %Decimal{coef: :sNaN} = num2) do
|
||
{
|
||
error(:invalid_operation, "operation on NaN", num2),
|
||
error(:invalid_operation, "operation on NaN", num2)
|
||
}
|
||
end
|
||
|
||
def div_rem(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: {num1, num1}
|
||
|
||
def div_rem(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: {num2, num2}
|
||
|
||
def div_rem(%Decimal{coef: :inf}, %Decimal{coef: :inf}) do
|
||
numbers = {%Decimal{coef: :NaN}, %Decimal{coef: :NaN}}
|
||
error(:invalid_operation, "±Infinity / ±Infinity", numbers)
|
||
end
|
||
|
||
def div_rem(%Decimal{sign: sign1, coef: :inf} = num1, %Decimal{sign: sign2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
{%{num1 | sign: sign}, %Decimal{sign: sign1, coef: 0}}
|
||
end
|
||
|
||
def div_rem(%Decimal{} = num1, %Decimal{coef: :inf} = num2) do
|
||
%Decimal{sign: sign1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, exp: exp2} = num2
|
||
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
# TODO: Subnormal
|
||
# exponent?
|
||
{%Decimal{sign: sign, coef: 0, exp: exp1 - exp2}, %{num2 | sign: sign1}}
|
||
end
|
||
|
||
def div_rem(%Decimal{coef: 0}, %Decimal{coef: 0}) do
|
||
error = error(:invalid_operation, "0 / 0", %Decimal{coef: :NaN})
|
||
{error, error}
|
||
end
|
||
|
||
def div_rem(%Decimal{sign: sign1}, %Decimal{sign: sign2, coef: 0}) do
|
||
div_sign = if sign1 == sign2, do: 1, else: -1
|
||
div_error = error(:division_by_zero, nil, %Decimal{sign: div_sign, coef: :inf})
|
||
rem_error = error(:division_by_zero, nil, %Decimal{sign: sign1, coef: 0})
|
||
{div_error, rem_error}
|
||
end
|
||
|
||
def div_rem(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
div_sign = if sign1 == sign2, do: 1, else: -1
|
||
|
||
cond do
|
||
compare(%{num1 | sign: 1}, %{num2 | sign: 1}) == %Decimal{sign: -1, coef: 1} ->
|
||
{%Decimal{sign: div_sign, coef: 0, exp: exp1 - exp2}, %{num1 | sign: sign1}}
|
||
|
||
coef1 == 0 ->
|
||
{context(%{num1 | sign: div_sign}), context(%{num2 | sign: sign1})}
|
||
|
||
true ->
|
||
case integer_division(div_sign, coef1, exp1, coef2, exp2) do
|
||
{:ok, result} ->
|
||
{result, sub(num1, mult(num2, result))}
|
||
|
||
{:error, error, reason, num} ->
|
||
error(error, reason, {num, num})
|
||
end
|
||
end
|
||
end
|
||
|
||
def div_rem(num1, num2) do
|
||
div_rem(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Compares two values numerically and returns the maximum. Unlike most other
|
||
functions in `Decimal` if a number is NaN the result will be the other number.
|
||
Only if both numbers are NaN will NaN be returned.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.max(1, "2.0")
|
||
#Decimal<2.0>
|
||
|
||
iex> Decimal.max(1, "NaN")
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.max("NaN", "NaN")
|
||
#Decimal<NaN>
|
||
|
||
"""
|
||
@spec max(decimal, decimal) :: t
|
||
def max(%Decimal{coef: :qNaN}, %Decimal{} = num2), do: num2
|
||
|
||
def max(%Decimal{} = num1, %Decimal{coef: :qNaN}), do: num1
|
||
|
||
def max(%Decimal{sign: sign1, exp: exp1} = num1, %Decimal{sign: sign2, exp: exp2} = num2) do
|
||
case compare(num1, num2) do
|
||
%Decimal{sign: -1, coef: 1} ->
|
||
num2
|
||
|
||
%Decimal{sign: 1, coef: 1} ->
|
||
num1
|
||
|
||
%Decimal{coef: 0} ->
|
||
cond do
|
||
sign1 != sign2 ->
|
||
if sign1 == 1, do: num1, else: num2
|
||
|
||
sign1 == 1 ->
|
||
if exp1 > exp2, do: num1, else: num2
|
||
|
||
sign1 == -1 ->
|
||
if exp1 < exp2, do: num1, else: num2
|
||
end
|
||
end
|
||
|> context()
|
||
end
|
||
|
||
def max(num1, num2) do
|
||
max(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Compares two values numerically and returns the minimum. Unlike most other
|
||
functions in `Decimal` if a number is NaN the result will be the other number.
|
||
Only if both numbers are NaN will NaN be returned.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.min(1, "2.0")
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.min(1, "NaN")
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.min("NaN", "NaN")
|
||
#Decimal<NaN>
|
||
|
||
"""
|
||
@spec min(decimal, decimal) :: t
|
||
def min(%Decimal{coef: :qNaN}, %Decimal{} = num2), do: num2
|
||
|
||
def min(%Decimal{} = num1, %Decimal{coef: :qNaN}), do: num1
|
||
|
||
def min(%Decimal{sign: sign1, exp: exp1} = num1, %Decimal{sign: sign2, exp: exp2} = num2) do
|
||
case compare(num1, num2) do
|
||
%Decimal{sign: -1, coef: 1} ->
|
||
num1
|
||
|
||
%Decimal{sign: 1, coef: 1} ->
|
||
num2
|
||
|
||
%Decimal{coef: 0} ->
|
||
cond do
|
||
sign1 != sign2 ->
|
||
if sign1 == -1, do: num1, else: num2
|
||
|
||
sign1 == 1 ->
|
||
if exp1 < exp2, do: num1, else: num2
|
||
|
||
sign1 == -1 ->
|
||
if exp1 > exp2, do: num1, else: num2
|
||
end
|
||
end
|
||
|> context()
|
||
end
|
||
|
||
def min(num1, num2) do
|
||
min(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Negates the given number.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.minus(1)
|
||
#Decimal<-1>
|
||
|
||
iex> Decimal.minus("-Inf")
|
||
#Decimal<Infinity>
|
||
|
||
"""
|
||
@spec minus(decimal) :: t
|
||
def minus(%Decimal{coef: :sNaN} = num), do: error(:invalid_operation, "operation on NaN", num)
|
||
def minus(%Decimal{coef: :qNaN} = num), do: num
|
||
def minus(%Decimal{sign: sign} = num), do: context(%{num | sign: -sign})
|
||
def minus(num), do: minus(decimal(num))
|
||
|
||
@doc """
|
||
Applies the context to the given number rounding it to specified precision.
|
||
"""
|
||
@spec plus(t) :: t
|
||
def plus(%Decimal{coef: :sNaN} = num), do: error(:invalid_operation, "operation on NaN", num)
|
||
def plus(%Decimal{} = num), do: context(num)
|
||
|
||
@doc """
|
||
Check if given number is positive
|
||
"""
|
||
doc_since("1.5.0")
|
||
@spec positive?(t) :: boolean
|
||
def positive?(%Decimal{coef: :sNaN} = num),
|
||
do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def positive?(%Decimal{coef: :qNaN}), do: false
|
||
def positive?(%Decimal{coef: 0}), do: false
|
||
def positive?(%Decimal{sign: -1}), do: false
|
||
def positive?(%Decimal{sign: 1}), do: true
|
||
|
||
@doc """
|
||
Check if given number is negative
|
||
"""
|
||
doc_since("1.5.0")
|
||
@spec negative?(t) :: boolean
|
||
def negative?(%Decimal{coef: :sNaN} = num),
|
||
do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def negative?(%Decimal{coef: :qNaN}), do: false
|
||
def negative?(%Decimal{coef: 0}), do: false
|
||
def negative?(%Decimal{sign: 1}), do: false
|
||
def negative?(%Decimal{sign: -1}), do: true
|
||
|
||
@doc """
|
||
Multiplies two numbers.
|
||
|
||
## Exceptional conditions
|
||
|
||
* If one number is ±0 and the other is ±Infinity `:invalid_operation` is
|
||
signalled.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.mult("0.5", 3)
|
||
#Decimal<1.5>
|
||
|
||
iex> Decimal.mult("Inf", -1)
|
||
#Decimal<-Infinity>
|
||
|
||
"""
|
||
@spec mult(decimal, decimal) :: t
|
||
def mult(%Decimal{coef: :sNaN} = num1, %Decimal{}),
|
||
do: error(:invalid_operation, "operation on NaN", num1)
|
||
|
||
def mult(%Decimal{}, %Decimal{coef: :sNaN} = num2),
|
||
do: error(:invalid_operation, "operation on NaN", num2)
|
||
|
||
def mult(%Decimal{coef: :qNaN} = num1, %Decimal{}), do: num1
|
||
|
||
def mult(%Decimal{}, %Decimal{coef: :qNaN} = num2), do: num2
|
||
|
||
def mult(%Decimal{coef: 0}, %Decimal{coef: :inf}),
|
||
do: error(:invalid_operation, "0 * ±Infinity", %Decimal{coef: :NaN})
|
||
|
||
def mult(%Decimal{coef: :inf}, %Decimal{coef: 0}),
|
||
do: error(:invalid_operation, "0 * ±Infinity", %Decimal{coef: :NaN})
|
||
|
||
def mult(%Decimal{sign: sign1, coef: :inf, exp: exp1}, %Decimal{sign: sign2, exp: exp2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
# exponent?
|
||
%Decimal{sign: sign, coef: :inf, exp: exp1 + exp2}
|
||
end
|
||
|
||
def mult(%Decimal{sign: sign1, exp: exp1}, %Decimal{sign: sign2, coef: :inf, exp: exp2}) do
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
# exponent?
|
||
%Decimal{sign: sign, coef: :inf, exp: exp1 + exp2}
|
||
end
|
||
|
||
def mult(%Decimal{} = num1, %Decimal{} = num2) do
|
||
%Decimal{sign: sign1, coef: coef1, exp: exp1} = num1
|
||
%Decimal{sign: sign2, coef: coef2, exp: exp2} = num2
|
||
sign = if sign1 == sign2, do: 1, else: -1
|
||
%Decimal{sign: sign, coef: coef1 * coef2, exp: exp1 + exp2} |> context()
|
||
end
|
||
|
||
def mult(num1, num2) do
|
||
mult(decimal(num1), decimal(num2))
|
||
end
|
||
|
||
@doc """
|
||
Reduces the given number. Removes trailing zeros from coefficient while
|
||
keeping the number numerically equivalent by increasing the exponent.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.reduce(Decimal.new("1.00"))
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.reduce(Decimal.new("1.01"))
|
||
#Decimal<1.01>
|
||
|
||
"""
|
||
@spec reduce(t) :: t
|
||
def reduce(%Decimal{coef: :sNaN} = num), do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def reduce(%Decimal{coef: :qNaN} = num), do: num
|
||
|
||
def reduce(%Decimal{coef: :inf} = num) do
|
||
# exponent?
|
||
%{num | exp: 0}
|
||
end
|
||
|
||
def reduce(%Decimal{sign: sign, coef: coef, exp: exp}) do
|
||
if coef == 0 do
|
||
%Decimal{sign: sign, coef: 0, exp: 0}
|
||
else
|
||
%{do_reduce(coef, exp) | sign: sign} |> context
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Rounds the given number to specified decimal places with the given strategy
|
||
(default is to round to nearest one). If places is negative, at least that
|
||
many digits to the left of the decimal point will be zero.
|
||
|
||
See `Decimal.Context` for more information about rounding algorithms.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.round("1.234")
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.round("1.234", 1)
|
||
#Decimal<1.2>
|
||
|
||
"""
|
||
@spec round(decimal, integer, rounding) :: t
|
||
def round(num, places \\ 0, mode \\ :half_up)
|
||
|
||
def round(%Decimal{coef: :sNaN} = num, _, _),
|
||
do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def round(%Decimal{coef: :qNaN} = num, _, _), do: num
|
||
|
||
def round(%Decimal{coef: :inf} = num, _, _), do: num
|
||
|
||
def round(%Decimal{} = num, n, mode) do
|
||
%Decimal{sign: sign, coef: coef, exp: exp} = reduce(num)
|
||
digits = :erlang.integer_to_list(coef)
|
||
target_exp = -n
|
||
value = do_round(sign, digits, exp, target_exp, mode)
|
||
context(value, [])
|
||
end
|
||
|
||
def round(num, n, mode) do
|
||
round(decimal(num), n, mode)
|
||
end
|
||
|
||
@doc """
|
||
Finds the square root.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.sqrt("100")
|
||
#Decimal<10>
|
||
|
||
"""
|
||
doc_since("1.7.0")
|
||
@spec sqrt(decimal) :: t
|
||
def sqrt(%Decimal{coef: :sNaN} = num),
|
||
do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def sqrt(%Decimal{coef: :qNaN} = num),
|
||
do: error(:invalid_operation, "operation on NaN", num)
|
||
|
||
def sqrt(%Decimal{coef: 0, exp: exp} = num),
|
||
do: %{num | exp: exp >>> 1}
|
||
|
||
def sqrt(%Decimal{sign: -1} = num),
|
||
do: error(:invalid_operation, "less than zero", num)
|
||
|
||
def sqrt(%Decimal{sign: 1, coef: :inf} = num),
|
||
do: num
|
||
|
||
def sqrt(%Decimal{sign: 1, coef: coef, exp: exp}) do
|
||
precision = get_context().precision + 1
|
||
digits = :erlang.integer_to_list(coef)
|
||
num_digits = length(digits)
|
||
|
||
# Since the root is calculated from integer operations only, it must be
|
||
# large enough to contain the desired precision. Calculate the amount of
|
||
# `shift` required (powers of 10).
|
||
case exp &&& 1 do
|
||
0 ->
|
||
# To get the desired `shift`, subtract the precision of `coef`'s square
|
||
# root from the desired precision.
|
||
#
|
||
# If `coef` is 10_000, the root is 100 (3 digits of precision).
|
||
# If `coef` is 100, the root is 10 (2 digits of precision).
|
||
shift = precision - ((num_digits + 1) >>> 1)
|
||
sqrt(coef, shift, exp)
|
||
|
||
_ ->
|
||
# If `exp` is odd, multiply `coef` by 10 and reduce shift by 1/2. `exp`
|
||
# must be even so the root's exponent is an integer.
|
||
shift = precision - ((num_digits >>> 1) + 1)
|
||
sqrt(coef * 10, shift, exp)
|
||
end
|
||
end
|
||
|
||
def sqrt(num) do
|
||
sqrt(decimal(num))
|
||
end
|
||
|
||
defp sqrt(coef, shift, exp) do
|
||
if shift >= 0 do
|
||
# shift `coef` up by `shift * 2` digits
|
||
sqrt(coef * pow10(shift <<< 1), shift, exp, true)
|
||
else
|
||
# shift `coef` down by `shift * 2` digits
|
||
operand = pow10(-shift <<< 1)
|
||
sqrt(Kernel.div(coef, operand), shift, exp, Kernel.rem(coef, operand) === 0)
|
||
end
|
||
end
|
||
|
||
defp sqrt(shifted_coef, shift, exp, exact) do
|
||
# the preferred exponent is `exp / 2` as per IEEE 754
|
||
exp = exp >>> 1
|
||
# guess a root 10x higher than desired precision
|
||
guess = pow10(get_context().precision + 1)
|
||
root = sqrt_loop(shifted_coef, guess)
|
||
|
||
if exact and root * root === shifted_coef do
|
||
# if the root is exact, use preferred `exp` and shift `coef` to match
|
||
coef =
|
||
if shift >= 0,
|
||
do: Kernel.div(root, pow10(shift)),
|
||
else: root * pow10(-shift)
|
||
|
||
context(%Decimal{sign: 1, coef: coef, exp: exp})
|
||
else
|
||
# otherwise the calculated root is inexact (but still meets precision),
|
||
# so use the root as `coef` and get the final exponent by shifting `exp`
|
||
context(%Decimal{sign: 1, coef: root, exp: exp - shift})
|
||
end
|
||
end
|
||
|
||
# Babylonion method
|
||
defp sqrt_loop(coef, guess) do
|
||
quotient = Kernel.div(coef, guess)
|
||
|
||
if guess <= quotient do
|
||
guess
|
||
else
|
||
sqrt_loop(coef, (guess + quotient) >>> 1)
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Creates a new decimal number from an integer or a string representation.
|
||
|
||
A decimal number will always be created exactly as specified with all digits
|
||
kept - it will not be rounded with the context.
|
||
|
||
## Backus–Naur form
|
||
|
||
sign ::= "+" | "-"
|
||
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||
indicator ::= "e" | "E"
|
||
digits ::= digit [digit]...
|
||
decimal-part ::= digits "." [digits] | ["."] digits
|
||
exponent-part ::= indicator [sign] digits
|
||
infinity ::= "Infinity" | "Inf"
|
||
nan ::= "NaN" [digits] | "sNaN" [digits]
|
||
numeric-value ::= decimal-part [exponent-part] | infinity
|
||
numeric-string ::= [sign] numeric-value | [sign] nan
|
||
|
||
## Floats
|
||
|
||
See also `from_float/1`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.new(1)
|
||
#Decimal<1>
|
||
|
||
iex> Decimal.new("3.14")
|
||
#Decimal<3.14>
|
||
"""
|
||
@spec new(decimal) :: t
|
||
def new(%Decimal{} = num), do: num
|
||
|
||
def new(int) when is_integer(int),
|
||
do: %Decimal{sign: if(int < 0, do: -1, else: 1), coef: Kernel.abs(int)}
|
||
|
||
def new(float) when is_float(float) do
|
||
IO.warn(
|
||
"passing float to Decimal.new/1 is deprecated as floats have inherent inaccuracy. Use Decimal.from_float/1 or Decimal.cast/1 instead"
|
||
)
|
||
|
||
from_float(float)
|
||
end
|
||
|
||
def new(binary) when is_binary(binary) do
|
||
case do_parse(binary) do
|
||
{:ok, decimal} -> decimal
|
||
{:error, error} -> raise Error, error
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Creates a new decimal number from the sign, coefficient and exponent such that
|
||
the number will be: `sign * coefficient * 10 ^ exponent`.
|
||
|
||
A decimal number will always be created exactly as specified with all digits
|
||
kept - it will not be rounded with the context.
|
||
"""
|
||
@spec new(1 | -1, non_neg_integer | :qNaN | :sNaN | :inf, integer) :: t
|
||
def new(sign, coef, exp)
|
||
when sign in [1, -1] and
|
||
((is_integer(coef) and coef >= 0) or coef in [:qNaN, :sNan, :inf]) and
|
||
is_integer(exp),
|
||
do: %Decimal{sign: sign, coef: coef, exp: exp}
|
||
|
||
@doc """
|
||
Creates a new decimal number from a floating point number.
|
||
|
||
Floating point numbers use a fixed number of binary digits to represent
|
||
a decimal number which has inherent inaccuracy as some decimal numbers cannot
|
||
be represented exactly in limited precision binary.
|
||
|
||
Floating point numbers will be converted to decimal numbers with
|
||
`:io_lib_format.fwrite_g/1`. Since this conversion is not exact and
|
||
because of inherent inaccuracy mentioned above, we may run into counter-intuitive results:
|
||
|
||
iex> Enum.reduce([0.1, 0.1, 0.1], &+/2)
|
||
0.30000000000000004
|
||
|
||
iex> Enum.reduce([Decimal.new("0.1"), Decimal.new("0.1"), Decimal.new("0.1")], &Decimal.add/2)
|
||
#Decimal<0.3>
|
||
|
||
For this reason, it's recommended to build decimals with `new/1`, which is always precise, instead.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.from_float(3.14)
|
||
#Decimal<3.14>
|
||
|
||
"""
|
||
doc_since("1.5.0")
|
||
@spec from_float(float) :: t
|
||
def from_float(float) when is_float(float) do
|
||
float
|
||
|> :io_lib_format.fwrite_g()
|
||
|> fix_float_exp()
|
||
|> IO.iodata_to_binary()
|
||
|> new()
|
||
end
|
||
|
||
@doc """
|
||
Creates a new decimal number from an integer, string, float, or existing decimal number.
|
||
|
||
Because conversion from a floating point number is not exact, it's recommended
|
||
to instead use `new/1` or `from_float/1` when the argument's type is certain.
|
||
See `from_float/1`.
|
||
|
||
If the value cannot be cast, Decimal.Error is raised.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.cast(3)
|
||
#Decimal<3>
|
||
|
||
iex> Decimal.cast(3.0)
|
||
#Decimal<3.0>
|
||
|
||
iex> Decimal.cast("3")
|
||
#Decimal<3>
|
||
|
||
iex> Decimal.cast("3.0")
|
||
#Decimal<3.0>
|
||
|
||
iex> Decimal.new(3) |> Decimal.cast()
|
||
#Decimal<3>
|
||
|
||
"""
|
||
@spec cast(float | decimal) :: t
|
||
def cast(float) when is_float(float), do: from_float(float)
|
||
def cast(value), do: new(value)
|
||
|
||
@doc """
|
||
Parses a binary into a decimal.
|
||
|
||
If successful, returns a tuple in the form of `{:ok, decimal}`,
|
||
otherwise `:error`.
|
||
|
||
## Examples
|
||
|
||
iex> Decimal.parse("3.14")
|
||
{:ok, %Decimal{coef: 314, exp: -2, sign: 1}}
|
||
|
||
iex> Decimal.parse("-1.1e3")
|
||
{:ok, %Decimal{coef: 11, exp: 2, sign: -1}}
|
||
|
||
iex> Decimal.parse("bad")
|
||
:error
|
||
|
||
"""
|
||
@spec parse(String.t()) :: {:ok, t} | :error
|
||
def parse(binary) when is_binary(binary) do
|
||
case do_parse(binary) do
|
||
{:ok, decimal} -> {:ok, decimal}
|
||
{:error, _} -> :error
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Converts given number to its string representation.
|
||
|
||
## Options
|
||
|
||
* `:scientific` - number converted to scientific notation.
|
||
* `:normal` - number converted without a exponent.
|
||
* `:xsd` - number converted to the [canonical XSD representation](https://www.w3.org/TR/xmlschema-2/#decimal).
|
||
* `:raw` - number converted to its raw, internal format.
|
||
|
||
"""
|
||
@spec to_string(t, :scientific | :normal | :xsd | :raw) :: String.t()
|
||
def to_string(num, type \\ :scientific)
|
||
|
||
def to_string(%Decimal{sign: sign, coef: :qNaN}, _type) do
|
||
if sign == 1, do: "NaN", else: "-NaN"
|
||
end
|
||
|
||
def to_string(%Decimal{sign: sign, coef: :sNaN}, _type) do
|
||
if sign == 1, do: "sNaN", else: "-sNaN"
|
||
end
|
||
|
||
def to_string(%Decimal{sign: sign, coef: :inf}, _type) do
|
||
if sign == 1, do: "Infinity", else: "-Infinity"
|
||
end
|
||
|
||
def to_string(%Decimal{sign: sign, coef: coef, exp: exp}, :normal) do
|
||
list = integer_to_charlist(coef)
|
||
|
||
list =
|
||
if exp >= 0 do
|
||
list ++ :lists.duplicate(exp, ?0)
|
||
else
|
||
diff = length(list) + exp
|
||
|
||
if diff > 0 do
|
||
List.insert_at(list, diff, ?.)
|
||
else
|
||
'0.' ++ :lists.duplicate(-diff, ?0) ++ list
|
||
end
|
||
end
|
||
|
||
list = if sign == -1, do: [?- | list], else: list
|
||
IO.iodata_to_binary(list)
|
||
end
|
||
|
||
def to_string(%Decimal{sign: sign, coef: coef, exp: exp}, :scientific) do
|
||
list = integer_to_charlist(coef)
|
||
length = length(list)
|
||
adjusted = exp + length - 1
|
||
|
||
list =
|
||
cond do
|
||
exp == 0 ->
|
||
list
|
||
|
||
exp < 0 and adjusted >= -6 ->
|
||
abs_exp = Kernel.abs(exp)
|
||
diff = -length + abs_exp + 1
|
||
|
||
if diff > 0 do
|
||
list = :lists.duplicate(diff, ?0) ++ list
|
||
List.insert_at(list, 1, ?.)
|
||
else
|
||
List.insert_at(list, exp - 1, ?.)
|
||
end
|
||
|
||
true ->
|
||
list = if length > 1, do: List.insert_at(list, 1, ?.), else: list
|
||
list = list ++ 'E'
|
||
list = if exp >= 0, do: list ++ '+', else: list
|
||
list ++ integer_to_charlist(adjusted)
|
||
end
|
||
|
||
list = if sign == -1, do: [?- | list], else: list
|
||
IO.iodata_to_binary(list)
|
||
end
|
||
|
||
def to_string(%Decimal{sign: sign, coef: coef, exp: exp}, :raw) do
|
||
str = Integer.to_string(coef)
|
||
str = if sign == -1, do: [?- | str], else: str
|
||
str = if exp != 0, do: [str, "E", Integer.to_string(exp)], else: str
|
||
|
||
IO.iodata_to_binary(str)
|
||
end
|
||
|
||
def to_string(%Decimal{} = decimal, :xsd) do
|
||
decimal |> canonical_xsd() |> to_string(:normal)
|
||
end
|
||
|
||
defp canonical_xsd(%Decimal{coef: 0} = decimal), do: %{decimal | exp: -1}
|
||
|
||
defp canonical_xsd(%Decimal{coef: coef, exp: 0} = decimal),
|
||
do: %{decimal | coef: coef * 10, exp: -1}
|
||
|
||
defp canonical_xsd(%Decimal{coef: coef, exp: exp} = decimal)
|
||
when exp > 0,
|
||
do: canonical_xsd(%{decimal | coef: coef * 10, exp: exp - 1})
|
||
|
||
defp canonical_xsd(%Decimal{coef: coef} = decimal)
|
||
when Kernel.rem(coef, 10) != 0,
|
||
do: decimal
|
||
|
||
defp canonical_xsd(%Decimal{coef: coef, exp: exp} = decimal),
|
||
do: canonical_xsd(%{decimal | coef: Kernel.div(coef, 10), exp: exp + 1})
|
||
|
||
@doc """
|
||
Returns the decimal represented as an integer.
|
||
|
||
Fails when loss of precision will occur.
|
||
"""
|
||
@spec to_integer(t) :: integer
|
||
def to_integer(%Decimal{sign: sign, coef: coef, exp: 0})
|
||
when is_integer(coef),
|
||
do: sign * coef
|
||
|
||
def to_integer(%Decimal{sign: sign, coef: coef, exp: exp})
|
||
when is_integer(coef) and exp > 0,
|
||
do: to_integer(%Decimal{sign: sign, coef: coef * 10, exp: exp - 1})
|
||
|
||
def to_integer(%Decimal{sign: sign, coef: coef, exp: exp})
|
||
when is_integer(coef) and exp < 0 and Kernel.rem(coef, 10) == 0,
|
||
do: to_integer(%Decimal{sign: sign, coef: Kernel.div(coef, 10), exp: exp + 1})
|
||
|
||
@doc """
|
||
Returns the decimal converted to a float.
|
||
|
||
The returned float may have lower precision than the decimal. Fails if
|
||
the decimal cannot be converted to a float.
|
||
"""
|
||
@spec to_float(t) :: float
|
||
def to_float(%Decimal{sign: sign, coef: coef, exp: exp}) when is_integer(coef) do
|
||
# Convert back to float without loss
|
||
# http://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
|
||
{num, den} = ratio(coef, exp)
|
||
|
||
boundary = den <<< 52
|
||
|
||
cond do
|
||
num == 0 ->
|
||
0.0
|
||
|
||
num >= boundary ->
|
||
{den, exp} = scale_down(num, boundary, 52)
|
||
decimal_to_float(sign, num, den, exp)
|
||
|
||
true ->
|
||
{num, exp} = scale_up(num, boundary, 52)
|
||
decimal_to_float(sign, num, den, exp)
|
||
end
|
||
end
|
||
|
||
defp scale_up(num, den, exp) when num >= den, do: {num, exp}
|
||
defp scale_up(num, den, exp), do: scale_up(num <<< 1, den, exp - 1)
|
||
|
||
defp scale_down(num, den, exp) do
|
||
new_den = den <<< 1
|
||
|
||
if num < new_den do
|
||
{den >>> 52, exp}
|
||
else
|
||
scale_down(num, new_den, exp + 1)
|
||
end
|
||
end
|
||
|
||
defp decimal_to_float(sign, num, den, exp) do
|
||
quo = Kernel.div(num, den)
|
||
rem = num - quo * den
|
||
|
||
tmp =
|
||
case den >>> 1 do
|
||
den when rem > den -> quo + 1
|
||
den when rem < den -> quo
|
||
_ when (quo &&& 1) === 1 -> quo + 1
|
||
_ -> quo
|
||
end
|
||
|
||
sign = if sign == -1, do: 1, else: 0
|
||
tmp = tmp - @power_of_2_to_52
|
||
exp = if tmp < @power_of_2_to_52, do: exp, else: exp + 1
|
||
<<tmp::float>> = <<sign::size(1), exp + 1023::size(11), tmp::size(52)>>
|
||
tmp
|
||
end
|
||
|
||
@doc """
|
||
Runs function with given context.
|
||
"""
|
||
@spec with_context(Context.t(), (() -> x)) :: x when x: var
|
||
def with_context(%Context{} = context, fun) when is_function(fun, 0) do
|
||
old = Process.put(@context_key, context)
|
||
|
||
try do
|
||
fun.()
|
||
after
|
||
set_context(old || %Context{})
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Gets the process' context.
|
||
"""
|
||
@spec get_context() :: Context.t()
|
||
def get_context do
|
||
Process.get(@context_key, %Context{})
|
||
end
|
||
|
||
@doc """
|
||
Set the process' context.
|
||
"""
|
||
@spec set_context(Context.t()) :: :ok
|
||
def set_context(%Context{} = context) do
|
||
Process.put(@context_key, context)
|
||
:ok
|
||
end
|
||
|
||
@doc """
|
||
Update the process' context.
|
||
"""
|
||
@spec update_context((Context.t() -> Context.t())) :: :ok
|
||
def update_context(fun) when is_function(fun, 1) do
|
||
get_context() |> fun.() |> set_context
|
||
end
|
||
|
||
## ARITHMETIC ##
|
||
|
||
defp add_align(coef1, exp1, coef2, exp2) when exp1 == exp2, do: {coef1, coef2}
|
||
|
||
defp add_align(coef1, exp1, coef2, exp2) when exp1 > exp2,
|
||
do: {coef1 * pow10(exp1 - exp2), coef2}
|
||
|
||
defp add_align(coef1, exp1, coef2, exp2) when exp1 < exp2,
|
||
do: {coef1, coef2 * pow10(exp2 - exp1)}
|
||
|
||
defp add_sign(sign1, sign2, coef) do
|
||
cond do
|
||
coef > 0 -> 1
|
||
coef < 0 -> -1
|
||
sign1 == -1 and sign2 == -1 -> -1
|
||
sign1 != sign2 and get_context().rounding == :floor -> -1
|
||
true -> 1
|
||
end
|
||
end
|
||
|
||
defp div_adjust(coef1, coef2, adjust) when coef1 < coef2,
|
||
do: div_adjust(coef1 * 10, coef2, adjust + 1)
|
||
|
||
defp div_adjust(coef1, coef2, adjust) when coef1 >= coef2 * 10,
|
||
do: div_adjust(coef1, coef2 * 10, adjust - 1)
|
||
|
||
defp div_adjust(coef1, coef2, adjust), do: {coef1, coef2, adjust}
|
||
|
||
defp div_calc(coef1, coef2, coef, adjust, prec10) do
|
||
cond do
|
||
coef1 >= coef2 ->
|
||
div_calc(coef1 - coef2, coef2, coef + 1, adjust, prec10)
|
||
|
||
coef1 == 0 and adjust >= 0 ->
|
||
{coef, adjust, coef1, []}
|
||
|
||
coef >= prec10 ->
|
||
signals = [:rounded]
|
||
signals = if base10?(coef1), do: signals, else: [:inexact | signals]
|
||
{coef, adjust, coef1, signals}
|
||
|
||
true ->
|
||
div_calc(coef1 * 10, coef2, coef * 10, adjust + 1, prec10)
|
||
end
|
||
end
|
||
|
||
defp div_int_calc(coef1, coef2, coef, adjust, precision) do
|
||
cond do
|
||
coef1 >= coef2 ->
|
||
div_int_calc(coef1 - coef2, coef2, coef + 1, adjust, precision)
|
||
|
||
adjust != precision ->
|
||
div_int_calc(coef1 * 10, coef2, coef * 10, adjust + 1, precision)
|
||
|
||
true ->
|
||
{coef, coef1}
|
||
end
|
||
end
|
||
|
||
defp integer_division(div_sign, coef1, exp1, coef2, exp2) do
|
||
precision = exp1 - exp2
|
||
{coef1, coef2, adjust} = div_adjust(coef1, coef2, 0)
|
||
|
||
{coef, _rem} = div_int_calc(coef1, coef2, 0, adjust, precision)
|
||
|
||
prec10 = pow10(get_context().precision)
|
||
|
||
if coef > prec10 do
|
||
{
|
||
:error,
|
||
:invalid_operation,
|
||
"integer division impossible, quotient too large",
|
||
%Decimal{coef: :NaN}
|
||
}
|
||
else
|
||
{:ok, %Decimal{sign: div_sign, coef: coef, exp: 0}}
|
||
end
|
||
end
|
||
|
||
defp do_reduce(coef, exp) do
|
||
if Kernel.rem(coef, 10) == 0 do
|
||
do_reduce(Kernel.div(coef, 10), exp + 1)
|
||
else
|
||
%Decimal{coef: coef, exp: exp}
|
||
end
|
||
end
|
||
|
||
defp ratio(coef, exp) when exp >= 0, do: {coef * pow10(exp), 1}
|
||
defp ratio(coef, exp) when exp < 0, do: {coef, pow10(-exp)}
|
||
|
||
pow10_max =
|
||
Enum.reduce(0..104, 1, fn int, acc ->
|
||
defp pow10(unquote(int)), do: unquote(acc)
|
||
defp base10?(unquote(acc)), do: true
|
||
acc * 10
|
||
end)
|
||
|
||
defp pow10(num) when num > 104, do: pow10(104) * pow10(num - 104)
|
||
|
||
defp base10?(num) when num > unquote(pow10_max) do
|
||
if Kernel.rem(num, unquote(pow10_max)) == 0 do
|
||
base10?(Kernel.div(num, unquote(pow10_max)))
|
||
else
|
||
false
|
||
end
|
||
end
|
||
|
||
defp base10?(_num), do: false
|
||
|
||
## ROUNDING ##
|
||
|
||
defp do_round(sign, digits, exp, target_exp, rounding) do
|
||
num_digits = length(digits)
|
||
precision = num_digits - (target_exp - exp)
|
||
|
||
cond do
|
||
exp == target_exp ->
|
||
%Decimal{sign: sign, coef: digits_to_integer(digits), exp: exp}
|
||
|
||
exp < target_exp and precision < 0 ->
|
||
zeros = :lists.duplicate(target_exp - exp, ?0)
|
||
digits = zeros ++ digits
|
||
{signif, remain} = :lists.split(1, digits)
|
||
|
||
signif =
|
||
if increment?(rounding, sign, signif, remain),
|
||
do: digits_increment(signif),
|
||
else: signif
|
||
|
||
coef = digits_to_integer(signif)
|
||
%Decimal{sign: sign, coef: coef, exp: target_exp}
|
||
|
||
exp < target_exp and precision >= 0 ->
|
||
{signif, remain} = :lists.split(precision, digits)
|
||
|
||
signif =
|
||
if increment?(rounding, sign, signif, remain),
|
||
do: digits_increment(signif),
|
||
else: signif
|
||
|
||
coef = digits_to_integer(signif)
|
||
%Decimal{sign: sign, coef: coef, exp: target_exp}
|
||
|
||
exp > target_exp ->
|
||
digits = digits ++ Enum.map(1..(exp - target_exp), fn _ -> ?0 end)
|
||
coef = digits_to_integer(digits)
|
||
%Decimal{sign: sign, coef: coef, exp: target_exp}
|
||
end
|
||
end
|
||
|
||
defp digits_to_integer([]), do: 0
|
||
defp digits_to_integer(digits), do: :erlang.list_to_integer(digits)
|
||
|
||
defp precision(%Decimal{coef: :sNaN} = num, _precision, _rounding) do
|
||
{num, []}
|
||
end
|
||
|
||
defp precision(%Decimal{coef: :qNaN} = num, _precision, _rounding) do
|
||
{num, []}
|
||
end
|
||
|
||
defp precision(%Decimal{coef: :inf} = num, _precision, _rounding) do
|
||
{num, []}
|
||
end
|
||
|
||
defp precision(%Decimal{sign: sign, coef: coef, exp: exp} = num, precision, rounding) do
|
||
digits = :erlang.integer_to_list(coef)
|
||
num_digits = length(digits)
|
||
|
||
if num_digits > precision do
|
||
do_precision(sign, digits, num_digits, exp, precision, rounding)
|
||
else
|
||
{num, []}
|
||
end
|
||
end
|
||
|
||
defp do_precision(sign, digits, num_digits, exp, precision, rounding) do
|
||
precision = Kernel.min(num_digits, precision)
|
||
{signif, remain} = :lists.split(precision, digits)
|
||
|
||
signif =
|
||
if increment?(rounding, sign, signif, remain), do: digits_increment(signif), else: signif
|
||
|
||
signals = if any_nonzero(remain), do: [:inexact, :rounded], else: [:rounded]
|
||
|
||
exp = exp + length(remain)
|
||
coef = digits_to_integer(signif)
|
||
dec = %Decimal{sign: sign, coef: coef, exp: exp}
|
||
{dec, signals}
|
||
end
|
||
|
||
defp increment?(_, _, _, []), do: false
|
||
|
||
defp increment?(:down, _, _, _), do: false
|
||
|
||
defp increment?(:up, _, _, _), do: true
|
||
|
||
defp increment?(:ceiling, sign, _, remain), do: sign == 1 and any_nonzero(remain)
|
||
|
||
defp increment?(:floor, sign, _, remain), do: sign == -1 and any_nonzero(remain)
|
||
|
||
defp increment?(:half_up, _, _, [digit | _]), do: digit >= ?5
|
||
|
||
defp increment?(:half_even, _, [], [?5 | rest]), do: any_nonzero(rest)
|
||
|
||
defp increment?(:half_even, _, signif, [?5 | rest]),
|
||
do: any_nonzero(rest) or Kernel.rem(:lists.last(signif), 2) == 1
|
||
|
||
defp increment?(:half_even, _, _, [digit | _]), do: digit > ?5
|
||
|
||
defp increment?(:half_down, _, _, [digit | rest]),
|
||
do: digit > ?5 or (digit == ?5 and any_nonzero(rest))
|
||
|
||
defp any_nonzero(digits), do: :lists.any(fn digit -> digit != ?0 end, digits)
|
||
|
||
defp digits_increment(digits), do: digits_increment(:lists.reverse(digits), [])
|
||
|
||
defp digits_increment([?9 | rest], acc), do: digits_increment(rest, [?0 | acc])
|
||
|
||
defp digits_increment([head | rest], acc), do: :lists.reverse(rest, [head + 1 | acc])
|
||
|
||
defp digits_increment([], acc), do: [?1 | acc]
|
||
|
||
## CONTEXT ##
|
||
|
||
defp context(num, signals \\ []) do
|
||
context = get_context()
|
||
{result, prec_signals} = precision(num, context.precision, context.rounding)
|
||
error(put_uniq(signals, prec_signals), nil, result, context)
|
||
end
|
||
|
||
defp put_uniq(list, elems) when is_list(elems) do
|
||
Enum.reduce(elems, list, &put_uniq(&2, &1))
|
||
end
|
||
|
||
defp put_uniq(list, elem) do
|
||
if elem in list, do: list, else: [elem | list]
|
||
end
|
||
|
||
## PARSING ##
|
||
|
||
defp do_parse("+" <> rest = raw) do
|
||
rest |> String.downcase() |> parse_unsign(raw)
|
||
end
|
||
|
||
defp do_parse("-" <> rest = raw) do
|
||
case rest |> String.downcase() |> parse_unsign(raw) do
|
||
{:ok, num} -> {:ok, %{num | sign: -1}}
|
||
{:error, error} -> {:error, error}
|
||
end
|
||
end
|
||
|
||
defp do_parse(bin) do
|
||
bin |> String.downcase() |> parse_unsign(bin)
|
||
end
|
||
|
||
defp parse_unsign("inf", _) do
|
||
{:ok, %Decimal{coef: :inf}}
|
||
end
|
||
|
||
defp parse_unsign("infinity", _) do
|
||
{:ok, %Decimal{coef: :inf}}
|
||
end
|
||
|
||
defp parse_unsign("snan", _) do
|
||
{:ok, %Decimal{coef: :sNaN}}
|
||
end
|
||
|
||
defp parse_unsign("nan", _) do
|
||
{:ok, %Decimal{coef: :qNaN}}
|
||
end
|
||
|
||
defp parse_unsign(bin, raw) do
|
||
{int, rest} = parse_digits(bin)
|
||
{float, rest} = parse_float(rest)
|
||
{exp, rest} = parse_exp(rest)
|
||
|
||
if rest != "" or (int == [] and float == []) do
|
||
handle_error(
|
||
:invalid_operation,
|
||
"number parsing syntax: " <> raw,
|
||
%Decimal{coef: :NaN},
|
||
nil
|
||
)
|
||
else
|
||
int = if int == [], do: '0', else: int
|
||
exp = if exp == [], do: '0', else: exp
|
||
|
||
number = %Decimal{
|
||
coef: List.to_integer(int ++ float),
|
||
exp: List.to_integer(exp) - length(float)
|
||
}
|
||
|
||
{:ok, number}
|
||
end
|
||
end
|
||
|
||
defp parse_float("." <> rest), do: parse_digits(rest)
|
||
defp parse_float(bin), do: {[], bin}
|
||
|
||
defp parse_exp(<<?e, rest::binary>>) do
|
||
case rest do
|
||
<<sign, rest::binary>> when sign in [?+, ?-] ->
|
||
{digits, rest} = parse_digits(rest)
|
||
{[sign | digits], rest}
|
||
|
||
_ ->
|
||
parse_digits(rest)
|
||
end
|
||
end
|
||
|
||
defp parse_exp(bin) do
|
||
{[], bin}
|
||
end
|
||
|
||
defp parse_digits(bin), do: parse_digits(bin, [])
|
||
|
||
defp parse_digits(<<digit, rest::binary>>, acc) when digit in ?0..?9 do
|
||
parse_digits(rest, [digit | acc])
|
||
end
|
||
|
||
defp parse_digits(rest, acc) do
|
||
{:lists.reverse(acc), rest}
|
||
end
|
||
|
||
# Util
|
||
|
||
defp decimal(%Decimal{} = num), do: num
|
||
defp decimal(num) when is_integer(num), do: new(num)
|
||
defp decimal(num) when is_binary(num), do: new(num)
|
||
|
||
defp decimal(other) when is_float(other) do
|
||
raise ArgumentError,
|
||
"implicit conversion of #{inspect(other)} to Decimal is not allowed. Use Decimal.from_float/1"
|
||
end
|
||
|
||
defp handle_error(signals, reason, result, context) do
|
||
context = context || get_context()
|
||
signals = List.wrap(signals)
|
||
|
||
flags = Enum.reduce(signals, context.flags, &put_uniq(&2, &1))
|
||
set_context(%{context | flags: flags})
|
||
|
||
error_signal = Enum.find(signals, &(&1 in context.traps))
|
||
nan = if error_signal, do: :sNaN, else: :qNaN
|
||
|
||
result = if match?(%Decimal{coef: :NaN}, result), do: %{result | coef: nan}, else: result
|
||
|
||
if error_signal do
|
||
error = [signal: error_signal, reason: reason, result: result]
|
||
{:error, error}
|
||
else
|
||
{:ok, result}
|
||
end
|
||
end
|
||
|
||
defp fix_float_exp(digits) do
|
||
fix_float_exp(digits, [])
|
||
end
|
||
|
||
defp fix_float_exp([?e | rest], [?0 | [?. | result]]) do
|
||
fix_float_exp(rest, [?e | result])
|
||
end
|
||
|
||
defp fix_float_exp([digit | rest], result) do
|
||
fix_float_exp(rest, [digit | result])
|
||
end
|
||
|
||
defp fix_float_exp([], result), do: :lists.reverse(result)
|
||
|
||
if Version.compare(System.version(), "1.3.0") == :lt do
|
||
defp integer_to_charlist(string), do: Integer.to_char_list(string)
|
||
else
|
||
defp integer_to_charlist(string), do: Integer.to_charlist(string)
|
||
end
|
||
end
|
||
|
||
defimpl Inspect, for: Decimal do
|
||
def inspect(dec, _opts) do
|
||
"#Decimal<" <> Decimal.to_string(dec) <> ">"
|
||
end
|
||
end
|
||
|
||
defimpl String.Chars, for: Decimal do
|
||
def to_string(dec) do
|
||
Decimal.to_string(dec)
|
||
end
|
||
end
|