On Apr 5, 2013, at 3:34 PM, Tanaka Akira wrote:

> 57563.232824357045 is not representable as a Float.

Sorry, poor wording on my part.  What I meant was that the Float created =
from the floating point literal 57563.232824357045 is displayed by =
#inspect and #to_s as "57563.232824357045".  IOW, calling #to_s on the =
57563.232824357045 literal returns a string that represents the same =
value as the literal even though the underlying bits store a different =
value.  This is not true for all literals.  For example, calling #to_s =
on the literal 57563.232824357044 will also return the string =
"57563.232824357045".

Essentially, Float#to_s returns the shortest decimal string (or one of =
several equally short strings) that is not closer to any other Float =
value.  A Rational that exactly equals the value represented by that =
decimal string is also not closer to any other Float value, so =
converting it to Float via #to_f would be expected to return the =
original Float value, but that is not always the case.

> f.to_r.to_f =3D=3D f is true.

Yes, I now realize that this will always be true.  Even though =
Rational#to_f rounds the numerator and denominator to double precision =
before dividing and then rounds the quotient after dividing, this will =
never cause problems for Rationals created via Float#to_r (assuming =
Bignum#to_f works sanely) due to the way Float.to_r works.

I would also like f.to_s.to_r.to_f =3D=3D f to always be true.  This is =
is not always the case because f.to_s.to_r has factors of 2 and 5 in the =
denominator, so the reduced Rational in this case runs the risk of #to_f =
not returning the closest approximation to the original value.  In =
extreme cases, f.to_s.to_r.to_f can return values two representable =
values away from the original:

>> f1=3D4.7622749438937484e-07
=3D> 4.7622749438937484e-07
>> f2=3D4.762274943893749e-07
=3D> 4.762274943893749e-07
>> f1 < f2
=3D> true

After converting to String then to Rational then back to Float, f2 =
retains its original value, but f1 becomes larger than f2!

>> f1srf=3Df1.to_s.to_r.to_f
=3D> 4.7622749438937494e-07
>> f2srf=3Df2.to_s.to_r.to_f
=3D> 4.762274943893749e-07
>> f1srf > f2srf <=3D=3D NB: GREATER THAN
=3D> true

Getting back to the original post, Time.new converts its "seconds" =
parameter using num_exact(), which converts Floats (among other types) =
to Rational using #to_r.  It then divmod's the value by 1 to get integer =
seconds and fractional seconds.  The complaint in the original post was =
that using the literal 12.68 for the seconds parameter led =
Time#strftime's %L specifier to show 679 milliseconds rather than 680.

In an earlier post, I suggested modifying num_exact to convert Floats to =
Rational via Float#rationalize, but now I think that converting to =
String and then to Rational (or preferably a more direct approach that =
does the equivalent) would lead to the best user experience.

Converting Floats passed as "seconds" using Float#to_r assumes that =
people have 53 bits of precision in their "seconds" values.  I suspect =
that this is not true for the vast majority of users.  More likely they =
have millisecond, microsecond, or maybe nanosecond precision.  People =
with higher (or non-decimal) precision will (or at least should, IMHO) =
be using Rationals already.  Converting Float "seconds" to String and =
then to Rational makes the most sense (IMHO) as it preserves the decimal =
precision of the input.  The (debatable) rounding issue of Rational#to_f =
is not really problematic for this use case since it does not affect =
values that are likely to be used for seconds.

Dave