Current section

Files

Jump to
ex_cldr_units lib cldr math.ex
Raw

lib/cldr/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]
import Unit, only: [incompatible_unit_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 time 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)
#Unit<:foot, 2>
iex> Cldr.Unit.Math.add Cldr.Unit.new!(:foot, 1), Cldr.Unit.new!(:mile, 1)
#Unit<:foot, 5280.945925937846>
iex> Cldr.Unit.Math.add Cldr.Unit.new!(:foot, 1), Cldr.Unit.new!(:gallon, 1)
{:error, {Cldr.Unit.IncompatibleUnitError,
"Operations can only be performed between units of the same type. Received #Unit<:foot, 1> and #Unit<:gallon, 1>"}}
"""
@spec add(unit_1 :: Unit.t, unit_2 :: Unit.t) ::
Unit.t | {:error, {Unit.IncompatibleUnitError, 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_type_1} = unit_1, %Unit{unit: unit_type_2} = unit_2) do
if Unit.compatible?(unit_type_1, unit_type_2) do
add(unit_1, Conversion.convert(unit_2, unit_type_1))
else
{:error, incompatible_unit_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 time 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_1 :: Unit.t, unit_2 :: 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 time 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)
#Unit<:kilogram, 4.54640709056436>
iex> Cldr.Unit.sub Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Unit<:pint, 2.88662>
iex> Cldr.Unit.sub Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Unit<:pint, 4>
"""
@spec sub(unit_1 :: Unit.t, unit_2 :: Unit.t) ::
Unit.t | {:error, {Unit.IncompatibleUnitError, 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_type_1} = unit_1, %Unit{unit: unit_type_2} = unit_2) do
if Unit.compatible?(unit_type_1, unit_type_2) do
sub(unit_1, Conversion.convert(unit_2, unit_type_1))
else
{:error, incompatible_unit_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 time 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_1 :: Unit.t, unit_2 :: 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 time 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)
#Unit<:kilogram, 2.2679645471781984>
iex> Cldr.Unit.mult Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Unit<:pint, 10.566899999999999>
iex> Cldr.Unit.mult Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Unit<:pint, 5>
"""
@spec mult(unit_1 :: Unit.t, unit_2 :: Unit.t) ::
Unit.t | {:error, {Unit.IncompatibleUnitError, 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_type_1} = unit_1, %Unit{unit: unit_type_2} = unit_2) do
if Unit.compatible?(unit_type_1, unit_type_2) do
mult(unit_1, Conversion.convert(unit_2, unit_type_1))
else
{:error, incompatible_unit_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 time 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_1 :: Unit.t, unit_2 :: 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 time 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)
#Unit<:kilogram, 11.023100000000001>
iex> Cldr.Unit.div Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:liter, 1)
#Unit<:pint, 2.365878355998448>
iex> Cldr.Unit.div Cldr.Unit.new!(:pint, 5), Cldr.Unit.new!(:pint, 1)
#Unit<:pint, 5.0>
"""
@spec div(unit_1 :: Unit.t, unit_2 :: Unit.t) ::
Unit.t | {:error, {Unit.IncompatibleUnitError, 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_type_1} = unit_1, %Unit{unit: unit_type_2} = unit_2) do
if Unit.compatible?(unit_type_1, unit_type_2) do
div(unit_1, Conversion.convert(unit_2, unit_type_1))
else
{:error, incompatible_unit_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 time 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_1 :: Unit.t, unit_2 :: 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
end