人間が読みやすい数値のフォーマット
Crystal 0.28.0では、人間が読みやすい数値のフォーマットのための新しい機能が追加されました。
以前は、様々なNumber
型に対して#to_s
を使用するか、せいぜいsprintf
を使用するしかありませんでした。どちらも出力フォーマットが限られており、コンピュータにとっての数値表現に重点が置かれています。人間にとっての可読性は考慮されていません。
ユーザーインターフェースに数値を表示する場合、人間が理解できる形式である必要があります。
数値のフォーマット
新しいNumber#format
メソッドをご紹介します。
このメソッドを使用すると、数値をカスタマイズ可能な形式で出力できます。これは、通常人間が数値を記述する方法を表現できます。
数値のスタイル
数値は、設定可能な小数点記号と桁区切り記号を使用してフォーマットできます。
123_456.789.format('.', ',') # => "123,456.789"
123_456.789.format(',', '.') # => "123.456,789"
123_456.789.format(',', ' ') # => "123 456,789"
123_456.789.format(',', '\'') # => "123'456,789"
桁区切りグループの桁数も設定可能です。これは、例えば万単位でグループ化された中国語の数値などに有効です。
123_456.789.format('.', ',', group: 4) # => "12,3456.789"
さまざまな文化的背景で使用されるスタイルは多種多様ですが、このメソッドは、最も一般的な形式のほとんどを表現できるだけの柔軟性を備えています。
世界の桁区切り記号は、国際的なスタイルの概要を示しており、小数点記号に関するWikipediaの記事は、このトピックに関するより詳細な情報を提供しています。
小数点以下の桁数
浮動小数点数は、人間が読める文字列に変換すると、小数点以下の桁数が多くなる可能性があります。ユーザー出力の場合、そのような詳細は通常は不要であり、小数点以下数桁を表示すれば十分です。
#format
メソッドで小数点以下の桁数を直接設定できます。
123_456.789.format(decimal_places: 2) # => "123,456.79"
123_456.789.format(decimal_places: 0) # => "123,457"
123_456.789.format(decimal_places: 4) # => "123,456.7890"
フォーマットする前に値を手動で四捨五入するよりも、これは簡単で、より多くのオプションが利用できます。
小数点以下の桁数はデフォルトで固定です。末尾のゼロは、only_significant
がtrue
の場合にのみ省略されます。
123_456.789.format(decimal_places: 6) # => "123,456.789000"
123_456.789.format(decimal_places: 6, only_significant: true) # => "123,456.789"
数値を人間が理解しやすい形式にする
異なる桁数の数値を関連付ける場合、意味のある方法で広範囲の値を表現することは困難です。
このような場合、値の大きさを数量詞を使用して表現するのが一般的です。
このために、Number#humanize
があります。これは、特定の有効桁数で数値を最も近い千の位に四捨五入します。
1_200_000_000.humanize # => "1.2G"
0.000_000_012.humanize # => "12.0n"
小数点のseparator
と桁区切りのdelimiter
について、Number#format
と同じ引数を持つため、スタイルをまったく同じ方法で設定できます。
有効桁数はprecision
で調整できます。ただし、デフォルト値の3
は、ほとんどのアプリケーションに適しています。siginficant
がtrue
の場合、precision
の値は、数値の値に関係なく、小数点以下の桁数の固定量です。
数量詞は、デフォルトではSI接頭辞(k
、M
、G
など)ですが、リストまたはprocを提供することで完全に設定可能です。
カスタマイズ可能な数量詞
Number#humanize
は、特定の桁数の桁数と数量詞を計算するproc引数を取ることができます。
次の例は、単位指定子を含むメートル単位で長さをフォーマットする方法を示しています。これは、0.01
と0.99
の間の値に一般的な*センチメートル*単位を使用することで、デフォルトの実装から派生しています(一般的なマッピングでは*ミリメートル*と表現されます)。他のすべての値は、一般的なSI接頭辞を使用します(Number.si_prefix
によって提供されます)。
def humanize_length(number)
number.humanize do |magnitude, number|
case magnitude
when -2, -1 then {-2, " cm"}
else
magnitude = Number.prefix_index(magnitude)
{magnitude, " #{Number.si_prefix(magnitude)}m"}
end
end
end
humanize_length(1_420) # => "1.42 km"
humanize_length(0.23) # => "23.0 cm"
humanize_length(0.05) # => "5.0 cm"
humanize_length(0.001) # => "1.0 mm"
バイトを人間が理解しやすい形式にする
3番目のメソッドは、Int#humanize_bytes
で、バイト数(たとえば、メモリサイズ)を一般的な形式でフォーマットできます。IEC(Ki
、Mi
、Gi
、Ti
、Pi
、Ei
、Zi
、Yi
)とJEDEC(K
、M
、G
、T
、P
、E
、Z
、Y
)の両方の接頭辞をサポートしています。
1.humanize_bytes # => "1B"
1024.humanize_bytes # => "1.0kiB"
1536.humanize_bytes # => "1.5kiB"
524288.humanize_bytes(format: :JEDEC) # => "512kB"
1073741824.humanize_bytes(format: :JEDEC) # => "1.0GB"
このメソッドの実装は、Numer#humanize
に基づくカスタム形式の別の例です。
まとめ
これらの新しいメソッドは、数値を読者にとって見やすくするための優れた機能を提供します。
特定のロケールに対するスタイルマッピングは提供していません。これは、専用のI18Nライブラリに任せるべき重要なタスクです。ただし、これらのメソッドは、そのようなライブラリが基盤とすることのできる便利な構成要素です。また、異なるロケールをサポートする必要がない場合は、すぐに使用できます。
ただし、実装は完璧ではありません。ローカライズは複雑で、正しく行うのは困難です。いつものことですが、問題は細部に宿っています。たとえば、桁区切り記号とグループサイズは設定可能ですが、固定値です。インドの記数法はこの方法では表現できません。また、アラビア数字のみがサポートされています。そして、おそらくもっと特殊な動作が必要になるケースがたくさんあるでしょう。
しかし、おそらく一般的なユースケースの90%以上に対応でき、すでに多くの場所で役立っています。そして、常に改善の余地があります。
詳細な背景情報は、これらの機能をもたらしたPRにあります。
また、より一般的な観点から数値のフォーマットに関する良い読み物として、Hjalmar Gislasonによる機械と人間のための数値のフォーマットがあります。