Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/cldr/format/decimal.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "bigdecimal"

module Cldr
module Format
class Decimal
Expand All @@ -15,7 +17,7 @@ def initialize(format, symbols = {})
end

def apply(number, options = {})
number = Float(number)
number = BigDecimal(number) unless number.is_a?(::Numeric)
format = number.abs == number ? positive : negative
format.apply(number, options)
rescue TypeError, ArgumentError
Expand Down
9 changes: 6 additions & 3 deletions lib/cldr/format/decimal/number.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "bigdecimal"

module Cldr
module Format
class Decimal
Expand Down Expand Up @@ -33,12 +35,13 @@ def parse_format(format, symbols = {})
def parse_number(number, options = {})
precision = options[:precision] || fraction_format.precision
number = round_to(number, precision)
number.abs.to_s.split(".")
number.abs.to_s("F").split(".")
end

# BigDecimal keeps full precision, avoids Float scientific notation, and
# rounds half-even (ties to even) as CLDR / UTS #35 mandates.
def round_to(number, precision)
factor = 10**precision
(number * factor).round.to_f / factor
BigDecimal(number.to_r, 0).round(precision, half: :even)
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions test/format/currency_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require File.expand_path(File.join(File.dirname(__FILE__) + "/../test_helper"))

class TestCldrCurrencyFormat < Test::Unit::TestCase
test "interpolates the currency symbol" do
assert_equal "$123.45", Cldr::Format::Currency.new("¤0.00").apply(123.45, currency: "$")
end

test "rounds half-even like the decimal formatter" do
assert_equal "$0.12", Cldr::Format::Currency.new("¤0.00").apply(0.125, currency: "$")
assert_equal "$2", Cldr::Format::Currency.new("¤0").apply(2.5, currency: "$")
end

test "formats a large amount without scientific notation" do
assert_equal "$12,345,678,901,234,567,890", Cldr::Format::Currency.new("¤#,##0").apply(12345678901234567890, currency: "$")
end
end
30 changes: 30 additions & 0 deletions test/format/decimal/number_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,34 @@ class TestCldrDecimalNumberFormat < Test::Unit::TestCase
test "cldr example 00000.0000 => 01234,5670" do
assert_equal "01234,5670", Cldr::Format::Decimal::Number.new("00000.0000", decimal: ",", group: " ").apply(1234.567)
end

# CLDR / UTS #35 rounds half-even; expected values cross-checked against ICU and Babel 2.17.
test "rounds exact halves to even at precision 2" do
number = Cldr::Format::Decimal::Number.new("0.00")
assert_equal "0.12", number.apply(0.125)
assert_equal "0.62", number.apply(0.625)
assert_equal "0.38", number.apply(0.375)
assert_equal "0.88", number.apply(0.875)
assert_equal "2.12", number.apply(2.125)
end

test "rounds exact halves to even at precision 0" do
number = Cldr::Format::Decimal::Number.new("0")
assert_equal "0", number.apply(0.5)
assert_equal "2", number.apply(1.5)
assert_equal "2", number.apply(2.5)
assert_equal "4", number.apply(3.5)
assert_equal "4", number.apply(4.5)
end

test "formats large integers without scientific notation" do
number = Cldr::Format::Decimal::Number.new("#,##0")
assert_equal "1,000,000,000,000,000", number.apply(10**15)
assert_equal "10,000,000,000,000,000", number.apply(10**16)
assert_equal "12,345,678,901,234,567,890", number.apply(12345678901234567890)
end

test "keeps full precision for integers above 2**53" do
assert_equal "9,007,199,254,740,993", Cldr::Format::Decimal::Number.new("#,##0").apply(2**53 + 1)
end
end
14 changes: 14 additions & 0 deletions test/format/decimal_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,18 @@ class TestCldrDecimalFormat < Test::Unit::TestCase
test "positive/negative patterns, negative number" do
assert_equal "-123", Cldr::Format::Decimal.new("#;-#").apply(-123)
end

test "rounds half-even through the public formatter" do
assert_equal "2", Cldr::Format::Decimal.new("0").apply(2.5)
assert_equal "-2", Cldr::Format::Decimal.new("0").apply(-2.5)
assert_equal "0.12", Cldr::Format::Decimal.new("0.00").apply(0.125)
end

test "formats a large integer without precision loss" do
assert_equal "12,345,678,901,234,567,890", Cldr::Format::Decimal.new("#,##0").apply(12345678901234567890)
end

test "leaves a non-numeric argument untouched" do
assert_equal "abc", Cldr::Format::Decimal.new("#").apply("abc")
end
end
14 changes: 14 additions & 0 deletions test/format/percent_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require File.expand_path(File.join(File.dirname(__FILE__) + "/../test_helper"))

class TestCldrPercentFormat < Test::Unit::TestCase
test "interpolates the percent sign" do
assert_equal "12.34 %", Cldr::Format::Percent.new("0.00 %").apply(12.34)
end

test "rounds half-even like the decimal formatter" do
assert_equal "0.12 %", Cldr::Format::Percent.new("0.00 %").apply(0.125)
assert_equal "2 %", Cldr::Format::Percent.new("0 %").apply(2.5)
end
end