2013/2/12 loirotte (Philippe Dosch) <loirotte / gmail.com>:

> Bug #7829: Rounding error in Ruby Time
> https://bugs.ruby-lang.org/issues/7829

> Even if I know the precision errors related to the implementation of IEEE 754 floating values, I'm very surprised of:
>
> irb(main):001:0> Time.utc(1970,1,1,0,0,12.860).strftime("%H:%M:%S,%L")
> => "00:00:12,859"
>
> The fact is that I obtain:
>
> irb(main):002:0> Time.utc( 1970, 1, 1, 0, 0, 12.860 ).subsec
> => (60517119992791/70368744177664)
> irb(main):003:0> Time.utc( 1970, 1, 1, 0, 0, 12.860 ).subsec.to_f
> => 0.8599999999999994

1. Ruby parser converts 12.860 to
12.8599999999999994315658113919198513031005859375.

  12.860 is converted to IEEE 754 double value at Ruby parser.
  The IEEE 754 double value is actually
12.8599999999999994315658113919198513031005859375.

  % ruby -e 'puts "%.100g" % 12.860'
  12.8599999999999994315658113919198513031005859375

  Or 0x1.9b851eb851eb80000000p+3, in hexadecimal.

  % ruby -e 'puts "%.20a" % 12.860'
  0x1.9b851eb851eb80000000p+3

  So Time.utc takes the value and Time#subsec returns the value under the point.

  % ruby -e 'v = 12.860.to_r - 12; puts v, v.to_f'
  60517119992791/70368744177664
  0.8599999999999994

  The Time object records the value given as is.

  A proposal to change (fix) this behavior:
  http://www.slideshare.net/mrkn/float-is-legacy

2. Time.strftime("%L") doesn't round, but floor.

  %L (and %N) in Time.strftime doesn't round the value but floor the value.

  Since 3-digits under the point of
0.8599999999999994315658113919198513031005859375
  is "859", %L shows "859".

  rounding is not appropriate here.
  It is clearely unexpected that %L for 0.99999 shows "1000".

3. Use Time#round.

  There is a method to rounding Time: Time#round.

  If you needs a Time value rouinding 3-digits under the second,
  use time.round(3).

  % ruby -e 'p Time.utc(1970,1,1,0,0,12.860).round(3).strftime("%H:%M:%S,%L")'
  "00:00:12,860"
-- 
Tanaka Akira