为人类格式化漂亮数字
在 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"
在不同的文化环境中使用着许多不同的样式,而这种方法足够灵活,可以表示大多数常见的格式。
世界如何分隔数字 提供了国际样式的概述,而 维基百科关于小数点分隔符的文章 提供了一些关于此主题的更多见解。
小数位数
浮点数在转换为人类可读字符串时可能会产生很多小数位数。对于用户输出,这种细节通常会分散注意力,显示几个小数位数就足够了。
小数位数可以在 #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"
它与 Number#format
具有相同的用于小数点 separator
和千位 delimiter
的参数,因此样式的配置方式完全相同。
可以使用 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"
使字节人性化
第三种方法是 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。