Packages
ex_cldr_numbers
1.3.0-rc.0
2.38.3
2.38.2
2.38.1
2.38.0
2.37.0
2.36.0
2.35.2
2.35.1
2.35.0
2.34.1
2.34.0
2.33.6
2.33.5
2.33.4
2.33.3
2.33.2
2.33.1
2.33.0
2.32.4
2.32.3
2.32.2
2.32.1
2.32.0
retired
2.31.3
retired
2.31.2
2.31.1
2.31.0
2.30.1
2.30.0
2.29.0
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.0
2.25.2
2.25.1
2.25.0
2.24.0
2.23.3
2.23.2
2.23.1
2.23.0
2.23.0-rc.4
2.23.0-rc.3
2.23.0-rc.2
2.23.0-rc.1
2.23.0-rc.0
2.22.1
2.22.0
2.21.0
2.20.0
2.19.0
2.18.4
2.18.3
2.18.2
2.18.1
2.18.0
2.17.0
2.17.0-rc.1
2.17.0-rc.0
2.16.1
2.16.0
2.16.0-rc.0
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.0
2.13.2
2.13.1
2.13.0
2.13.0-rc.0
2.12.1
2.12.0
2.11.0
2.10.0
2.9.0
2.8.0
2.7.2
2.7.1
2.7.0
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.0
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.1
2.1.0
2.0.0
2.0.0-rc.0
1.6.0
1.5.2
1.5.1
1.5.0
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.3.0-rc.1
1.3.0-rc.0
retired
1.2.0
1.1.0
1.0.1
1.0.0
1.0.0-rc.0
retired
0.3.3
0.3.2
retired
0.3.1
retired
0.3.0
retired
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Number and currency localization and formatting functions for the Common Locale Data Repository (CLDR).
Retired package: Deprecated
Current section
Files
Jump to
Current section
Files
lib/number/formatter/short_formatter.ex
defmodule Cldr.Number.Formatter.Short do
@moduledoc """
Formats a number according to the locale-specific `:short` formats
This is best explained by some
examples:
iex> Cldr.Number.to_string 123, format: :short
{:ok, "123"}
iex> Cldr.Number.to_string 1234, format: :short
{:ok, "1K"}
iex> Cldr.Number.to_string 523456789, format: :short
{:ok, "523M"}
iex> Cldr.Number.to_string 7234567890, format: :short
{:ok, "7B"}
iex> Cldr.Number.to_string 7234567890, format: :long
{:ok, "7 billion"}
These formats are compact representations however they do lose
precision in the presentation in favour of human readibility.
Note that for a `:currency` short format the number of decimal places
is retrieved from the currency definition itself. You can see the difference
in the following examples:
iex> Cldr.Number.to_string 1234, format: :short, currency: "EUR"
{:ok, "€1K"}
iex> Cldr.Number.to_string 1234, format: :short, currency: "EUR", fractional_digits: 2
{:ok, "€1.23K"}
iex> Cldr.Number.to_string 1234, format: :short, currency: "JPY"
{:ok, "¥1K"}
**This module is not part of the public API and is subject
to change at any time.**
"""
alias Cldr.Math
alias Cldr.Locale
alias Cldr.Number.{System, Format, Formatter, Cardinal}
# Notes from Unicode TR35 on formatting short formats:
#
# To format a number N, the greatest type less than or equal to N is
# used, with the appropriate plural category. N is divided by the type, after
# removing the number of zeros in the pattern, less 1. APIs supporting this
# format should provide control over the number of significant or fraction
# digits.
#
# If the value is precisely 0, or if the type is less than 1000, then the
# normal number format pattern for that sort of object is supplied. For
# example, formatting 1200 would result in “$1.2K”, while 990 would result in
# simply “$990”.
#
# Thus N=12345 matches <pattern type="10000" count="other">00 K</pattern> . N
# is divided by 1000 (obtained from 10000 after removing "00" and restoring one
# "0". The result is formatted according to the normal decimal pattern. With no
# fractional digits, that yields "12 K".
def to_string(number, style, options) do
locale = options[:locale] || Cldr.get_current_locale()
with {:ok, locale} <- Cldr.validate_locale(locale),
{:ok, number_system} <- System.system_name_from(options[:number_system], locale) do
short_format_string(number, style, locale, number_system, options)
end
end
@spec short_format_string(number, atom, Locale.name(), atom, Map.t()) :: List.t()
defp short_format_string(number, style, locale, number_system, options) do
case Format.formats_for(locale, number_system) do
{:ok, formats} ->
formats = Map.get(formats, style)
{number, format} =
case choose_short_format(number, formats, options) do
{range, [format, number_of_zeros]} ->
{normalise_number(number, range, number_of_zeros), format}
{_range, format} ->
{number, format}
end
Formatter.Decimal.to_string(number, format, digits(options, options[:fractional_digits]))
{:error, _} = error ->
error
end
end
# For short formats the fractional digits should be 0 unless otherwise specified,
# even for currencies
defp digits(options, nil) do
Map.put(options, :fractional_digits, 0)
end
defp digits(options, _digits) do
options
end
defp choose_short_format(number, _rules, options) when is_number(number) and number < 1000 do
format =
options[:locale]
|> Format.formats_for!(options[:number_system])
|> Map.get(standard_or_currency(options))
{number, format}
end
defp choose_short_format(number, rules, options) when is_number(number) do
[range, rule] =
rules
|> Enum.filter(fn [range, _rules] -> range <= number end)
|> Enum.reverse()
|> hd
mod =
number
|> trunc
|> rem(range)
{range, Cardinal.pluralize(mod, options[:locale], rule)}
end
defp choose_short_format(%Decimal{} = number, rules, options) do
number
|> Decimal.round(0, :floor)
|> Decimal.to_integer()
|> choose_short_format(rules, options)
end
defp standard_or_currency(options) do
if options[:currency] do
:currency
else
:standard
end
end
@one_thousand Decimal.new(1000)
defp normalise_number(%Decimal{} = number, range, number_of_zeros) do
if Decimal.cmp(number, @one_thousand) == :lt do
number
else
Decimal.div(number, Decimal.new(adjustment(range, number_of_zeros)))
end
end
defp normalise_number(number, _range, _number_of_zeros) when number < 1000 do
number
end
defp normalise_number(number, range, number_of_zeros) do
number / adjustment(range, number_of_zeros)
end
# TODO: We can precompute these at compile time which would
# save this lookup
defp adjustment(range, number_of_zeros) do
range / Math.power_of_10(number_of_zeros - 1)
end
end