I've found some behaviour of Time
in Ruby that I don't understand while writing some tests. Am I missing something or is this a genuine problem?
I can reproduce the situation in irb as follows - first create a Time
and add 30 seconds to it:
t = Time.new(2007,01,15,11,15,30.1)
# => 2007-01-15 11:15:30 +0000
t1 = t + 30
# => 2007-01-15 11:16:00 +0000
Then create another time which should be equal to t1
:
t2 = Time.new(2007,01,15,11,16,0.1)
# => 2007-01-15 11:16:00 +0000
Now I'd expect t1
and t2
to be equal, but they aren't according to ==
. From the rough experiments I've done it seems like ==
works, except when the addition of seconds moves t1
onto a new minute:
t1 == t2
# => false
However if you call to_f
on them then ==
does return true
:
t1.to_f == t2.to_f
# => true
Just to confirm that there aren't any nano seconds hanging around:
t1.nsec
# => 100000000
t2.nsec
# => 100000000
=== Added after answers from joanbm and Vitalii Elenhaupt (sorry to open this up again)
joanbm and Vitalii Elenhaupt point out that t1.to_r
and t2.to_r
produce different results.
But... according to the Ruby-Doc Time
within the normal range is stored as a 63 bit integer of the number of nano seconds since the epoch - which suggests that float-point issues shouldn't come into it.
So... why if Time is stored as an integer, and #to_f
and #nsec
can produce the same result to 9 decimal places, can't ==
use this info to recognize the two times as equal? (Maybe Ruby uses #to_r
in the equality test?).
And... is it safe to assume that t1.to_f == t2.to_f
will always give an accurate test of equality to 9 decimal places and is that the best way to compare Time
objects?
This is the notorious problem with inaccurate representation of floating point numbers in computers, quite unrelated to Ruby or Time class implementation.
You give to both objects floating numbers as seconds argument, and its (inaccurate) fractional part constructor stores as a rational number for its internal representation:
t = Time.new(2007,01,15,11,15,30.1)
t.subsec # (14073748835533/140737488355328) <- may vary on your system
t.subsec.to_f # 0.10000000000000142 <- this is not equal to 0.1 !!
dtto for the 2nd time:
t2 = Time.new(2007,01,15,11,16,0.1)
t2.subsec # (3602879701896397/36028797018963968)
t2.subsec.to_f # 0.1 <- there accidentally precision didn't get lost
Just use exact numerical types like Rational
and you are done:
t = Time.new(2007,01,15,11,15,Rational('30.1'))
t1 = t + 30
t2=Time.new(2007,01,15,11,16,0.1r) # alternate notation, see docs
t1 == t2 # true
Or do a rough comparison with Time#round
method applied on both sides.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With