AuroraMeter.Credits.Money (Aurora Meter v1.0.0)

View Source

Conversions between the ledger's integer micro-dollars and the units the rest of the world uses.

AuroraMeter.Credits stores every amount as an integer number of micro-dollars (1 µ$ = 1e-6 USD, so 1_000_000 is one dollar and 10_000 is one cent). Integers keep the ledger exact under concurrency and let a price of a few thousandths of a cent (a token, a request, a byte) be charged without rounding. Convert at the edges:

iex> AuroraMeter.Credits.Money.from_cents(1_235)
12_350_000

iex> AuroraMeter.Credits.Money.to_cents(12_350_000)
1_235

iex> AuroraMeter.Credits.Money.from_decimal(Decimal.new("12.35"))
12_350_000

iex> AuroraMeter.Credits.Money.format(12_350_000)
"$12.35"

iex> AuroraMeter.Credits.Money.format_compact(1_234_000_000)
"$1.2k"

Summary

Types

An amount in micro-dollars.

Functions

Returns amount when it is an integer the ledger can hold, and raises ArgumentError naming the limit when it is not.

Formats micro-dollars as a dollar string with :precision decimals (default 2), rounding half away from zero; negative amounts as "-$1.00".

Formats micro-dollars as a short label for a chart axis or a stat tile: thousands, millions and billions collapse to one decimal ("$1.2k", "$3M"), ordinary amounts render as dollars and cents ("$0.07"), and a sub-cent amount keeps just enough precision to stay non-zero ("$0.000015") rather than rounding away to "$0.00".

Converts cents to micro-dollars.

Converts a Decimal amount of dollars to micro-dollars, rounding half up at the sixth decimal place.

The largest magnitude assert_range!/1 admits.

Converts micro-dollars to whole cents, rounding with :round (default, half away from zero), :floor or :ceil.

Types

micro()

@type micro() :: integer()

An amount in micro-dollars.

Functions

assert_range!(amount)

@spec assert_range!(term()) :: micro()

Returns amount when it is an integer the ledger can hold, and raises ArgumentError naming the limit when it is not.

The limit is 9000000000000000 micro-dollars either side of zero, which is nine billion US dollars. AuroraMeter.Credits's grant/3, hold/4, debit/4, reverse/4, settle/3 and set_low_balance_threshold/2 all call it before any database work, so an amount out of range is an ArgumentError from the function the host called rather than a DBConnection.EncodeError from the driver two layers down.

It is deliberately not called by from_cents/1, to_cents/2 or from_decimal/1: those are pure conversions, a host may legitimately convert a figure it is not about to write, and changing them would change the meaning of code that has nothing to do with the ledger.

Examples

iex> AuroraMeter.Credits.Money.assert_range!(20_000_000)
20_000_000

iex> AuroraMeter.Credits.Money.assert_range!(-20_000_000)
-20_000_000

iex> AuroraMeter.Credits.Money.assert_range!(9_000_000_000_000_001)
** (ArgumentError) amount 9000000000000001 is outside the range AuroraMeter.Credits can hold: at most 9000000000000000 micro-dollars (9,000,000,000 USD) either side of zero

iex> AuroraMeter.Credits.Money.assert_range!(1.5)
** (ArgumentError) amount must be an integer number of micro-dollars, got: 1.5

format(micro, opts \\ [])

@spec format(micro(), [{:precision, 0..6}]) :: String.t()

Formats micro-dollars as a dollar string with :precision decimals (default 2), rounding half away from zero; negative amounts as "-$1.00".

Examples

iex> AuroraMeter.Credits.Money.format(12_350_000)
"$12.35"

iex> AuroraMeter.Credits.Money.format(-1_000_000)
"-$1.00"

iex> AuroraMeter.Credits.Money.format(15, precision: 6)
"$0.000015"

iex> AuroraMeter.Credits.Money.format(1_999_999, precision: 0)
"$2"

format_compact(micro)

@spec format_compact(micro()) :: String.t()

Formats micro-dollars as a short label for a chart axis or a stat tile: thousands, millions and billions collapse to one decimal ("$1.2k", "$3M"), ordinary amounts render as dollars and cents ("$0.07"), and a sub-cent amount keeps just enough precision to stay non-zero ("$0.000015") rather than rounding away to "$0.00".

Use format/2 wherever the exact amount matters; this is for the places where space matters more than the last decimal.

Examples

iex> AuroraMeter.Credits.Money.format_compact(1_234_000_000)
"$1.2k"

iex> AuroraMeter.Credits.Money.format_compact(70_000)
"$0.07"

iex> AuroraMeter.Credits.Money.format_compact(0)
"$0"

iex> AuroraMeter.Credits.Money.format_compact(2_000_000_000)
"$2k"

iex> AuroraMeter.Credits.Money.format_compact(-4_500_000_000_000)
"-$4.5M"

iex> AuroraMeter.Credits.Money.format_compact(15)
"$0.000015"

from_cents(cents)

@spec from_cents(integer()) :: micro()

Converts cents to micro-dollars.

Examples

iex> AuroraMeter.Credits.Money.from_cents(100)
1_000_000

iex> AuroraMeter.Credits.Money.from_cents(-50)
-500_000

from_decimal(dollars)

@spec from_decimal(Decimal.t()) :: micro()

Converts a Decimal amount of dollars to micro-dollars, rounding half up at the sixth decimal place.

Examples

iex> AuroraMeter.Credits.Money.from_decimal(Decimal.new("0.000015"))
15

iex> AuroraMeter.Credits.Money.from_decimal(Decimal.new("-2.5"))
-2_500_000

max_micro()

@spec max_micro() :: pos_integer()

The largest magnitude assert_range!/1 admits.

Examples

iex> AuroraMeter.Credits.Money.max_micro()
9_000_000_000_000_000

to_cents(micro, opts \\ [])

@spec to_cents(micro(), [{:rounding, :round | :floor | :ceil}]) :: integer()

Converts micro-dollars to whole cents, rounding with :round (default, half away from zero), :floor or :ceil.

Examples

iex> AuroraMeter.Credits.Money.to_cents(1_234_999)
123

iex> AuroraMeter.Credits.Money.to_cents(1_235_000)
124

iex> AuroraMeter.Credits.Money.to_cents(1_230_001, rounding: :ceil)
124

iex> AuroraMeter.Credits.Money.to_cents(-1_235_000, rounding: :floor)
-124