On Feb 20, 2013, at 7:46 AM, loirotte (Philippe Dosch) wrote:

> Typing this instruction:
>=20
> irb(main):001:0> Time.utc(1970,1,1,0,0,12.860).strftime("%H:%M:%S,%L")
> =3D> "00:00:12,859"
>=20
> 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
=3D> 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")
=3D> "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')
=3D> "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=3D1)
>> 12.86.to_r-12
=3D> (60517119992791/70368744177664)

## Using precision=3D1000
>> Rational(12.86*1000).to_r/1000-12
=3D> (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=3D1), 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")
=3D> "00:00:12,860"

Please let me know if this would be more appropriate for ruby-talk.

Thanks,
Dave