I am wondering that modulus/reminder(%) is not working properly.
As example:
a = 4.49
b = 449.0
when I do it b % a, it is not showing 0.0, it is showing 4.489999999999997
I want to know the root cause of this behavior.
What will have to get 0.0 for 449%4.49?
Let me give some suggestions.
CodePudding user response:
Don't Use Floating Point Numbers When Precision Counts
Floating point arithmetic can't be accurately represented in binary, and Ruby's Float class makes this extremely clear right at the top where it says:
A Float object represents a sometimes-inexact real number using the native architecture's double-precision floating point representation.
Floats are still often used as "good enough" for certain use cases. However, Ruby provides quite a few alternatives with various initialization, conversion, or coercion methods when you need to represent fractions, decimals, or other types of numbers with higher precision or greater accuracy:
- Ruby Core
- Ruby Standard Library
- Various third-party gems like monetize for handling currency.
While I personally like BigDecimal for its flexibility in dealing with high-precision Float values, in most cases your safest bet is to convert your Float to a Rational number before operating on it, and then converting it back if you need the result in a different format than the default return value of the expression. For example, to convert your Float to a Rational before operating on it:
a = 4.49
b = 449.0
b.to_r % a
#=> 0.0
Note that you generally only need to convert/coerce one of your two numeric values for this to work, as Ruby generally recognizes internally that it needs to operate on similar data types. For example, the same is true with Floats:
4.0 % 3
#=> 1.0
In this case and on my platform, the result is correct regardless of the fact that the second example is using both a Float and an Integer in the expression. The point here is just that only one of the two numbers needs to be a Float in order for Ruby to determine that it needs to treat them both as a Float, and to return a Float as the value.
There are possibly edge cases where Ruby won't auto-convert your values correctly when you only convert one of them explicitly, but for most practical purposes you should be able to rely on it for all core numeric types and (at least in my experience) with BigDecimal.
CodePudding user response:
I want to know the root cause of this behavior.
It's because not every (decimal) floating point literal can be represented exactly as a binary floating point number.
449.0
can be represented exactly as a float, but 4.49
cannot. Its exact value is:
sprintf('%.49f', 4.49)
#=> "4.4900000000000002131628207280300557613372802734375"
'%.49f'
prints the value with 49 fractional digits which happens to be the correct amount for this value. (pure coincidence)
The above value – being slightly larger than 4.49 – only fits 99 times into 449. Let's calculate the remainder:
99 * 4.4900000000000002131628207280300557613372802734375
= 444.5100000000000211031192520749755203723907470703125
449.0
- 444.5100000000000211031192520749755203723907470703125
= 4.4899999999999788968807479250244796276092529296875
Which is (again exactly) what 449 % 4.49
returns:
sprintf('%.49f', 449 % 4.49)
#=> "4.4899999999999788968807479250244796276092529296875"