Forum: Ruby-core [ruby-trunk - Bug #9059][Open] Equal Time objects don't hash equal

A83b5c08b687b10dc536f70a043ac526?d=identicon&s=25 zakhar (Isaac Schwabacher) (Guest)
on 2013-10-29 19:50
(Received via mailing list)
Issue #9059 has been reported by zakhar (Isaac Schwabacher).

----------------------------------------
Bug #9059: Equal Time objects don't hash equal
https://bugs.ruby-lang.org/issues/9059

Author: zakhar (Isaac Schwabacher)
Status: Open
Priority: Normal
Assignee:
Category:
Target version:
ruby -v: ruby 2.0.0p247 (2013-06-27 revision 41674)
[x86_64-darwin12.4.0]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN


=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
A83b5c08b687b10dc536f70a043ac526?d=identicon&s=25 zakhar (Isaac Schwabacher) (Guest)
on 2013-10-29 23:10
(Received via mailing list)
Issue #9059 has been updated by zakhar (Isaac Schwabacher).


=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 = @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
       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

----------------------------------------
Bug #9059: Equal Time objects don't hash equal
https://bugs.ruby-lang.org/issues/9059#change-42650

Author: zakhar (Isaac Schwabacher)
Status: Open
Priority: Normal
Assignee:
Category:
Target version:
ruby -v: ruby 2.0.0p247 (2013-06-27 revision 41674)
[x86_64-darwin12.4.0]
Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN


=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
This topic is locked and can not be replied to.