Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyのRationalクラスで分数と有理数を操作する方法

正直に告白します。私は浮動小数点数があまり好きではありません。コンピュータにとっては確かに便利な存在ですが、人間の目には次のような結果は理解しがたいものです。

129.95 * 100
# => 12994.999999999998

この結果は数学的な美しさに反しているだけでなく、ユーザー体験(UX)の観点からも好ましくありません。

もしレシピに「小麦粉を0.37211927843カップ計ってください」と書いてあったら、読んだ人はきっと著者のセンスに苦笑しながら、実際には3分の1カップを測ることになるでしょう。

多くの人は、任意の桁の小数よりも分数の方がはるかにイメージしやすいものです。そのため、アプリケーションで人間に数値を伝えたいのであれば、分数形式での表現を検討する価値があります。

RubyのRationalクラスは、有理数を扱うための強力なツールです。有理数同士の計算ができるだけでなく、複雑な浮動小数点数に近い「きれいな分数」を見つけることもできます。早速見ていきましょう。

有理数(Rational)とは?

ここでいう「有理数」とは、要するに「分数」のことです。有理数は分子と分母という2つの部分で構成されます。

1/2 # 分子が1、分母が2
5   # 分子が5、分母が1

Rubyでは、有理数は整数や浮動小数点数と同じように独立したデータ型として扱われます。新しい有理数オブジェクトを作る方法はいくつかあります。

3/2r              # この記法はRuby 2.1で導入された
1.5.to_r          # Floatはto_rで有理数に変換できる
"3/2".to_r        # 文字列も変換できる
Rational('3/2')   # 昔はこのように書くしかなかった
Rational(3, 2)    # ...当時の生活がいかに大変だったかお分かりでしょうか?

基本的な四則演算

2つの有理数を加減乗除すると、その結果も必ず有理数になります。

2/3r + 1/3r
# => (1/1)
2/3r - 1/3r
# => (1/3)
2/3r * 1/3r
# => (2/9)
(2/3r) / (1/3r) # インタプリタの混乱を避けるため括弧が必要
# => (2/1)

その他の演算子もほぼ期待どおりに動作します。**(べき乗)や、><といった比較演算子も問題なく使えます。

基本的なルールとして、結果を分数にするには入力の両方が分数である必要があります。ただし例外がひとつあり、それは整数です。すべての整数は有理数でもあるため、Rubyは賢いことに、整数との演算結果も有理数として返してくれます。

2/3r + 2
# => (8/3)

近似値を求める

有理数の最も便利な性質のひとつは、暗算しやすい形に近似できることです。これを活かすには、分数をシンプルに保つことが重要です。3320774221237909/2251799813685248のような巨大な分数ではなく、3/2のように。

幸い、Rubyには「正確だけど醜い数値」を「多少不正確だけど美しい数値」へ変換する手軽な方法が用意されています。それがrationalizeメソッドです。

使い方は次のとおりです。

# 正確だが醜い
(1.47472).to_r
=> (3320774221237909/2251799813685248)

# 精度は落ちるが美しい
(1.47472).to_r.rationalize(0.05)
=> (3/2)

rationalizeメソッドは引数を1つ受け取ります。これは許容誤差、つまり「簡潔さと引き換えに許容できる精度の幅」を指定します。

このメソッドは、指定した許容範囲内で最も分母の小さい数を見つけます。具体例を見てみましょう。

# 5/10と7/10の間で、分母が最小の数は?
(6/10r).rationalize(1/10r)
# => (1/2)

# 11/20と13/20の間では?
(6/10r).rationalize(1/20r)
=> (3/5)

# ..そして1/10と11/10の間では?
(6/10r).rationalize(1/2r)
=> (1/1)

もっと大ざっぱな近似

分数に対応する整数や浮動小数点数が得られれば十分な場合は、いくつかの選択肢があります。

# 最も近い整数を返す
(6/10r).round
# => 1

# 小数点以下を切り捨てる
(12/10r).to_i
# => 1

有理数の制限事項

Rubyで有理数を扱う際には、いくつかの注意点があります。まず当然ながら、ゼロで割ることはできません。

4/0r
ZeroDivisionError: divided by 0

また、無理数を有理数として扱おうとすると、奇妙な挙動に遭遇します。

# えっ、2の平方根は無理数のはずでは?
Rational(Math.sqrt(2))
# => (6369051672525773/4503599627370496)

# 円周率PIも無理数だったはずですが。
Rational(Math::PI)
# => (884279719003555/281474976710656)

本来なら、無理数を有理数として扱おうとした時点で例外が発生すると期待するところです。しかし残念ながら、Rubyはそこまで賢くありません。代わりに、これらの無理数の浮動小数点近似値をそのまま有理数へ変換してしまうのです。致命的な問題ではありませんが、知っておくべき挙動といえます。

まとめ

浮動小数点数の丸め誤差は、金額計算などをはじめ深刻なバグの原因になり得ます。Rationalクラスを使えば、数値を分数のまま正確に扱えるため、誤差を気にせず計算できます。さらにrationalizeを組み合わせれば、人間にとって読みやすい「きれいな分数」への変換も簡単です。数値をユーザーに見せる場面や、厳密な計算が求められる場面では、ぜひ有理数の活用を検討してみてください。

  1. Rubyでマージソートを徹底解説!分割統治と再帰で学ぶ効率的なソートアルゴリズム

    本記事は、Rubyでさまざまなソートアルゴリズムを実装するシリーズの第3回です。第1回ではバブルソート、第2回では選択ソートについて取り上げました。 これまでの連載でも触れてきたように、データをどう並べ替えるかを理解することは、ソフトウェアエンジニアにとって欠かせないスキルのひとつです。幸いなことに、Rubyのような高水準言語には、配列を効率的にソートできる組み込みメソッドがすでに備わっています。たとえば、配列に対して .sort を呼び出すと、内部ではクイックソートが使われています。本記事では、クイックソートとよく似たアルゴリズムである「マージソート」について学んでいきます。この2つは

  2. コマンドプロンプト入門:ファイルとフォルダを操作する基本コマンド

    コマンドプロンプト入門:ファイルとフォルダを操作する基本コマンド エクスプローラーを使わずにファイルを操作できたら便利だと思ったことはありませんか?開発者やITエンジニアの方なら、ターミナルでの作業は日常茶飯事でしょう。しかし、開発者でなくても、基本的なコマンドラインの知識は持っておいて損はありません。 このガイドでは、ターミナル作業で役立つ、よく使われるファイル操作コマンドを紹介します。 フォルダ間の移動には「cd」と「dir」 コマンドプロンプト(PowerShell や Windows Terminal でも同様)でファイルシステム内を移動するには、cd と dir という2つの基本コマ