number_to_delimited の符号処理バグを修正
number_to_delimited が符号("-"/"+")付きの数値を整形する際、桁区切り文字が符号直後に誤って挿入されるバグを解消しました。負数やプラス記号付き文字列が正しくフォーマットされることで、請求書やレポートへの誤出力リスクが排除されます。
背景
このバグは NumberToDelimitedConverter#parts が整数部を 3 桁ごとに分割するロジックで、offset = left.size % 3 を用いた高速パスがデフォルト化されたことに起因します。符号が left 文字列の先頭に含まれるため、最初のグループが符号単体となり、区切り文字が "-," の形で出力されていました。結果として number_to_delimited(-123456) が "-,123,456" という誤った文字列を返し、 downstream の number_to_rounded や number_to_human でも同様の破損が発生しました。
技術的な変更
NumberToDelimitedConverter#parts で整数部の先頭が符号かどうかを判定し、符号があれば一時的に除去してから 3 桁区切りを行い、最後に符号を再結合するよう修正しました。これにより符号は区切り処理の対象から外れ、正しいフォーマット "-123,456" が得られます。変更は activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb の数行に留まり、既存の delimiter_pattern 正規表現パスはそのまま残しています。
- offset = left.size % 3
- if offset > 0
- left_parts << left[0, offset]
+ sign = left.slice!(0) if left.start_with?("-", "+")
+ offset = left.size % 3
+ if offset > 0
+ left_parts << left[0, offset]
end
# ...省略...
- left = left_parts.join(options[:delimiter])
+ left = "#{sign}#{left_parts.join(options[:delimiter])}"
テストスイートにも activesupport/test/number_helper_test.rb に符号付きケースを網羅するテストを追加し、負数・正数・文字列形式の両方で期待通りの出力が得られることを検証しました。新しいテストは test_to_delimited_with_negative_numbers と test_to_delimited_with_leading_plus_sign の二つで、様々な桁数・小数部の組み合わせをチェックしています。
+def test_to_delimited_with_negative_numbers
+ assert_equal "-123,456", NumberHelper.number_to_delimited(-123456)
+ # ...他ケース省略...
+end
+
+def test_to_delimited_with_leading_plus_sign
+ assert_equal "+123,456", NumberHelper.number_to_delimited("+123456")
+ # ...他ケース省略...
+end
設計判断
符号除去・再付加のロジックは NumberToCurrencyConverter が採用している方式と同様で、既存の API を変更せずに内部実装だけを調整するという最小侵襲の方針が取られました。delimiter_pattern の正規表現パスは残したため、従来の高速パスとレガシーパスの両方が同等に動作し、後方互換性が確保されています。
この修正は number_to_rounded や number_to_human といった他のヘルパーが内部で同じコンバータを利用している点でも効果を及ぼし、追加の変更なしで広範囲にわたるフォーマット不整合が解消されました。結果として、金額や統計情報の表示に関わる全てのユースケースで安全に区切り文字を使用できるようになります。
まとめ
符号付き数値の区切り処理バグを根本的に修正し、負数・プラス記号付き文字列が正しく整形されるようになりました。実装変更は局所的で後方互換性を損なわず、関連ヘルパー全体にプラスの影響を与えることから、Rails の数値ヘルパー群の信頼性が向上したと言えます。