Packages
ex_cldr_units
3.3.0
3.20.5
3.20.4
3.20.3
3.20.2
3.20.1
3.20.0
3.19.2
3.19.1
3.19.0
3.18.1
3.18.0
3.17.2
3.17.1
3.17.0
3.16.5
3.16.4
3.16.3
3.16.2
3.16.1
3.16.0
3.15.0
3.14.0
3.13.3
3.13.2
3.13.1
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
3.10.0
3.9.2
3.9.1
3.9.0
3.8.0
3.8.0-rc.2
3.8.0-rc.1
3.8.0-rc.0
3.7.1
3.7.0
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.0
3.4.0-rc.0
3.3.1
3.3.0
3.3.0-rc.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.1
3.0.0
3.0.0-rc.0
2.8.1
2.8.0
2.7.0
2.6.1
2.6.0
2.5.3
2.5.2
2.5.1
2.5.0
2.4.0
2.3.3
2.3.2
retired
2.3.1
2.3.0
2.2.0
2.1.0
2.0.0
1.3.0
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-rc.0
retired
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Unit formatting (volume, area, length, ...), conversion and arithmetic functions based upon the Common Locale Data Repository (CLDR).
Current section
Files
Jump to
Current section
Files
lib/cldr/unit/math.ex
defmodule Cldr.Unit.Math do
@moduledoc """
Simple arithmetic functions for the `Unit.t` type
"""
alias Cldr.Unit
alias Cldr.Unit.Conversion
import Kernel, except: [div: 2, round: 1, trunc: 1]
import Unit, only: [incompatible_units_error: 2]
@doc """
Adds two compatible `%Unit{}` types
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the sum of `unit_1` and the potentially converted
`unit_2` or
* `{:error, {IncompatibleUnitError, message}}`
## Examples
iex> Cldr.Unit.Math.add Cldr.Unit.new!(:foot, 1), Cldr.Unit.new!(:foot, 1)
#Cldr.Unit<:foot, 2>
iex> Cldr.Unit.Math.add Cldr.Unit.new!(:foot, 1), Cldr.Unit.new!(:mile, 1)
#Cldr.Unit<:foot, 5281>
iex> Cldr.Unit.Math.add Cldr.Unit.new!(:foot, 1), Cldr.Unit.new!(:gallon, 1)
{:error, {Cldr.Unit.IncompatibleUnitsError,
"Operations can only be performed between units of the same category. Received #Cldr.Unit<:foot, 1> and #Cldr.Unit<:gallon, 1>"}}
"""
@spec add(Unit.t(), Unit.t()) :: Unit.t() | {:error, {module(), String.t()}}
def add(%Unit{unit: unit, value: value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_1) and is_number(value_2) do
Unit.new!(unit, value_1 + value_2)
end
def add(%Unit{unit: unit, value: %Decimal{} = value_1}, %Unit{
unit: unit,
value: %Decimal{} = value_2
}) do
Unit.new!(unit, Decimal.add(value_1, value_2))
end
def add(%Unit{unit: unit, value: %Decimal{}} = unit_1, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
add(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def add(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Decimal{}} = unit_1)
when is_number(value_2) do
add(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def add(%Unit{unit: unit, value: %Ratio{} = value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
Unit.new!(unit, Ratio.add(value_1, value_2))
end
def add(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Ratio{} = value_1})
when is_number(value_2) do
Unit.new!(unit, Ratio.add(value_1, value_2))
end
def add(%Unit{unit: unit_category_1} = unit_1, %Unit{unit: unit_category_2} = unit_2) do
if Unit.compatible?(unit_category_1, unit_category_2) do
add(unit_1, Conversion.convert!(unit_2, unit_category_1))
else
{:error, incompatible_units_error(unit_1, unit_2)}
end
end
@doc """
Adds two compatible `%Unit{}` types
and raises on error
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the sum of `unit_1` and the potentially converted
`unit_2` or
* Raises an exception
"""
@spec add!(Unit.t(), Unit.t()) :: Unit.t() | no_return()
def add!(unit_1, unit_2) do
case add(unit_1, unit_2) do
{:error, {exception, reason}} -> raise exception, reason
unit -> unit
end
end
@doc """
Subtracts two compatible `%Unit{}` types
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the difference between `unit_1` and the potentially
converted `unit_2`
* `{:error, {IncompatibleUnitError, message}}`
## Examples
iex> Cldr.Unit.sub Cldr.Unit.new!(:kilogram, 5), Cldr.Unit.new!(:pound, 1)
#Cldr.Unit<:kilogram, -81900798833369519 <|> 18014398509481984>
iex> Cldr.Unit.sub Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Cldr.Unit<:pint, -36794683014431043834033898368027039378825884348261 <|> 12746616238742849396626455585282990375683527307233>
iex> Cldr.Unit.sub Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Cldr.Unit<:pint, 4>
"""
@spec sub(Unit.t(), Unit.t()) :: Unit.t() | {:error, {module(), String.t()}}
def sub(%Unit{unit: unit, value: value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_1) and is_number(value_2) do
Unit.new!(unit, value_1 - value_2)
end
def sub(%Unit{unit: unit, value: %Decimal{} = value_1}, %Unit{
unit: unit,
value: %Decimal{} = value_2
}) do
Unit.new!(unit, Decimal.sub(value_1, value_2))
end
def sub(%Unit{unit: unit, value: %Decimal{}} = unit_1, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
sub(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def sub(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Decimal{}} = unit_1)
when is_number(value_2) do
sub(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def sub(%Unit{unit: unit, value: %Ratio{} = value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
Unit.new!(unit, Ratio.sub(value_1, value_2))
end
def sub(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Ratio{} = value_1})
when is_number(value_2) do
Unit.new!(unit, Ratio.sub(value_1, value_2))
end
def sub(%Unit{unit: unit_category_1} = unit_1, %Unit{unit: unit_category_2} = unit_2) do
if Unit.compatible?(unit_category_1, unit_category_2) do
sub(unit_1, Conversion.convert!(unit_2, unit_category_1))
else
{:error, incompatible_units_error(unit_1, unit_2)}
end
end
@doc """
Subtracts two compatible `%Unit{}` types
and raises on error
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the difference between `unit_1` and the potentially
converted `unit_2`
* Raises an exception
"""
@spec sub!(Unit.t(), Unit.t()) :: Unit.t() | no_return()
def sub!(unit_1, unit_2) do
case sub(unit_1, unit_2) do
{:error, {exception, reason}} -> raise exception, reason
unit -> unit
end
end
@doc """
Multiplies two compatible `%Unit{}` types
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the product of `unit_1` and the potentially
converted `unit_2`
* `{:error, {IncompatibleUnitError, message}}`
## Examples
iex> Cldr.Unit.mult Cldr.Unit.new!(:kilogram, 5), Cldr.Unit.new!(:pound, 1)
#Cldr.Unit<:kilogram, 40855968570202005 <|> 18014398509481984>
iex> Cldr.Unit.mult Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Cldr.Unit<:pint, 134691990896416015745491897791939562497958760939520 <|> 12746616238742849396626455585282990375683527307233>
iex> Cldr.Unit.mult Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Cldr.Unit<:pint, 5>
"""
@spec mult(Unit.t(), Unit.t()) :: Unit.t() | {:error, {module(), String.t()}}
def mult(%Unit{unit: unit, value: value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_1) and is_number(value_2) do
Unit.new!(unit, value_1 * value_2)
end
def mult(%Unit{unit: unit, value: %Decimal{} = value_1}, %Unit{
unit: unit,
value: %Decimal{} = value_2
}) do
Unit.new!(unit, Decimal.mult(value_1, value_2))
end
def mult(%Unit{unit: unit, value: %Decimal{}} = unit_1, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
mult(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def mult(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Decimal{}} = unit_1)
when is_number(value_2) do
mult(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def mult(%Unit{unit: unit, value: %Ratio{} = value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
Unit.new!(unit, Ratio.mult(value_1, value_2))
end
def mult(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Ratio{} = value_1})
when is_number(value_2) do
Unit.new!(unit, Ratio.mult(value_1, value_2))
end
def mult(%Unit{unit: unit_category_1} = unit_1, %Unit{unit: unit_category_2} = unit_2) do
if Unit.compatible?(unit_category_1, unit_category_2) do
{:ok, conversion} = Conversion.convert(unit_2, unit_category_1)
mult(unit_1, conversion)
else
{:error, incompatible_units_error(unit_1, unit_2)}
end
end
@doc """
Multiplies two compatible `%Unit{}` types
and raises on error
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the product of `unit_1` and the potentially
converted `unit_2`
* Raises an exception
"""
@spec mult!(Unit.t(), Unit.t()) :: Unit.t() | no_return()
def mult!(unit_1, unit_2) do
case mult(unit_1, unit_2) do
{:error, {exception, reason}} -> raise exception, reason
unit -> unit
end
end
@doc """
Divides one compatible `%Unit{}` type by another
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the dividend of `unit_1` and the potentially
converted `unit_2`
* `{:error, {IncompatibleUnitError, message}}`
## Examples
iex> Cldr.Unit.div Cldr.Unit.new!(:kilogram, 5), Cldr.Unit.new!(:pound, 1)
#Cldr.Unit<:kilogram, 8171193714040401 <|> 90071992547409920>
iex> Cldr.Unit.div Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Cldr.Unit<:pint, 26938398179283203149098379558387912499591752187904 <|> 63733081193714246983132277926414951878417636536165>
iex> Cldr.Unit.div Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Cldr.Unit<:pint, 5.0>
"""
@spec div(Unit.t(), Unit.t()) :: Unit.t() | {:error, {module(), String.t()}}
def div(%Unit{unit: unit, value: value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_1) and is_number(value_2) do
Unit.new!(unit, value_1 / value_2)
end
def div(%Unit{unit: unit, value: %Decimal{} = value_1}, %Unit{
unit: unit,
value: %Decimal{} = value_2
}) do
Unit.new!(unit, Decimal.div(value_1, value_2))
end
def div(%Unit{unit: unit, value: %Decimal{}} = unit_1, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
div(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def div(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Decimal{}} = unit_1)
when is_number(value_2) do
div(unit_1, Unit.new!(unit, Decimal.new(value_2)))
end
def div(%Unit{unit: unit, value: %Ratio{} = value_1}, %Unit{unit: unit, value: value_2})
when is_number(value_2) do
Unit.new!(unit, Ratio.div(value_1, value_2))
end
def div(%Unit{unit: unit, value: value_2}, %Unit{unit: unit, value: %Ratio{} = value_1})
when is_number(value_2) do
Unit.new!(unit, Ratio.div(value_1, value_2))
end
def div(%Unit{unit: unit_category_1} = unit_1, %Unit{unit: unit_category_2} = unit_2) do
if Unit.compatible?(unit_category_1, unit_category_2) do
div(unit_1, Conversion.convert!(unit_2, unit_category_1))
else
{:error, incompatible_units_error(unit_1, unit_2)}
end
end
@doc """
Divides one compatible `%Unit{}` type by another
and raises on error
## Options
* `unit_1` and `unit_2` are compatible Units
returned by `Cldr.Unit.new/2`
## Returns
* A `%Unit{}` of the same type as `unit_1` with a value
that is the dividend of `unit_1` and the potentially
converted `unit_2`
* Raises an exception
"""
@spec div!(Unit.t(), Unit.t()) :: Unit.t() | no_return()
def div!(unit_1, unit_2) do
case div(unit_1, unit_2) do
{:error, {exception, reason}} -> raise exception, reason
unit -> unit
end
end
@doc """
Rounds the value of a unit.
## Options
* `unit` is any unit returned by `Cldr.Unit.new/2`
* `places` is the number of decimal places to round to. The default is `0`.
* `mode` is the rounding mode to be applied. The default is `:half_up`.
## Returns
* A `%Unit{}` of the same type as `unit` with a value
that is rounded to the specified number of decimal places
## Rounding modes
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)
## Examples
iex> Cldr.Unit.round Cldr.Unit.new!(:yard, 1031.61), 1
#Cldr.Unit<:yard, 1031.6>
iex> Cldr.Unit.round Cldr.Unit.new!(:yard, 1031.61), 2
#Cldr.Unit<:yard, 1031.61>
iex> Cldr.Unit.round Cldr.Unit.new!(:yard, 1031.61), 1, :up
#Cldr.Unit<:yard, 1031.7>
"""
@spec round(
unit :: Unit.t(),
places :: non_neg_integer,
mode :: :down | :up | :ceiling | :floor | :half_even | :half_up | :half_down
) :: Unit.t()
def round(unit, places \\ 0, mode \\ :half_up)
def round(%Unit{value: %Ratio{} = value} = unit, places, mode) do
value = Ratio.to_float(value)
round(%{unit | value: value}, places, mode)
end
def round(%Unit{unit: unit, value: value}, places, mode) do
rounded_value = Cldr.Math.round(value, places, mode)
Unit.new!(unit, rounded_value)
end
@doc """
Truncates a unit's value
"""
def trunc(%Unit{value: %Ratio{} = value} = unit) do
value = Ratio.to_float(value)
trunc(%{unit | value: value})
end
def trunc(%Unit{value: value} = unit) when is_float(value) do
%{unit | value: Kernel.trunc(value)}
end
def trunc(%Unit{value: value} = unit) when is_integer(value) do
unit
end
def trunc(%Unit{value: %Decimal{} = value} = unit) do
%{unit | value: Decimal.round(value, 0, :floor)}
end
@doc """
Compare two units, converting to a common unit
type if required.
If conversion is performed, the results are both
rounded to a single decimal place before
comparison.
Returns `:gt`, `:lt`, or `:eq`.
## Example
iex> x = Cldr.Unit.new!(:kilometer, 1)
iex> y = Cldr.Unit.new!(:meter, 1000)
iex> Cldr.Unit.Math.compare x, y
:eq
"""
def compare(
%Unit{unit: unit, value: %Decimal{}} = unit_1,
%Unit{unit: unit, value: %Decimal{}} = unit_2
) do
Cldr.Decimal.compare(unit_1.value, unit_2.value)
end
def compare(%Unit{value: %Decimal{}} = unit_1, %Unit{value: %Decimal{}} = unit_2) do
unit_2 = Unit.Conversion.convert!(unit_2, unit_1.unit)
compare(unit_1, unit_2)
end
def compare(%Unit{unit: unit} = unit_1, %Unit{unit: unit} = unit_2) do
cond do
unit_1.value == unit_2.value -> :eq
unit_1.value > unit_2.value -> :gt
unit_1.value < unit_2.value -> :lt
end
end
def compare(%Unit{} = unit_1, %Unit{} = unit_2) do
unit_1 =
unit_1
|> round(1, :half_even)
unit_2 =
unit_2
|> Unit.Conversion.convert!(unit_1.unit)
|> round(1, :half_even)
compare(unit_1, unit_2)
end
@deprecated "Please use Cldr.Unit.Math.compare/2"
def cmp(unit_1, unit_2) do
compare(unit_1, unit_2)
end
end