Project

General

Profile

Actions

Bug #9059

closed

Equal Time objects don't hash equal

Added by zakhar (Isaac Schwabacher) over 10 years ago. Updated over 10 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0]
[ruby-core:58070]

Description

=begin
Time objects break the promise that if (({t0.eql? t1})), then (({t0.hash == t1.hash})).
It is possible that this is related to the resolution of ((<this problem|URL:https://www.ruby-forum.com/topic/4415234>)), since both issues seem to be related to round-off error.

#!/usr/bin/env ruby

require 'minitest/autorun'

describe Object do
before do
t0 = Time.new(2013, 10, 29, 12, 30, 27)
t1 = t0 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
t_midnight = Time.new(2013, 10, 29)
@x = t0 - (t0.to_r - t_midnight.to_r) % 60
@y = t1 - (t1.to_r - t_midnight.to_r) % 60
end

it "should hash equal to eql objects" do
if @x.respond_to? :eql? and @x.respond_to? :hash and @y.respond_to? :hash
@x.hash.must_equal @y.hash if @x.eql? @y
end
end
end

This test also failed on ruby -v ((%ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux]%)).
If I had to guess, I would say that the instance variables from which (({@t0.tv_})) and (({@t1.tv_})) are computed are (({#==})) but not (({#eql?})), and that (({Time#eql?})) is (correctly, I think) using (({#==})) to compare these, but (({Time#hash})) is using their (({#hash}))es directly rather than converting them to a common type first.

=end

Updated by zakhar (Isaac Schwabacher) over 10 years ago

=begin
Further testing supports the above hypothesis:

$ cat test_time_hash.rb
#!/usr/bin/env ruby

require 'minitest/autorun'

describe Object do
before do
@x = Time.new(2013, 10, 29, 12, 30, 27)
@y = @x + '1/1_000_000_000'.to_r - '1/1_000_000_000'.to_r
@z = @x + '1/10_000_000_000'.to_r - '1/10_000_000_000'.to_r
@w (Kenichiro Wakitani) = @x + 2**(8 * 0.size) - 2**(8 * 0.size)
end

describe "Time with fixnum nanoseconds" do
it "should hash equal to an eql object" do
if @x.respond_to? :eql? and @x.respond_to? :hash and @y.respond_to? :hash
@x.hash.must_equal @y.hash if @x.eql? @y
end
end
end

describe "Time with rational nanoseconds" do
it "should hash equal to an eql object" do
if @x.respond_to? :eql? and @x.respond_to? :hash and @z.respond_to? :hash
@x.hash.must_equal @z.hash if @x.eql? @z
end
end
end

describe "Time with bignum nanoseconds" do
it "should hash equal to an eql object" do
if @x.respond_to? :eql? and @x.respond_to? :hash and @w.respond_to? :hash
@x.hash.must_equal @w.hash if @x.eql? @w (Kenichiro Wakitani)
end
end
end
end

$ ruby test_time_hash.rb
Run options: --seed 21051

Running tests:

..F

Finished tests in 0.170915s, 17.5526 tests/s, 17.5526 assertions/s.

  1. Failure:
    test_0001_should hash equal to an eql object(Object::Time with rational nanoseconds) [test_time_hash.rb:24]:
    Expected: 383634095889643923
    Actual: 1265190933511225464

3 tests, 3 assertions, 1 failures, 0 errors, 0 skips

I found it interesting that the Bignum test passed while the Rational test failed, so I followed up:

2.0.0-p247 :001 > (2**(8 * 0.size - 2)).class # the smallest positive integer that can't be a Fixnum
=> Bignum
2.0.0-p247 :003 > (2**(8 * 0.size - 2) - 2**(8 * 0.size - 2)).class
=> Fixnum
2.0.0-p247 :004 > ('1/10'.to_r - '1/10'.to_r).class
=> Rational

It looks like I did not actually succeed in testing the Bignum case; I suspect that it will show the same behavior as the Rational case if you can figure out how to get a Bignum and a Fixnum that compare equal.

HTH

=end

Actions #2

Updated by akr (Akira Tanaka) over 10 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r43473.
Isaac, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • time.c (v2w): Normalize a rational value to an integer if possible.
    [ruby-core:58070] [Bug #9059] reported by Isaac Schwabacher.
Actions

Also available in: Atom PDF

Like0
Like0Like0