Rails: 通貨は浮動小数点ではなくRubyのmoney gemで表現すること(翻訳)
アプリケーションで金額を管理しなければならなくなることはざらにあります。何の話かというと、Rubyコミュニティにある素晴らしいオープンソースのソリューションを使えば、自分で開発するよりもずっと良い結果を得られるのです。
以下のようにするのではなく
金額を表現するときにFloat
やBigDecimal
を使う。
# マイグレーション
add_column :products, :price, :decimal
class Product < ApplicationModel
def price_to_s
"$ #{price.round(2).to_s("F")} USD"
end
end
product.price = 5
#=> 0.5e1
product.price_to_s
#=> "$ 5.0 USD"
以下のようにする
Rubyのmoney
gemを使う。Railsの場合はmoney-rails
gemを使う1。
# マイグレーション
add_monetize :products, :price
class Product < ApplicationModel
monetize :price_cents
end
product.price = Money.from_amount(5, "USD")
#=> #<Money fractional:500 currency:USD>
product.price.format
#=> "$5.00"
そうする理由
最初に申し上げますが、通貨の値を表現するときに浮動小数点(floating-point)の実装を利用するのは絶対禁物です。浮動小数点ではコンピュータ内部の表現形式に由来する丸め誤差が発生する可能性があります(参考)。とにかく絶対に使わないでください。
decimal
はより直感に沿っていて振る舞いも通貨らしくなるので、この問題をある程度解決します。
money gemは、十分実績のある推奨事項を背後のデータ構造で強制するだけではなく、他にもメリットがあります。他の優秀なgemと同様、このgemはシンプルでありながら洗練されており、実地のバトルテストで実績を積んだValue Objectパターンを金額や通貨の表現に提供します。
money gemの磨き抜かれた#format
実装は、Rubyの標準的な国際化(i18n)バックエンドと連携して、金額を文字列として適切(かつ柔軟)に表示する機能を備えています。
また、組み込みの通貨変換機能で通貨を手軽に変換できます。為替レートを独自に指定することも、常に更新されている為替レートサービスにリンクすることも可能です。
そうしない理由があるとすれば
アプリケーションがもっと複雑な金融モデリングの世界を扱う場合は、独自の金額表現をコード化したいこともあるでしょう。端数計算を行う場合は、money gemでは洗練度のレベルに達しないかもしれません。
関連記事
- 訳注: money-rails gemは内部でmoney gemに依存しています。 ↩
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。