我在Ruby中发现了Time
的一些行为,在编写测试时我并不理解。我错过了什么或者这是一个真正的问题吗?
我可以按如下方式重现irb中的情况 - 首先创建一个Time
并添加30秒:
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
然后创建另一个应该等于t1
的时间:
t2 = Time.new(2007,01,15,11,16,0.1)
# => 2007-01-15 11:16:00 +0000
现在我希望t1
和t2
相等,但它们不符合==
。从粗略的实验中我看来==
似乎有效,除非添加秒将t1
移动到新的一分钟:
t1 == t2
# => false
但是,如果您对其发送to_f
,则==
会返回true
:
t1.to_f == t2.to_f
# => true
只是为了确认没有任何纳秒秒数:
t1.nsec
# => 100000000
t2.nsec
# => 100000000
===在joanbm和Vitalii Elenhaupt的答案后添加(很抱歉再次打开)
joanbm和Vitalii Elenhaupt指出t1.to_r
和t2.to_r
会产生不同的结果。
但是...根据正常范围内的Ruby-Doc Time
存储为自纪元以来纳秒秒数的63位整数 - 这表明浮点问题不应该'进入它。
那么......为什么如果Time存储为整数,并且#to_f
和#nsec
可以产生相同的结果到9位小数,则==
不能使用此信息认为这两次是平等的? (也许Ruby在等式测试中使用#to_r
。)。
并且...可以安全地假设t1.to_f == t2.to_f
将始终对9位小数进行准确的等式测试,这是比较Time
个对象的最佳方法吗?
答案 0 :(得分:4)
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:
--Main Project
|-- core
|-- android
|-- desktop
dtto for the 2nd time:
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 !!
Just use exact numerical types like 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
and you are done:
Rational
Or do a rough comparison with 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
method applied on both sides.
答案 1 :(得分:0)
您正在为Time对象使用不同的参数
#irb
t = Time.new(2007,01,15,11,15,30,0.1)
=> 2007-01-15 11:15:30 +0000
t1 = t2 + 30
=> 2007-01-15 11:16:00 +0000
如果你没有通过会议记录,我的系统上的时区似乎发生了变化:
t2 = Time.new(2007,01,15,11,16,0.1)
=> 2007-01-15 11:16:00 +0100 # what happened here? '+0100'
t1 == t2
=> false
即使那个不是的情况,有趣的是这发生在我的irb中。
使用显式时区构建像Time.utc(2007,01,15,11,15,30,0.1)
这样的对象,完全返回您描述的行为。
但是,如果传递相同数量的参数(t2为0分钟),则比较将返回预期结果,因为您可能甚至不会遇到joanbm描述的浮点问题。 / p>
t = Time.new(2007,01,15,11,15,30,0.1)
=> 2007-01-15 11:15:30 +0000
t1 = t + 30
=> 2007-01-15 11:16:00 +0000
t2 = Time.new(2007,01,15,11,16,0,0.1)
=> 2007-01-15 11:16:00 +0000
t1 == t2
=> true
如果你打印时间理性表格,你会得到:
t1.to_r
=> (42112611033072059383231283/36028797018963968)
t2.to_r
=> (42112611033072059383231283/36028797018963968)
当to_r
返回自epoch(或Unix时间)以来的秒数时间,只要日期,时间和时区匹配,t1和t2就没有理由不相等
不幸的是,我不能给出一个很好的解释,为什么会这样。 这看起来很有趣,当我找到这种行为的原因时我会回来。
修改强>
这并不能解释时区的变化,因此无法解释问题,但是你错过了t
的逗号,所以你实际上传递了30.1而不是30秒。
t = Time.new(2007,01,15,11,15,30.1)
可能应该是
t = Time.new(2007,01,15,11,15,30,0.1)
但是这不会导致您的比较失败。
答案 2 :(得分:0)
之所以发生这种情况,是因为两次都不同。这是this answer的一个很好的例证:
>> t1.to_r #=> (164501373566169071275213/140737488355328)
>> t2.to_r #=> (42112351632939282246454477/36028797018963968)
如果您不关心毫秒,可以使用to_i
方法比较时间戳:
>> t1.to_i == t2.to_i # => true
或从时间戳创建一个新的Time
对象:
>> Time.at(t1.to_i) == Time.at(t2.to_i) # => true