diff options
author | Sean Griffin <sean@seantheprogrammer.com> | 2016-03-24 16:06:40 -0600 |
---|---|---|
committer | Sean Griffin <sean@seantheprogrammer.com> | 2016-03-24 16:09:19 -0600 |
commit | c7d3bd48dff0a509f5c21ec8864cb4f774d604e6 (patch) | |
tree | e187eee3bfeb528d3f3719d36d462713267e8f9f | |
parent | a12ad8ae542d0d96249be7db6f708ead4d7054aa (diff) | |
download | rails-c7d3bd48dff0a509f5c21ec8864cb4f774d604e6.tar.gz rails-c7d3bd48dff0a509f5c21ec8864cb4f774d604e6.tar.bz2 rails-c7d3bd48dff0a509f5c21ec8864cb4f774d604e6.zip |
Apply scale before precision when coercing floats to decimal
Since precision is always larger than scale, it can actually change
rounding behavior. Given a precision of 5 and a scale of 3, when you
apply the precision of 5 to `1.25047`, the result is `1.2505`, which
when the scale is applied would be `1.251` instead of the expected
`1.250`.
This issue appears to only occur with floats, as scale doesn't apply to
other numeric types, and the bigdecimal constructor actually ignores
precision entirely when working with strings. There's no way we could
handle this for the "unknown object which responds to `to_d`" case, as
we can't assume an interface for applying the scale.
Fixes #24235
-rw-r--r-- | activemodel/lib/active_model/type/decimal.rb | 12 | ||||
-rw-r--r-- | activemodel/test/cases/type/decimal_test.rb | 7 |
2 files changed, 17 insertions, 2 deletions
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb index d19d8baada..11ea327026 100644 --- a/activemodel/lib/active_model/type/decimal.rb +++ b/activemodel/lib/active_model/type/decimal.rb @@ -29,12 +29,12 @@ module ActiveModel end end - scale ? casted_value.round(scale) : casted_value + apply_scale(casted_value) end def convert_float_to_big_decimal(value) if precision - BigDecimal(value, float_precision) + BigDecimal(apply_scale(value), float_precision) else value.to_d end @@ -47,6 +47,14 @@ module ActiveModel precision.to_i end end + + def apply_scale(value) + if scale + value.round(scale) + else + value + end + end end end end diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb index 353dbf84ad..1950566c0e 100644 --- a/activemodel/test/cases/type/decimal_test.rb +++ b/activemodel/test/cases/type/decimal_test.rb @@ -52,6 +52,13 @@ module ActiveModel assert_not type.changed?(5.0, 5.0, '5.0') assert_not type.changed?(-5.0, -5.0, '-5.0') end + + def test_scale_is_applied_before_precision_to_prevent_rounding_errors + type = Decimal.new(precision: 5, scale: 3) + + assert_equal BigDecimal("1.250"), type.cast(1.250473853637869) + assert_equal BigDecimal("1.250"), type.cast("1.250473853637869") + end end end end |