1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Fix AS::NumberHelper results with large precisions

before:
  ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
  => "3.14158999999999988261834005243144929409027099609375"
after:
  ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
  => "3.14159000000000000000000000000000000000000000000000"
This commit is contained in:
Kenta Murata & Akira Matsuda 2013-12-20 15:35:13 +09:00 committed by Akira Matsuda
parent c437a98aca
commit 9e997e9039
3 changed files with 62 additions and 7 deletions

View file

@ -1,3 +1,14 @@
* Fixed Float related error in NumberHelper with large precisions.
before:
ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
#=> "3.14158999999999988261834005243144929409027099609375"
after:
ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
#=> "3.14159000000000000000000000000000000000000000000000"
*Kenta Murata*, *Akira Matsuda*
* Default the new `I18n.enforce_available_locales` config to `true`, meaning
`I18n` will make sure that all locales passed to it must be declared in the
`available_locales` list.

View file

@ -5,28 +5,50 @@ module ActiveSupport
self.validate_float = true
def convert
@number = Float(number)
precision = options.delete :precision
significant = options.delete :significant
case number
when Float, String
@number = BigDecimal(number.to_s)
when Rational
if significant
@number = BigDecimal(number, digit_count(number.to_i) + precision)
else
@number = BigDecimal(number, precision)
end
else
@number = number.to_d
end
if significant && precision > 0
digits, rounded_number = digits_and_rounded_number(precision)
precision -= digits
precision = 0 if precision < 0 # don't let it be negative
else
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
rounded_number = number.round(precision)
rounded_number = rounded_number.to_i if precision == 0
rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
delimited_number = NumberToDelimitedConverter.convert("%01.#{precision}f" % rounded_number, options)
formatted_string =
case rounded_number
when BigDecimal
s = rounded_number.to_s('F') + '0'*precision
a, b = s.split('.', 2)
a + '.' + b[0, precision]
else
"%01.#{precision}f" % rounded_number
end
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
format_number(delimited_number)
end
private
def digits_and_rounded_number(precision)
if number.zero?
if zero?
[1, 0]
else
digits = digit_count(number)
@ -38,11 +60,11 @@ module ActiveSupport
end
def calculate_rounded_number(multiplier)
(BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
(number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
end
def digit_count(number)
(Math.log10(number.abs) + 1).floor
(Math.log10(absolute_number(number)) + 1).floor
end
def strip_insignificant_zeros
@ -57,6 +79,14 @@ module ActiveSupport
number
end
end
def absolute_number(number)
number.respond_to?(:abs) ? number.abs : number.to_d.abs
end
def zero?
number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
end
end
end
end

View file

@ -123,6 +123,12 @@ module ActiveSupport
assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, :precision => 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20))
assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100))
end
end
@ -155,6 +161,14 @@ module ActiveSupport
assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), :precision => 20, :significant => true )
assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true )
assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true )
end
end