1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

数値クラスに逆数を取得するメソッドを追加(+refinementsの勉強)

Last updated at Posted at 2018-12-26

Rubyで時々「数値の逆数くらいメソッドで取得したい」と思うことがある。メソッドがあれば、数式を書いて括弧で括ったりする手間が省ける。

(1 / num).abs
# ↓
num.inverse.abs

無ければ作ればいいだけだが、いい機会なのでrefinementsを初めて利用してみる。ついでにモジュラ逆数の計算方法を写経する。

(追記)せっかく作ったのでgem化した。refinements無しも添付したため、利用は選択可能。
https://rubygems.org/gems/numeric_inverse

コード

refinementsによるメソッド定義方法は簡単だった。

  • 通常のパッチでは単に既存クラスを開く、つまり class XXX; ...; end と書く。(モジュールも可)
  • refinementsでは適当なモジュールを作成し、その中で refine(XXX) do; ...; end と書く( Module#refine を呼び出す)。 ... の部分は変わらない。

逆数を求めるメソッド #inverse は以下のように実装する。

  • 基本的には親クラスである Numeric1/self を計算するメソッドを追加するだけでいい
    • Integer, Rational, Float, Complex など子クラスはこれを呼び出すことになる
    • Integer#/ は整商を求めてしまうので、 #/ でなく #quo を明示的に使う方が良い
  • せっかくなので、整数クラス Integer ではオーバーライドして工夫をしておく
    • 整数論では「モジュラ逆数」というものが存在するので、引数に法を与えたらそちらを計算できるようにする
    • 通常の逆数も、わざわざ super() を呼んで割り算をせず、直接 Rational を作成してしまえばいい
  • メソッド名が長いと使うときに結局面倒なので、別名 #inv も作っておく
numeric_inverse.rb
module NumericInverse
	refine Numeric do
		##
		# Returns its inverse.
		#
		# @return [Numeric] inverse
		#
		def inverse
			1.quo(self)
		end

		alias inv inverse
	end

	refine Integer do
		##
		# Returns its inverse.
		#
		# @overload inverse
		#   @return [Rational] inverse
		# @overload inverse(m)
		#   @param m [Integer] modulus
		#   @return [Integer] modular multiplicative inverse
		#   @raise [ArgumentError] if +m+ is not coprime to +self+
		#
		def inverse(m = nil)
			return Rational(1, self) if m.nil?

			# extended Euclidean algorithm
			a, b, u, v = self, m, 1, 0
			until b.zero?
				a, (t, b) = b, a.divmod(b)
				u, v = v, u - t * v
			end

			# u is an inverse iff self.gcd(m) == 1
			if a.abs != 1
				raise ArgumentError,
				      "modulus is not coprime to the receiver" 
			end

			u = -u if a < 0
			u % m   # 0 <= u < m || m < u <= 0
		end

		alias inv inverse
	end
end

一応yardocを書いてみたが、ドキュメントはうまく生成されなかった。(yard 0.9.16)

利用

main.using または Module#using を呼び出すことで、以降それに応じた範囲内(ファイル内/モジュール定義内)で利用できるようになる。

# モジュールを定義しただけでは使えない
require './numeric_inverse'
p Rational(1227, 2018).inv   #=> NoMethodError

# `#using` を呼び出すと、スコープ内で使えるようになる
using NumericInverse
p Rational(1227, 2018).inv   #=> (2018/1227)

一緒に定義した Integer#inverse もきちんと機能している。

require './numeric_inverse'
using NumericInverse

# 整数の普通の逆数
p 63.inv        #=> (1/63)
p 63.inv * 63   #=> (1/1)

# モジュラ逆数
p 63.inv(100)              #=> 27
p 63.inv(100) * 63         #=> 1701
p 63.inv(100) * 63 % 100   #=> 1

irbでのエラー対策

irbで上記コードを試すと、 using のところでエラーが出る。

irb(main):001:0> require './numeric_inverse'
=> true
irb(main):002:0> using NumericInverse
RuntimeError: main.using is permitted only at toplevel
	from (irb):2:in `using'
	from (irb):2
	from /usr/bin/irb:11:in `<main>'

これを避けるには、irb起動時に --context-mode=1 オプションを追加する。(3未満の数なら良いが、0だと例外バックトレースが長くなるとのこと。)

参考

1
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?