From: David MacMahon Date: 2013-04-05T10:08:06+09:00 Subject: [ruby-core:54007] Re: [ruby-trunk - Bug #7829] Rounding error in Ruby Time On Apr 3, 2013, at 6:30 PM, Tanaka Akira wrote: > 2013/4/4 David MacMahon : >> >>>> f=57563.232824357045 >> => 57563.232824357045 >> >>>> puts "%016x\n"*5 % [f, f.to_r.to_f, f.to_s.to_f, f.to_s.to_r.to_f, f.rationalize.to_f].pack('D*').unpack('Q*') >> 40ec1b67734c10e7 >> 40ec1b67734c10e7 >> 40ec1b67734c10e7 >> 40ec1b67734c10e6 <=== String#to_r "error" >> 40ec1b67734c10e7 >> => nil > > I don't think that String#to_r is wrong. > > % ruby -e 'f=57563.232824357045 > p f, f.to_s, f.to_s.to_r > ' > 57563.232824357045 > "57563.232824357045" > (11512646564871409/200000000000) > > String#to_r is correct because > 57563.232824357045 == 11512646564871409/200000000000 in mathematical sense. > > The "error" is caused by Froat#to_s and it is expected. Of course you're right about String#to_r being correct. I think Float#to_s is correct as well. I think the problem is actually in Rational#to_f. Each distinct Float value has (or should have, IMHO) an unambiguous String representation such that f.to_s.to_f == f, discounting NaN and Infinity for which this relationship doesn't hold due to a limitation (bug?) of String#to_f. String#to_r works correctly as you pointed out. The problem occurs because the Rational returned by String#to_r is reduced. When converting the reduced fraction of this example to Float, Rational#to_f effectively computes: >> 11512646564871409.to_f/200000000000.to_f => 57563.23282435704 <=== does NOT equal original value instead of the un-reduced computation of: >> 57563232824357045.to_f/1000000000000.to_f => 57563.232824357045 <=== DOES equal original value As you can see, these two expressions do not product equal answers. This limitation of Rational#to_f can also be seen by using BigDecimal to convert from Rational to Float: >> class Rational >> def to_f_via_bd >> (BigDecimal.new(numerator)/denominator).to_f >> end >> end >> f=57563.232824357045 => 57563.232824357045 >> f.to_s.to_r.to_f => 57563.23282435704 <=== does NOT equal f >> f.to_s.to_r.to_f_via_bd => 57563.232824357045 <=== DOES equal f This same limitation also explains the problem I saw with Float.rationalize: >> 1.501852784991644e-17.rationalize.to_f => 1.5018527849916442e-17 <=== does NOT equal original value >> 1.501852784991644e-17.rationalize.to_f_via_bd => 1.501852784991644e-17 <=== DOES equal original value In an earlier message I wrote: "Converting Float to Rational is 'perfect' in that the conversion back to Float results in the same (limited precision) value." The above examples shows that this is not true. I think this could be considered a bug in Rational#to_f. > Anyway, I'm sure now that Float#rationalize should not be used > internally/automatically. I agree with this. Float#rationalize returns a Rational that is an approximation of the Float. This approximation is good enough that converting the Rational back to Float (avoiding intermediate rounding errors!) returns the original Float, but the Rational is NOT an exact representation. This is not a problem when using a single DateTime object, but performing math on a DateTime object that contains such an approximation seems like a bad idea. On the other hand, Float#to_s works well and String#to_r returns a Rational that exactly equals the floating point number represented by the String. What about changing num_exact() in time.c to handle Floats by converting to String and then to Rational rather than calling Float#to_r? > Anyone can use it as Time.utc(1970,1,1,0,0,12.860.rationalize) and it > may (or may not) solve problem, though. Or even better: Time.utc(1970,1,1,0,0,12.860.to_s.to_r). Dave