Number formatting and parsing utilities for PhoenixKit.
Formatting: thousand separators, abbreviations, percentages. Parsing:
parse_decimal/2 turns what a person typed into a form field ("2,5",
"1 234.56") into a Decimal — pair it with
PhoenixKitWeb.Components.Core.DecimalInput.decimal_input/1.
Summary
Functions
Formats a number with thousand separators.
Renders a number the way decimal_input/1 shows it: a normalized plain
string (2.5, 1000, never 1E+3 or 2.500), "" for nil, and a
binary unchanged — so the raw text a person typed round-trips through a
re-render exactly as typed.
Formats a number as a percentage.
Formats a number with abbreviations (K, M, B).
Parses a number a person typed into a form field — the counterpart of
PhoenixKitWeb.Components.Core.DecimalInput.decimal_input/1.
Same as parse_decimal/2, returning the Decimal or raising
ArgumentError with the reason.
Types
Functions
Formats a number with thousand separators.
Examples
iex> PhoenixKit.Utils.Number.format(1234567)
"1,234,567"
iex> PhoenixKit.Utils.Number.format(0)
"0"
iex> PhoenixKit.Utils.Number.format(nil)
"0"
Renders a number the way decimal_input/1 shows it: a normalized plain
string (2.5, 1000, never 1E+3 or 2.500), "" for nil, and a
binary unchanged — so the raw text a person typed round-trips through a
re-render exactly as typed.
Examples
iex> PhoenixKit.Utils.Number.format_decimal(Decimal.new("2.500"))
"2.5"
iex> PhoenixKit.Utils.Number.format_decimal(nil)
""
iex> PhoenixKit.Utils.Number.format_decimal("2,5")
"2,5"
Formats a number as a percentage.
Examples
iex> PhoenixKit.Utils.Number.format_percentage(95.5)
"95.5%"
iex> PhoenixKit.Utils.Number.format_percentage(100)
"100%"
iex> PhoenixKit.Utils.Number.format_percentage(nil)
"0%"
Formats a number with abbreviations (K, M, B).
Examples
iex> PhoenixKit.Utils.Number.format_short(1_234_567)
"1.2M"
iex> PhoenixKit.Utils.Number.format_short(5_432)
"5.4K"
iex> PhoenixKit.Utils.Number.format_short(123)
"123"
@spec parse_decimal(term(), keyword()) :: {:ok, Decimal.t()} | {:error, parse_error()}
Parses a number a person typed into a form field — the counterpart of
PhoenixKitWeb.Components.Core.DecimalInput.decimal_input/1.
Keyboards in most of Europe produce a decimal COMMA, browsers and
Decimal.parse/1 want a dot, and Decimal.parse/1 alone returns
whatever prefix it could read ("2,5" silently became 2). This
function takes the text as a person meant it and returns a normalized
Decimal or a reason:
- a dot or a comma is the decimal point (
"2.5","2,5",",5"); - spaces (no-break, thin and narrow no-break spaces too) around the
number are ignored; inside it they are thousands grouping of the
integer part and are dropped (
"1 234,56") — like the separators below, only between 3-digit groups, so"12 34"and"1,5 25"are:invalidrather than silently merged; tabs and line breaks are not spaces — a value pasted with one is:invalid; - with both a dot and a comma present, the LAST one is the decimal
point and the other is grouping (
"1.234,56","1,234.56"); - one kind repeated is grouping (
"1,234,567"); - an optional leading sign; nothing else — exponents (
"1e9"),NaN,Infinity, hex, stray letters are all{:error, :invalid}; - blank (or
nil) is{:error, :empty}, so a caller can tell "left empty" from "typed garbage"; - more than 64 bytes is
{:error, :invalid}without further inspection; - integers, floats and decimals pass straight through as a
Decimal.
The result has no trailing fraction zeros ("2.500" → 2.5) and no
exponent ("10" stays 10, never 1E+1), so it compares with ==
against Decimal.new/1 of the same text and prints plainly everywhere;
a zero is always unsigned — "-0" → 0.
Options
:min/:max— any number shape (0,"0.25",Decimal); a value outside them is{:error, :below_min}/{:error, :above_max}, never clamped. A magnitude of 10¹² or more is{:error, :above_max}even without:max.
Examples
iex> PhoenixKit.Utils.Number.parse_decimal("2,5")
{:ok, Decimal.new("2.5")}
iex> PhoenixKit.Utils.Number.parse_decimal("1 234,56")
{:ok, Decimal.new("1234.56")}
iex> PhoenixKit.Utils.Number.parse_decimal("")
{:error, :empty}
iex> PhoenixKit.Utils.Number.parse_decimal("1e9")
{:error, :invalid}
iex> PhoenixKit.Utils.Number.parse_decimal("-1", min: 0)
{:error, :below_min}
Same as parse_decimal/2, returning the Decimal or raising
ArgumentError with the reason.