October 23, 2017
Let's see an example of numerical validation of very large numbers.
class Score
validates :total, numericality: { less_than_or_equal_to: 10_000_000_000_000_000 }
end
> score = Score.new(total: (10_000_000_000_000_000 + 1).to_s)
> score.total
#=> 1.0e+16
> score.invalid?
#=> false
Here, we have added numerical validation on total
column of Score
model that it should be less than 10_000_000_000_000_000
.
After that we have created one instance of this model with total
greater than allowed value.
This should result in an invalid object as it violates the numericality criteria.
But it is still valid. We can also see that value of total
has been converted into floating number
instead of integer. This happens because Rails used to convert the input to float if it was not
already numeric.
The real problem is here is that the floating point comparison of the Ruby language itself which is not accurate for very large numbers.
>> a = (10_000_000_000_000_000 + 1).to_s
=> "10000000000000001"
>> b = Kernel.Float a
=> 1.0e+16
>> c = b + 1
=> 1.0e+16
>> c < b
=> false
>> c > b
=> false
>>
This issue has been fixed in Rails here. Now, if the given string input can be treated as integer, then an integer value is returned instead of float. This makes sure that the comparison works correctly.
# Rails 5.2
> score = Score.new(total: (10_000_000_000_000_000 + 1).to_s)
> score.total
#=> 10000000000000001
> score.invalid?
#=> true
This change is present in Rails 5.2 and above. It also backported to Rails 5.1 and Rails 5.0 branches.
If this blog was helpful, check out our full blog archive.