Duration#in_* がサブ秒精度を切り捨てていた不具合を修正
ActiveSupport::Duration の in_* 系メソッドが秒未満の小数部分を失うバグを、内部計算を in_seconds から正確な value に変更し、サブ秒単位の精度を保持できるようにしました。これにより、ドキュメント通り「浮動小数点で返す」契約が全ての単位変換で満たされます。
背景
ActiveSupport::Duration#in_* 系メソッドは、内部で in_seconds(to_i のエイリアス)を用いて秒単位に変換していました。to_i は整数化するため小数秒が切り捨てられ、結果として in_minutes から in_years までのメソッドがサブ秒精度を失っていました。例として、90.5.seconds.in_minutes が 1.5 と返り、本来期待される 1.508333333... と大きくずれていました。この挙動は「float を返す」ことがドキュメントで保証されているにも関わらず、実装上の不一致となっていました。
技術的な変更
activesupport/lib/active_support/duration.rb で in_* メソッド群 が in_seconds から value に置き換えられました。value は元の @value(小数秒を含む)そのものを保持しているため、除算前に切り捨てが行われません。具体的な差分は以下の通りです。
変更前:
def in_minutes
in_seconds / SECONDS_PER_MINUTE.to_f
end
変更後:
def in_minutes
value / SECONDS_PER_MINUTE.to_f
end
同様の修正が in_hours, in_days, in_weeks, in_months, in_years にも適用されています。さらに、activesupport/test/core_ext/duration_test.rb に test_in_units_preserve_sub_second_precision が追加され、90.5 秒や 0.5 秒といったサブ秒を含むケースで期待精度が保たれることを自動テストで担保しています。
設計判断
in_seconds(整数化)をそのまま使用し続ける 方針は、in_seconds が整数を返すことを意図した API である点で正当です。一方、in_* 系は「float を返す」ことが求められるため、value を直接利用する 改善が最も自然な選択でした。新たなメソッドや設定キーを導入せず、既存のインターフェースと内部変数を再利用したため、後方互換性に影響はありません。既存の整数単位変換は依然として to_i の挙動を踏襲し、ドキュメントと実装の乖離が解消されたことが主な設計上の利点です。
まとめ
今回の修正は、ActiveSupport::Duration の in_* 系メソッドがサブ秒精度を保持できるように内部計算を変更した 低リスクのバグフィックス です。ドキュメント通りの浮動小数点結果が得られ、既存の整数返却メソッドへの影響はありません。追加されたテストにより、将来的な回帰も防止されています。