Skip to content

Commit

Permalink
Change meta field :rounding to :round_nearest and add it as a to_stri…
Browse files Browse the repository at this point in the history
…ng parameter
  • Loading branch information
kipcole9 committed Apr 12, 2020
1 parent e838222 commit 3bc8291
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 26 deletions.
8 changes: 5 additions & 3 deletions bench/to_string.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{:ok, options} = Cldr.Number.Format.Options.validate_options([], MyApp.Cldr, locale: Cldr.get_locale())
number = 100000.55

Benchee.run(
%{
"Number to_string" => fn -> MyApp.Cldr.Number.to_string 10000.55 end,
"Number to_string preformatted options" => fn -> MyApp.Cldr.Number.to_string 10000.55, options end,
"Float to_string" => fn -> Float.to_string 10000.55 end
"Number to_string" => fn -> MyApp.Cldr.Number.to_string number end,
"Number to_string preformatted options" => fn -> MyApp.Cldr.Number.to_string number, options end,
"Float to_string" => fn -> Float.to_string 10000.55 end,
":erlang.float_to_binary" => fn -> :erlang.float_to_binary(number, [:compact, decimals: 3]) end
},
time: 10,
memory_time: 2
Expand Down
8 changes: 6 additions & 2 deletions lib/cldr/number.ex
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,16 @@ defmodule Cldr.Number do
locale currently in affect for this `Process` and which is set by
`Cldr.put_locale/1`.
* `:fractional_digits` is set to a positive integer value then the number
will be rounded to that number of digits and displayed accordingly overriding
* If `:fractional_digits` is set to a positive integer value then the number
will be rounded to that number of digits and displayed accordingly - overriding
settings that would be applied by default. For example, currencies have
fractional digits defined reflecting each currencies minor unit. Setting
`:fractional_digits` will override that setting.
* If `:round_nearest` is set to a positive integer value then the number
will be rounded to nearest increment of that value - overriding
settings that would be applied by default.
* `:minimum_grouping_digits` overrides the CLDR definition of minimum grouping
digits. For example in the locale `es` the number `1234` is formatted by default
as `1345` because the locale defines the `minimium_grouping_digits` as `2`. If
Expand Down
8 changes: 6 additions & 2 deletions lib/cldr/number/backend/number.ex
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,16 @@ defmodule Cldr.Number.Backend.Number do
locale currently in affect for this `Process` and which is set by
`Cldr.put_locale/1`.
* `:fractional_digits` is set to a positive integer value then the number
will be rounded to that number of digits and displayed accordingly overriding
* If `:fractional_digits` is set to a positive integer value then the number
will be rounded to that number of digits and displayed accordingly - overriding
settings that would be applied by default. For example, currencies have
fractional digits defined reflecting each currencies minor unit. Setting
`:fractional_digits` will override that setting.
* If `:round_nearest` is set to a positive integer value then the number
will be rounded to nearest increment of that value - overriding
settings that would be applied by default.
* `:minimum_grouping_digits` overrides the CLDR definition of minimum grouping
digits. For example in the locale `es` the number `1234` is formatted by default
as `1345` because the locale defines the `minimium_grouping_digits` as `2`. If
Expand Down
10 changes: 5 additions & 5 deletions lib/cldr/number/format/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ defmodule Cldr.Number.Format.Compiler do

# Default rounding increment (not the same as rounding decimal
# digits. `0` means no rounding increment to be applied.
@default_rounding 0
@default_round_nearest 0

@doc """
Returns a number placeholder symbol.
Expand Down Expand Up @@ -359,7 +359,7 @@ defmodule Cldr.Number.Format.Compiler do
exponent_sign: exponent_sign(format_parts),
scientific_rounding: scientific_rounding(format_parts),
grouping: grouping(format_parts),
rounding: rounding(format_parts),
round_nearest: round_nearest(format_parts),
padding_length: padding_length(format[:positive][:pad], format),
padding_char: padding_char(format),
multiplier: multiplier(format),
Expand Down Expand Up @@ -712,19 +712,19 @@ defmodule Cldr.Number.Format.Compiler do
# * In a pattern, digits '1' through '9' specify rounding, but otherwise
# behave identically to digit '0'.

defp rounding(%{"integer" => integer_format, "fraction" => fraction_format}) do
defp round_nearest(%{"integer" => integer_format, "fraction" => fraction_format}) do
format =
(integer_format <> @decimal_separator <> fraction_format)
|> String.replace(@rounding_pattern, "")
|> String.trim_trailing(@decimal_separator)

case Float.parse(format) do
:error -> @default_rounding
:error -> @default_round_nearest
{rounding, ""} -> rounding
end
end

defp rounding(_), do: @default_rounding
defp round_nearest(_), do: @default_round_nearest

@doc """
A regular expression that can be used to split either a number format
Expand Down
10 changes: 5 additions & 5 deletions lib/cldr/number/format/meta.ex
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ defmodule Cldr.Number.Format.Meta do
fraction: %{first: 0, rest: 0},
integer: %{first: 0, rest: 0}
},
rounding: 0,
round_nearest: 0,
padding_length: 0,
padding_char: " ",
multiplier: 1,
Expand Down Expand Up @@ -198,14 +198,14 @@ defmodule Cldr.Number.Format.Meta do
end

@doc """
Set the number of digits to which the number should
Set the increment to which the number should
be rounded.
"""
@spec put_integer_digits(t(), non_neg_integer) :: t()
def put_rounding_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
@spec put_round_nearest_digits(t(), non_neg_integer) :: t()
def put_round_nearest_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
meta
|> Map.put(:rounding, digits)
|> Map.put(:round_nearest, digits)
end

@doc """
Expand Down
19 changes: 18 additions & 1 deletion lib/cldr/number/format/options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ defmodule Cldr.Number.Format.Options do
:minimum_grouping_digits,
:pattern,
:rounding_mode,
:fractional_digits
:fractional_digits,
:round_nearest
]

# These are the options that can be supplied
Expand Down Expand Up @@ -84,6 +85,7 @@ defmodule Cldr.Number.Format.Options do
pattern: String.t(),
rounding_mode: Decimal.rounding(),
fractional_digits: pos_integer(),
round_nearest: pos_integer()
}

defstruct @options
Expand Down Expand Up @@ -384,6 +386,21 @@ defmodule Cldr.Number.Format.Options do
":fractional_digits must be a positive integer or nil. Found #{inspect other}"}}
end

def validate_option(:round_nearest, _options, _backend, nil) do
{:ok, nil}
end

def validate_option(:round_nearest, _options, _backend, int)
when is_integer(int) and int > 0 do
{:ok, int}
end

def validate_option(:round_nearest, _options, _backend, other) do
{:error,
{ArgumentError,
":round_nearest must be a positive integer or nil. Found #{inspect other}"}}
end

def validate_option(:rounding_mode, _options, _backend, nil) do
{:ok, :half_even}
end
Expand Down
24 changes: 19 additions & 5 deletions lib/cldr/number/formatter/decimal_formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ defmodule Cldr.Number.Formatter.Decimal do
|> adjust_fraction_for_currency(options.currency, options.currency_digits, backend)
|> adjust_fraction_for_significant_digits(number)
|> adjust_for_fractional_digits(options.fractional_digits)
|> adjust_for_round_nearest(options.round_nearest)
|> Map.put(:number, number)
end

Expand Down Expand Up @@ -154,12 +155,12 @@ defmodule Cldr.Number.Formatter.Decimal do
# Round to nearest rounds a number to the nearest increment specified. For example
# if `rounding: 5` then we round to the nearest multiple of 5. The appropriate rounding
# mode is used.
def round_to_nearest(number, %{rounding: rounding}, _backend, %{rounding_mode: _rounding_mode})
def round_to_nearest(number, %{round_nearest: rounding}, _backend, %{rounding_mode: _rounding_mode})
when rounding == 0 do
number
end

def round_to_nearest(%Decimal{} = number, %{rounding: rounding}, _backend, %{
def round_to_nearest(%Decimal{} = number, %{round_nearest: rounding}, _backend, %{
rounding_mode: rounding_mode
}) do
rounding = Decimal.new(rounding)
Expand All @@ -170,15 +171,15 @@ defmodule Cldr.Number.Formatter.Decimal do
|> Decimal.mult(rounding)
end

def round_to_nearest(number, %{rounding: rounding}, _backend, %{rounding_mode: rounding_mode})
def round_to_nearest(number, %{round_nearest: rounding}, _backend, %{rounding_mode: rounding_mode})
when is_float(number) do
number
|> Kernel./(rounding)
|> Math.round(0, rounding_mode)
|> Kernel.*(rounding)
end

def round_to_nearest(number, %{rounding: rounding}, _backend, %{rounding_mode: rounding_mode})
def round_to_nearest(number, %{round_nearest: rounding}, _backend, %{rounding_mode: rounding_mode})
when is_integer(number) do
number
|> Kernel./(rounding)
Expand Down Expand Up @@ -705,7 +706,7 @@ defmodule Cldr.Number.Formatter.Decimal do

def do_adjust_fraction(meta, digits, rounding) do
rounding = power_of_10(-digits) * rounding
%{meta | fractional_digits: %{max: digits, min: digits}, rounding: rounding}
%{meta | fractional_digits: %{max: digits, min: digits}, round_nearest: rounding}
end

#
Expand Down Expand Up @@ -749,6 +750,7 @@ defmodule Cldr.Number.Formatter.Decimal do
end

# To allow overriding fractional digits
# This causes rounding of the number
def adjust_for_fractional_digits(meta, nil) do
meta
end
Expand All @@ -757,6 +759,18 @@ defmodule Cldr.Number.Formatter.Decimal do
%{meta | fractional_digits: %{max: digits, min: digits}}
end

# To allow overriding round nearest
# which impacts the precision of the number
# and is commonly required for currency
# formatting
def adjust_for_round_nearest(meta, nil) do
meta
end

def adjust_for_round_nearest(meta, digits) do
%{meta | round_nearest: digits}
end

@doc false
def define_to_string(backend) do
config = Module.get_attribute(backend, :config)
Expand Down
6 changes: 3 additions & 3 deletions test/meta_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Cldr.Number.Format.Meta.Test do
number: 0,
padding_char: " ",
padding_length: 0,
rounding: 0,
round_nearest: 0,
scientific_rounding: 0,
significant_digits: %{max: 0, min: 0}
}
Expand All @@ -41,15 +41,15 @@ defmodule Cldr.Number.Format.Meta.Test do
|> Meta.put_exponent_digits(5)
|> Meta.put_exponent_sign(true)
|> Meta.put_scientific_rounding_digits(6)
|> Meta.put_rounding_digits(7)
|> Meta.put_round_nearest_digits(7)
|> Meta.put_padding_length(8)
|> Meta.put_padding_char("Z")
|> Meta.put_multiplier(9)

assert meta.exponent_digits == 5
assert meta.exponent_sign == true
assert meta.scientific_rounding == 6
assert meta.rounding == 7
assert meta.round_nearest == 7
assert meta.padding_length == 8
assert meta.padding_char == "Z"
assert meta.multiplier == 9
Expand Down

0 comments on commit 3bc8291

Please sign in to comment.