Money v2.2.2 Money View Source
Money implements a set of functions to store, retrieve, convert and perform
arithmetic on a Money.t
type that is composed of a currency code and
a decimal currency amount.
Money is very opinionated in the interests of serving as a dependable library that can underpin accounting and financial applications.
This opinion expressed by ensuring that:
Money must always have both a amount and a currency code.
The currency code must always be valid.
Money arithmetic can only be performed when both operands are of the same currency.
Money amounts are represented as a
Decimal
.Money is serialised to the database as a custom Postgres composite type that includes both the amount and the currency. Therefore for Ecto serialization Postgres is assumed as the data store. Serialization is entirely optional and Ecto is not a package dependency.
All arithmetic functions work in fixed point decimal. No rounding occurs automatically (unless expressly called out for a function).
Explicit rounding obeys the rounding rules for a given currency. The rounding rules are defined by the Unicode consortium in its CLDR repository as implemented by the hex package
ex_cldr
. These rules define the number of fractional digits for a currency and the rounding increment where appropriate.
Link to this section Summary
Functions
Add two Money
values
Add two Money
values and raise on error
Compares two Money
values 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
Compares two Money
values numerically and raises on error
Compares two Money
values numerically. If the first number is greater
than the second #Integer<1> is returned, if less than Integer<-1> is
returned. Otherwise, if both numbers are equal Integer<0> is returned
Compares two Money
values numerically and raises on error
Divide a Money
value by a number
Divide a Money
value by a number and raise on error
Returns a boolean indicating if two Money
values are equal
Returns a %Money{} struct from a currency code and a float amount, or
an error tuple of the form {:error, {exception, message}}
Returns a %Money{} struct from a currency code and a float amount, or raises an exception if the currency code is invalid
Convert an integer representation of money into a Money
struct
Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. The format of the argument is a 2-tuple where
Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. Raises an exception if the currency code is invalid
Multiply a Money
value by a number
Multiply a Money
value by a number and raise on error
Returns a %Money{} struct from a currency code and a currency amount or
an error tuple of the form {:error, {exception, message}}
Returns a %Money{} struct from a currency code and a currency amount. Raises an exception if the current code is invalid
Calls Decimal.reduce/1
on the given %Money{}
Round a Money
value into the acceptable range for the requested currency
Split a Money
value into a number of parts maintaining the currency’s
precision and rounding and ensuring that the parts sum to the original
amount
Subtract one Money
value struct from another
Subtract one Money
value struct from another and raise on error
Convert money
from one currency to another
Convert money
from one currency to another and raises on error
Returns a tuple comprising the currency code, integer amount, exponent and remainder
Returns a formatted string representation of a Money{}
Returns a formatted string representation of a Money{}
or raises if
there is an error
Link to this section Types
Money is composed of an atom representation of an ISO4217 currency code and
a Decimal
representation of an amount.
Link to this section Functions
Add two Money
values.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
{:ok, money}
or{:error, reason}
Example
iex> Money.add Money.new(:USD, 200), Money.new(:USD, 100)
{:ok, Money.new(:USD, 300)}
iex> Money.add Money.new(:USD, 200), Money.new(:AUD, 100)
{:error, {ArgumentError, "Cannot add monies with different currencies. " <>
"Received :USD and :AUD."}}
Add two Money
values and raise on error.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
{:ok, money}
orraises an exception
Examples
iex> Money.add! Money.new(:USD, 200), Money.new(:USD, 100)
#Money<:USD, 300>
Money.add! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot add two %Money{} with different currencies. Received :USD and :CAD.
cmp(money_1 :: Money.t(), money_2 :: Money.t()) :: :gt | :eq | :lt | {:error, {Exception.t(), String.t()}}
Compares two Money
values 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.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
:gt
|:eq
|:lt
or{:error, {Exception.t, String.t}}
Examples
iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 100)
:gt
iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 200)
:eq
iex> Money.cmp Money.new(:USD, 200), Money.new(:USD, 500)
:lt
iex> Money.cmp Money.new(:USD, 200), Money.new(:CAD, 500)
{:error,
{ArgumentError,
"Cannot compare monies with different currencies. Received :USD and :CAD."}}
Compares two Money
values numerically and raises on error.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
:gt
|:eq
|:lt
orraises an exception
Examples
Money.cmp! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot compare monies with different currencies. Received :USD and :CAD.
compare(money_1 :: Money.t(), money_2 :: Money.t()) :: -1 | 0 | 1 | {:error, {Exception.t(), String.t()}}
Compares two Money
values numerically. If the first number is greater
than the second #Integer<1> is returned, if less than Integer<-1> is
returned. Otherwise, if both numbers are equal Integer<0> is returned.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
-1
|0
|1
or{:error, {Exception.t, String.t}}
Examples
iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 100)
1
iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 200)
0
iex> Money.compare Money.new(:USD, 200), Money.new(:USD, 500)
-1
iex> Money.compare Money.new(:USD, 200), Money.new(:CAD, 500)
{:error,
{ArgumentError,
"Cannot compare monies with different currencies. Received :USD and :CAD."}}
Compares two Money
values numerically and raises on error.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
-1
|0
|1
orraises an exception
Examples
Money.compare! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot compare monies with different currencies. Received :USD and :CAD.
div(Money.t(), Cldr.Math.number_or_decimal()) :: Money.t()
Divide a Money
value by a number.
Options
money
is any validMoney.t
types returned byMoney.new/2
number
is an integer, float orDecimal.t
Note that dividing one %Money{} by another is not supported.
Returns
{:ok, money}
or{:error, reason}
Example
iex> Money.div Money.new(:USD, 200), 2
{:ok, Money.new(:USD, 100)}
iex> Money.div(Money.new(:USD, 200), "xx")
{:error, {ArgumentError, "Cannot divide money by \"xx\""}}
Divide a Money
value by a number and raise on error.
Options
money
is any validMoney.t
types returned byMoney.new/2
number
is an integer, float orDecimal.t
Returns
a
Money.t
struct orraises an exception
Examples
iex> Money.div Money.new(:USD, 200), 2
{:ok, Money.new(:USD, 100)}
Money.div(Money.new(:USD, 200), "xx")
** (ArgumentError) "Cannot divide money by \"xx\""]}}
Returns a boolean indicating if two Money
values are equal
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
true
orfalse
Example
iex> Money.equal? Money.new(:USD, 200), Money.new(:USD, 200)
true
iex> Money.equal? Money.new(:USD, 200), Money.new(:USD, 100)
false
from_float(currency_code(), float()) :: Money.t() | {:error, {Exception.t(), String.t()}}
Returns a %Money{} struct from a currency code and a float amount, or
an error tuple of the form {:error, {exception, message}}
.
Floats are fraught with danger in computer arithmetic due to the unexpected loss of precision during rounding. The IEEE754 standard indicates that a number with a precision of 16 digits should round-trip convert without loss of fidelity. This function supports numbers with a precision up to 15 digits and will error if the provided amount is outside that range.
Note that Money
cannot detect lack of precision or rounding errors
introduced upstream. This function therefore should be used with
great care and its use should be considered potentially harmful.
Options
currency_code
is an ISO4217 three-character upcased binary or atomamount
is a float
Examples
iex> Money.from_float 1.23456, :USD
#Money<:USD, 1.23456>
iex> Money.from_float 1.234567890987656, :USD
{:error,
{Money.InvalidAmountError,
"The precision of the float 1.234567890987656 is " <>
"greater than 15 which could lead to unexpected results. " <>
"Reduce the precision or call Money.new/2 with a Decimal or String amount"}}
from_float!(currency_code(), float()) :: Money.t() | no_return()
Returns a %Money{} struct from a currency code and a float amount, or raises an exception if the currency code is invalid.
See Money.from_float/2
for further information.
Note that Money
cannot detect lack of precision or rounding errors
introduced upstream. This function therefore should be used with
great care and its use should be considered potentially harmful.
Options
currency_code
is an ISO4217 three-character upcased binary or atomamount
is a float
Examples
iex> Money.from_float!(:USD, 1.234)
#Money<:USD, 1.234>
Money.from_float!(:USD, 1.234567890987654)
#=> ** (Money.InvalidAmountError) The precision of the float 1.234567890987654 is greater than 15 which could lead to unexpected results. Reduce the precision or call Money.new/2 with a Decimal or String amount
(ex_money) lib/money.ex:293: Money.from_float!/2
Convert an integer representation of money into a Money
struct.
This is the inverse operation of Money.to_integer_exp/1
. Note
that the ISO definition of currency digits (subunit) is always
used. This is, in some cases like the Colombian Peso (COP)
different to the CLDR definition.
Options
integer
is an integer representation of a mooney item including any decimal digits. ie. 20000 would interpreted to mean $200.00currency
is the currency code for theinteger
. The assumed decimal places is derived from the currency code.
Returns
A
Money
struct or{:error, {Cldr.UnknownCurrencyError, message}}
Examples
iex> Money.from_integer(20000, :USD)
#Money<:USD, 200.00>
iex> Money.from_integer(200, :JPY)
#Money<:JPY, 200>
iex> Money.from_integer(20012, :USD)
#Money<:USD, 200.12>
iex> Money.from_integer(20012, :COP)
#Money<:COP, 200.12>
Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. The format of the argument is a 2-tuple where:
Options
currency_code
is an ISO4217 three-character upcased binaryamount
is an integer or Decimal
This function is typically called from Ecto when it’s loading a %Money{}
struct from the database.
Example
iex> Money.from_tuple({"USD", 100})
#Money<:USD, 100>
iex> Money.from_tuple({100, "USD"})
#Money<:USD, 100>
Returns a %Money{} struct from a tuple consistenting of a currency code and a currency amount. Raises an exception if the currency code is invalid.
Options
currency_code
is an ISO4217 three-character upcased binaryamount
is an integer, float or Decimal
This function is typically called from Ecto when it’s loading a %Money{} struct from the database.
Example
iex> Money.from_tuple!({"USD", 100})
#Money<:USD, 100>
Money.from_tuple!({"NO!", 100})
** (Money.UnknownCurrencyError) Currency "NO!" is not known
(ex_money) lib/money.ex:130: Money.new!/1
mult(Money.t(), Cldr.Math.number_or_decimal()) :: Money.t()
Multiply a Money
value by a number.
Options
money
is any validMoney.t
type returned byMoney.new/2
number
is an integer, float orDecimal.t
Note that multipling one %Money{} by another is not supported.
Returns
{:ok, money}
or{:error, reason}
Example
iex> Money.mult(Money.new(:USD, 200), 2)
{:ok, Money.new(:USD, 400)}
iex> Money.mult(Money.new(:USD, 200), "xx")
{:error, {ArgumentError, "Cannot multiply money by \"xx\""}}
mult!(Money.t(), Cldr.Math.number_or_decimal()) :: Money.t() | none()
Multiply a Money
value by a number and raise on error.
Options
money
is any validMoney.t
types returned byMoney.new/2
number
is an integer, float orDecimal.t
Returns
a
Money.t
orraises an exception
Examples
iex> Money.mult!(Money.new(:USD, 200), 2)
#Money<:USD, 400>
Money.mult!(Money.new(:USD, 200), :invalid)
** (ArgumentError) Cannot multiply money by :invalid
Returns a %Money{} struct from a currency code and a currency amount or
an error tuple of the form {:error, {exception, message}}
.
Options
currency_code
is an ISO4217 three-character upcased binary or atomamount
is an integer, string or Decimal
Note that the currency_code
and amount
arguments can be supplied in
either order,
Examples
iex> Money.new(:USD, 100)
#Money<:USD, 100>
iex> Money.new(100, :USD)
#Money<:USD, 100>
iex> Money.new("USD", 100)
#Money<:USD, 100>
iex> Money.new("thb", 500)
#Money<:THB, 500>
iex> Money.new("EUR", Decimal.new(100))
#Money<:EUR, 100>
iex> Money.new(:EUR, "100.30")
#Money<:EUR, 100.30>
iex> Money.new(:XYZZ, 100)
{:error, {Money.UnknownCurrencyError, "The currency :XYZZ is invalid"}}
iex(1)> Money.new 123.445, :USD
{:error,
{Money.InvalidAmountError,
"Float amounts are not supported in new/2 due to potenial " <>
"rounding and precision issues. If absolutely required, " <>
"use Money.from_float/2"}}
Returns a %Money{} struct from a currency code and a currency amount. Raises an exception if the current code is invalid.
Options
currency_code
is an ISO4217 three-character upcased binary or atomamount
is an integer, float or Decimal
Examples
Money.new!(:XYZZ, 100)
** (Money.UnknownCurrencyError) Currency :XYZZ is not known
(ex_money) lib/money.ex:177: Money.new!/2
Calls Decimal.reduce/1
on the given %Money{}
This will reduce the coefficient and exponent of the
decimal amount in a standard way that may aid in
native comparison of %Money{}
items.
Example
iex> x = %Money{currency: :USD, amount: %Decimal{sign: 1, coef: 42, exp: 0}}
#Money<:USD, 42>
iex> y = %Money{currency: :USD, amount: %Decimal{sign: 1, coef: 4200000000, exp: -8}}
#Money<:USD, 42.00000000>
iex> x == y
false
iex> y = Money.reduce(x)
#Money<:USD, 42>
iex> x == y
true
Round a Money
value into the acceptable range for the requested currency.
Arguments
money
is a%Money{}
structopts
is a keyword list of options
Options
:rounding_mode
that defines how the number will be rounded. SeeDecimal.Context
. The default is:half_even
which is also known as “banker’s rounding”:currency_digits
which determines the rounding increment. The valid options are:cash
,:accounting
and:iso
. The default is:iso
. The rounding increment applies to currencies such as :AUD and :CHF which have an accounting increment of 0.01 but a minimum cash increment of 0.05.
Notes
There are two kinds of rounding applied:
Round to the appropriate number of fractional digits
Apply an appropriate rounding increment. Most currencies round to the same precision as the number of decimal digits, but some such as :AUD and :CHF round to a minimum such as 0.05 when its a cash amount.
Examples
iex> Money.round Money.new("123.73", :CHF), currency_digits: :cash
#Money<:CHF, 123.75>
iex> Money.round Money.new("123.7456", :CHF)
#Money<:CHF, 123.75>
Money.round Money.new("123.7456", :JPY)
#Money<:JPY, 124>
split(Money.t(), non_neg_integer()) :: {Money.t(), Money.t()}
Split a Money
value into a number of parts maintaining the currency’s
precision and rounding and ensuring that the parts sum to the original
amount.
Options
money
is a%Money{}
structparts
is an integer number of parts into which themoney
is split
Returns a tuple {dividend, remainder}
as the function result
derived as follows:
Round the money amount to the required currency precision using
Money.round/1
Divide the result of step 1 by the integer divisor
Round the result of the division to the precision of the currency using
Money.round/1
Return two numbers: the result of the division and any remainder that could not be applied given the precision of the currency.
Examples
Money.split Money.new(123.5, :JPY), 3
{¥41, ¥1}
Money.split Money.new(123.4, :JPY), 3
{¥41, ¥0}
Money.split Money.new(123.7, :USD), 9
{$13.74, $0.04}
sub(money_1 :: Money.t(), money_2 :: Money.t()) :: {:ok, Money.t()} | {:error, {Exception.t(), String.t()}}
Subtract one Money
value struct from another.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
{:ok, money}
or{:error, reason}
Example
iex> Money.sub Money.new(:USD, 200), Money.new(:USD, 100)
{:ok, Money.new(:USD, 100)}
Subtract one Money
value struct from another and raise on error.
Returns either {:ok, money}
or {:error, reason}
.
Options
money_1
andmoney_2
are any validMoney.t
types returned byMoney.new/2
Returns
a
Money.t
struct orraises an exception
Examples
iex> Money.sub! Money.new(:USD, 200), Money.new(:USD, 100)
#Money<:USD, 100>
Money.sub! Money.new(:USD, 200), Money.new(:CAD, 500)
** (ArgumentError) Cannot subtract monies with different currencies. Received :USD and :CAD.
to_currency(Money.t(), Money.currency_code(), Map.t()) :: {:ok, Map.t()} | {:error, {Exception.t(), String.t()}}
Convert money
from one currency to another.
Options
money
is anyMoney.t
struct returned byCldr.Currency.new/2
to_currency
is a valid currency code into which themoney
is convertedrates
is aMap
of currency rates where the map key is an upcased atom or string and the value is a Decimal conversion factor. The default is the latest available exchange rates returned fromMoney.ExchangeRates.latest_rates()
Examples
Money.to_currency(Money.new(:USD, 100), :AUD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)})
{:ok, #Money<:AUD, 73.4500>}
Money.to_currency(Money.new("USD", 100), "AUD", %{"USD" => Decimal.new(1), "AUD" => Decimal.new(0.7345)})
{:ok, #Money<:AUD, 73.4500>}
iex> Money.to_currency Money.new(:USD, 100) , :AUDD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
{:error, {Cldr.UnknownCurrencyError, "The currency :AUDD is invalid"}}
iex> Money.to_currency Money.new(:USD, 100) , :CHF, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
{:error, {Money.ExchangeRateError, "No exchange rate is available for currency :CHF"}}
Convert money
from one currency to another and raises on error
Options
money
is anyMoney.t
struct returned byCldr.Currency.new/2
to_currency
is a valid currency code into which themoney
is convertedrates
is aMap
of currency rates where the map key is an upcased atom or string and the value is a Decimal conversion factor. The default is the latest available exchange rates returned fromMoney.ExchangeRates.latest_rates()
Examples
iex> Money.to_currency! Money.new(:USD, 100) , :AUD, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
#Money<:AUD, 73.4500>
iex> Money.to_currency! Money.new("USD", 100) , "AUD", %{"USD" => Decimal.new(1), "AUD" => Decimal.new(0.7345)}
#Money<:AUD, 73.4500>
Money.to_currency! Money.new(:USD, 100) , :ZZZ, %{USD: Decimal.new(1), AUD: Decimal.new(0.7345)}
** (Cldr.UnknownCurrencyError) Currency :ZZZ is not known
Returns the amount part of a Money
type as a Decimal
Options
money
is any validMoney.t
type returned byMoney.new/2
Returns
- a
Decimal.t
Example
iex> m = Money.new("USD", 100)
iex> Money.to_decimal(m)
#Decimal<100>
Returns a tuple comprising the currency code, integer amount, exponent and remainder
Some services require submission of money items as an integer with an implied exponent that is appropriate to the currency.
Rather than return only the integer, Money.to_integer_exp
returns the currency code, integer, exponent and remainder.
The remainder is included because to return an integer
money with an implied exponent the Money
has to be rounded
potentially leaving a remainder.
Options
money
is anyMoney.t
struct returned byCldr.Currency.new/2
Notes
- Since the returned integer is expected to have the implied fractional
digits the
Money
needs to be rounded which is what this function does.
Example
iex> m = Money.new(:USD, "200.012356")
#Money<:USD, 200.012356>
iex> Money.to_integer_exp(m)
{:USD, 20001, -2, Money.new(:USD, "0.002356")}
iex> m = Money.new(:USD, "200.00")
#Money<:USD, 200.00>
iex> Money.to_integer_exp(m)
{:USD, 20000, -2, Money.new(:USD, "0.00")}
Returns a formatted string representation of a Money{}
.
Formatting is performed according to the rules defined by CLDR. See
Cldr.Number.to_string/2
for formatting options. The default is to format
as a currency which applies the appropriate rounding and fractional digits
for the currency.
Options
money_1
is any validMoney.t
type returned byMoney.new/2
Returns
{:ok, string}
or{:error, reason}
Examples
iex> Money.to_string Money.new(:USD, 1234)
{:ok, "$1,234.00"}
iex> Money.to_string Money.new(:JPY, 1234)
{:ok, "¥1,234"}
iex> Money.to_string Money.new(:THB, 1234)
{:ok, "THB1,234.00"}
iex> Money.to_string Money.new(:USD, 1234), format: :long
{:ok, "1,234 US dollars"}
Returns a formatted string representation of a Money{}
or raises if
there is an error.
Formatting is performed according to the rules defined by CLDR. See
Cldr.Number.to_string!/2
for formatting options. The default is to format
as a currency which applies the appropriate rounding and fractional digits
for the currency.
Examples
iex> Money.to_string! Money.new(:USD, 1234)
"$1,234.00"
iex> Money.to_string! Money.new(:JPY, 1234)
"¥1,234"
iex> Money.to_string! Money.new(:THB, 1234)
"THB1,234.00"
iex> Money.to_string! Money.new(:USD, 1234), format: :long
"1,234 US dollars"