aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
authorSean Griffin <sean@seantheprogrammer.com>2016-03-24 16:06:40 -0600
committerSean Griffin <sean@seantheprogrammer.com>2016-03-24 16:09:19 -0600
commitc7d3bd48dff0a509f5c21ec8864cb4f774d604e6 (patch)
treee187eee3bfeb528d3f3719d36d462713267e8f9f /activemodel
parenta12ad8ae542d0d96249be7db6f708ead4d7054aa (diff)
downloadrails-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
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/lib/active_model/type/decimal.rb12
-rw-r--r--activemodel/test/cases/type/decimal_test.rb7
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