From: David MacMahon Date: 2013-02-21T04:17:37+09:00 Subject: [ruby-core:52586] Re: [ruby-trunk - Bug #7829] Rounding error in Ruby Time On Feb 20, 2013, at 7:46 AM, loirotte (Philippe Dosch) wrote: > Typing this instruction: > > irb(main):001:0> Time.utc(1970,1,1,0,0,12.860).strftime("%H:%M:%S,%L") > => "00:00:12,859" > > gives an unexpected intuitive result. I totally agree with you that this is an unexpected, unintuitive result. The "problem" arises from the fact that you are passing in a Float for the number of seconds yet I suspect that Time uses Rational to support arbitrary precision. The conversion from the 12.860 literal to double precision floating point is limited in precision. The nearest representable value in this case is less than the "true" value. Converting Float to Rational is "perfect" in that the conversion back to Float results in the same (limited precision) value. The storing of 12.86 also "wastes" some bits of precision on the integer portion of the value: >> 12.86-12 => 0.8599999999999994 You can avoid this "problem" by passing in a Rational instead of a Float for the seconds: irb(main):001:0> Time.utc(1970,1,1,0,0,Rational(12860,1000)).strftime("%H:%M:%S,%L") => "00:00:12,860" The DateTime class, which I think also uses Rational internally, does not seem to suffer the same problem: irb(main):001:0> DateTime.civil(1970,1,1,0,0,12.86).strftime('%H:%M:%S,%L') => "00:00:12,860" If DateTime also got it wrong I'd say it's just a limitation of floating point representation. The fact that DateTime behaves as expected leads me to believe that maybe Time's implementation could be altered to match. My guess is that Time uses Float.to_r on the seconds parameter directly thereby getting a power-of-2 denominator in the Rational whereas DateTime defaults to nanosecond precision thereby getting a denominator of 86,400,000,000,000 (or a factor thereof) which is the number of nanoseconds per day. Perhaps it would be a nice feature to allow user specified precision on instances of these classes. That can already be done by passing in a Rational, but it could be convenient to have a separate parameter for this purpose. For example, to limit an instance to millisecond precision: Time.utc(1970, 1, 1, 0, 0, 12.86, precision: 1000) I know that millisecond precision would normally be specified as 1e-3, but that gets into floating point issues so I think it's cleaner to specify precision using the inverse. ## Not using precision (or precision=1) >> 12.86.to_r-12 => (60517119992791/70368744177664) ## Using precision=1000 >> Rational(12.86*1000).to_r/1000-12 => (43/50) This is not perfect since it still breaks when the integer portion in large, but it would work well for values representing seconds which are typically 60.0 (for leap seconds) or less. Maybe it would even be useful to add an optional precision parameter to Float#to_r, i.e. Float#to_r(precision=1), which would then return the equivalent of "Rational(self*precision, precision)". Interestingly, Ruby 1.9 has String#to_r which leads to this:: >> Time.utc(1970,1,1,0,0,12.86.to_s.to_r).strftime("%H:%M:%S,%L") => "00:00:12,860" Please let me know if this would be more appropriate for ruby-talk. Thanks, Dave