Reputation: 70819
Say I have the following code:
x = 0.8
y = 1.0
What's the best way of checking that y
is equivalent to an Integer
? At the moment I'm doing:
y.to_int == y
which works, but I feel like there should be a better way.
Upvotes: 34
Views: 17211
Reputation: 10250
While there are various ways to do this, one that hasn't been mentioned in other answers but which seems intuitive to me:
y == y.truncate
You could also "monkey-patch" this in as a method:
class Numeric
def is_int?
self == self.truncate
end
end
After which you can call:
y.is_int?
For the monkey-patch option, note that y.is_int?
is not to be confused with y.integer?
, which is different (the latter would be true for 1
but not 1.0
, whereas the former would be true for both).
there could be situations in which the internal floating-point representation of a number would cause erroneous results here (for example, (10*(0.2+0.1)).is_int?
(after the monkey-patch) returns false
, because 10*(0.2+0.1)
gives 3.0000000000000004
). For better or worse, I'm considering a full solution to such problems to be beyond the scope of this answer, as I don't believe it would crop up in the specific way you've represented the question. For anyone for whom that does matter, more detailed analyses of this problem exist, e.g. here, and should be consulted as appropriate.
One could also choose a different name in place of is_int?
-- perhaps integer_equivalent?
would be better?
Upvotes: 1
Reputation: 84343
Generally, you should use ==
to compare numbers when you don't care about the numeric type, which should be most of the time. When you really do care about the type, you should use the object-equality comparison inherited from Numeric#eql?.
You can just ask a numeric object if it's an integer. For example, Numeric#integer? lets you ask a number to check itself and return a Boolean value:
[1, 1.2, 1.02e+1].map(&:integer?)
#=> [true, false, false]
If all you care about is finding out whether y is an integer, then this is the way to go. Using your own examples:
y = 1
y.integer?
#=> true
y = 1.0
y.integer?
#=> false
If you're trying to do something more complicated, like trying to avoid automatic Numeric type conversions in equality comparisons, the only real limit is your imagination and the idea you're trying to express clearly in your code. There are many methods in Numeric, Float, Integer, Object, String, and other classes that would allow you to perform type-conversations and strict equality comparisons. A few examples follow.
Use various methods to convert to an Integer, and then check with strict object equality:
y = 1.2
# All of these will return false.
y.eql? y.truncate
y.eql? y.floor
y.eql? y.to_i
y.eql? Integer(y)
If you want to create a Boolean expression without the automatic numeric conversions made by ==
, you can use the class-specific method inherited from Numeric#zero?. For example:
(y % 1).zero?
y.modulo(1).zero?
If modulo doesn't do the trick for you with certain types of numbers, then you can use Float#ceil or Float#floor:
y = 1.2
(y - y.floor).zero?
#=> false
y = 1.02e+1
(y.floor - y).zero?
#=> false
Upvotes: 8
Reputation: 3472
You probably don't even want to do that. Floating point arithmetic is subject to rounding errors, and a series of operations that you would think gives e.g. 6.0 may actually give 5.9999999999999 . In that case, any check that the value is integer will fail, even though you probably intended it to succeed.
Normally it is a better approach to compare the float to an integer version within a given precision, like if (x - x.to_i).abs < 0.001
.
Upvotes: 3
Reputation: 222090
You mod
the value with 1, and check if the value equals 0.
if y % 1 == 0
Upvotes: 75