↑ で冪乗を計算できるようにする
四則演算の演算子は大体のプログラミング言語で共通して +
-
*
/
ですが、冪乗(累乗)演算子はプログラミング言語によって様々です。Ruby や Python などでは **
ですが、BASIC では ^
ですし、そもそも C や ES2016 以前の JavaScript など冪乗演算子が無い言語もよくあります。
参考:べき乗演算子 | 数値型 | プログラミング言語の比較 | hydroculのメモ
さて、一部界隈では冪乗を「↑」で表します。つまり $2 \uparrow 4=2^4=16$ です。というわけで Ruby でも「↑」で冪乗を計算できるようにしましょう。
とはいえ、いくら Ruby が自由度の高いプログラミング言語だと言っても、残念ながら自分で新たな演算子を作ることはできません。1 というわけで、「2 ↑ 4」と書きたいところを 2.↑ 4
のようにドットが入る点はご容赦ください。
class Integer
def ↑(n)
self ** n
end
end
puts 2.↑ 4 # => 16 (2 の 4 乗)
Ruby は 非 Ascii 文字もメソッド名に使えるので、こういうことができますね。2
Ruby 3.0 では endless メソッド定義を使って以下のようにも書けます。
def ↑(n) = self ** n
↑↑ でテトレーション
さて、冪乗とは乗算の繰り返しでした。
2 \uparrow 4 = 2^4 = \underbrace{2 \times 2 \times 2 \times 2}_{4つの2}
そうすると、「冪乗の繰り返し」という演算を考えたくなってきます。一部界隈ではそれを「テトレーション」と呼び、「↑↑」で表します。
2 \uparrow\uparrow 4 = \underbrace{2 \uparrow 2 \uparrow 2 \uparrow 2}_{4つの2} = 2^{2^{2^2}} = 2^{2^4} = 2^{16} = 65536
というわけで 2.↑↑ 4
でテトレーションを計算できるようにしたいと思います。それだけが目的なら、このように書けばいいでしょう。
def ↑↑(n)
Array.new(n, self).inject{ |result, item| item.↑ result }
end
なお、Array.new(n, self).inject(:↑)
と書きたいところですが、これではうまく行きません。冪乗は右結合なので $3 \uparrow 3 \uparrow 3 = 3 \uparrow (3 \uparrow 3) = 3 \uparrow 27 = 7625597484987$ となるはずなのですが、Array.new(n, self).inject(:↑)
だと計算順序上 $(3 \uparrow 3) \uparrow 3 = 27 \uparrow 3 = 19683$ の意味になってしまいます。
テトレーションのその先へ
さて、冪乗の繰り返しを行うテトレーションという演算を定義しました。そうすると、テトレーションの繰り返しという演算も定義したくなります。それは「ペンテーション」と呼ばれ、「↑↑↑」で表します。つまり、
2 \uparrow\uparrow\uparrow 4 = \underbrace{2 \uparrow\uparrow 2 \uparrow\uparrow 2 \uparrow\uparrow 2}_{4つの2}
ということですね。
ペンテーションをただ行うプログラムを書くなら、上のコードをコピペして少しだけいじって
def ↑↑↑(n)
Array.new(n, self).inject{ |result, item| item.↑↑ result }
end
とすれば良いでしょう。
ですが恐らくご想像のとおり、ペンテーションのさらに先「↑↑↑↑」(ヘキテーション)があります。さらに「↑↑↑↑↑」「↑↑↑↑↑↑」…と無限に矢印を伸ばしていくことができます。
先の方法で一つ上の演算をほぼコピペで定義していくことはできますが、「↑↑…(100個の↑)…↑↑」まで定義するのはさすがに手間です。こうした場合に考えられる方法として、define_method
を利用した動的なメソッド定義があります。
class Integer
2.upto(100) do |i|
define_method("↑" * i) do |n|
operator = "↑" * (i - 1)
Array.new(n, self).inject{ |result, item| item.send(operator, result) }
end
end
end
こうすれば簡単に「↑↑」から「↑↑…(100個の↑)…↑↑」まで定義できます。
しかし、こうして「↑↑…(100個の↑)…↑↑」までのメソッドを定義したところで、「↑↑…(101個の↑)…↑↑」が必要な機会があるかもしれません。3 たとえいくつの「↑↑…↑↑」が必要になっても、プログラムを書き換えることなく対応できるようにしたいものです。
そこで、Ruby 黒魔術の王道 method_missing
の出番です。4
class Integer
def ↑(n)
self ** n
end
def method_missing(name, *args)
return super if name.match?(/[^↑]/)
unless args.size == 1
raise ArgumentError,
"wrong number of arguments (given #{args.size}, expected 1)"
end
operator = name[0..-2]
n = args[0]
Array.new(n, self).inject do |result, item|
item.send(operator, result)
end
end
def respond_to_missing?(symbol, include_private)
symbol.match?(/[^↑]/) ? super : true
end
end
これでテトレーションだろうがペンテーションだろうがヘキテーションだろうが「↑↑…(1000個の↑)…↑↑」だろうが対応できます!
じゃあ、まずは軽く 3↑↑↑3 でも計算してみますかね!
> puts 3.↑↑↑ 3
bignum too big to convert into `long' (RangeError)
Oh...5
グラハム数を求めるプログラムを書く
さてせっかく矢印表記を定義したので、ギネスに載った巨大数として有名なグラハム数を求めるプログラムを書いてみましょう。もちろん 3↑↑↑3 よりさらに大きな数なので実際に求めることはできませんが、計算手順をプログラムとして表すだけなら可能です。
まず、俗にグラハム関数と呼ばれる関数 $g(n) = 3 \underbrace{\uparrow\uparrow\cdots\uparrow\uparrow}_{n個の\uparrow} 3$ を定義します。
def g(n)
3.send("↑" * n, 3)
end
method_missing を使って矢印表記を定義したことで、このようにグラハム関数も簡単に表せます。
グラハム数はグラハム関数を使って $G = \underbrace{g(g( \cdots g(g(4)) \cdots ))}_{64個の g( )}$ と表されますが、このような同じ関数の合成を $g^{64}(4)$ と冪乗みたいに書く場合があります。
だとすれば、Ruby においては同じ関数の合成を冪乗演算子 **
を使って method ** 64
みたいに書けるべきではないでしょうか。
class Method
def **(times)
Array.new(times, self).inject(:>>)
end
end
これでグラハム数を求めるプログラムの準備ができました。
g64 = method(:g) ** 64
puts g64.call(4)
実に簡潔にグラハム数を求めるプログラムが書けました!
最後に
Ruby の柔軟さは数学と相性が良いと思うので、数学屋の皆様は Ruby を使ってみてはいかがでしょうか。
-
できません…よね?やり方があれば是非ともコメントください。 ↩
-
このように組み込みクラスを拡張する場合は Refinement を使うのがマナーですが、このあと使うことになる
method_missing
は Refinement では使えないので、直接 Integer を拡張してしまいます。 ↩ -
たぶんありません。 ↩
-
公式ドキュメントにある通り、
method_missing
を定義する際はrespond_to_missing?
も定義しましょう。 ↩ -
この数はトリトリと呼ばれ、全宇宙の粒子をメモリとして使ったとしても到底表せないような数です。参考:仮面ライダービルドの数式 第16話 テトレーション ↩