跳至內容
GitHub 儲存庫 論壇 RSS 新聞訂閱

為人類格式化美觀的數字

Johannes Müller

在 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_significanttrue 時,才會省略尾隨的零。

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 可能已經非常適合大多數應用程式。當 siginficanttrue 時,precision 的值是固定的十進位數字,與數字的值無關。

量詞預設為 SI 字首(kMG 等),但它們是完全可配置的,可以透過提供清單或 proc 來配置。

可自訂的量詞

Number#humanize 可以接受 proc 參數,該參數會計算特定數量級的位數和量詞。

以下範例顯示如何以公制單位格式化長度,包括單位指示符。它透過對介於 0.010.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(KiMiGiTiPiEiZiYi)和 JEDEC(KMGTPEZY)字首。

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 的為機器和凡人格式化數字