読者です 読者をやめる 読者になる 読者になる

倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

commons-math 解読 (1) : MathUtils に定義されている static メソッド〜整数関数編〜

以前から気になっていた「commons-math」の API を気ままにいじっていきます。 当面は、簡単に使える org.apache.commons.math.util.MathUtils に定義されている static メソッドをあれこれ。

正直、commons-math を使うコードを Java で書くのは億劫なので、サンプルコードは Groovy で書いていきます(パフォーマンスなんてクソ喰らえ!)。 ホントは Scala の方がいい気もしますが、まだ勉強中なので断念。

で、この記事中のサンプルコードには、最初に

@Grab(group='org.apache.commons', module='commons-math', version='2.1')
import org.apache.commons.math.util.MathUtils

の2行がついていると思ってください。 また、Grape の設定もされてる前提で。 もちろん、別途 commons-math の Jar ファイルをとってきて CLASSPATH 上に配置しても構いませんが。

また、『Groovyイン・アクション』みたく assert を多用していますが、見やすさのために double (Double) 値も普通に等値評価してます。 くれぐれも良い子はマネしないでください*1

MathUtils クラス

MathUtils は java.lang.Math のようなユーティリティ・クラスで、たくさんの数学関数が static メソッドとして定義されています。 java.lang.Math のメソッドを拡張したものもあれば、java.lang.Math に追加されたためダブっているものもあります。 1つの記事に全部のメソッドを書くのは大変なので、独断と偏見にて以下のように分類します:

  1. 整数関数
  2. 指数・対数関数
  3. 冪等関数
  4. 幾何学的関数
  5. 機械制約関数

結構適当に分類して名前つけてます。 あまり細かいところは気にしないでね。 今回は整数関数。

整数関数

今回は下表に載っている関数を見ていきます:

メソッド 返り値の型 説明
factorial(int n)
factorialDouble(int n)
factorialLog(int n)
long
double
double
階乗 { n! }
binomialCoefficient(int n, int k)
binomialCoefficientDouble(int n, int k)
binomialCoefficientLog(int n, int k)
long
double
double
二項係数 { {}_nC_r }
gcd(int p, int q)
gcd(long p, long q)
int
long
最大公約数 (GCD : Greatest Common Divisor)
lcm(int a, int b)
lcm(long a, long b)
int
long
最小公倍数 (LCM : Least Common Multiple)

整数関数というより自然数関数ですが何か?

階乗 (Factorial) { n! }

まずは階乗 (factorial)。 数学っぽく書くとこんな定義:

  { \displaystyle
\begin{align*}
    n! = n\cdot(n-1)\cdot(n-2)\cdots2\cdot1
\end{align*}
}

{ n } が大きくなると { n! } は急激に大きくなるので、返り値は long (factorial() の場合)もしくは double (factorialDouble() の場合)になっています。 加えて、それらの対数値を返すメソッドも定義されています。 サンプルコードはこんな感じ:

import static java.lang.Math.log

assert MathUtils.factorial(10) == 10*9*8*7*6*5*4*3*2*1    // 3628800
assert MathUtils.factorialDouble(10) == 10*9*8*7*6*5*4*3*2*1    // 3628800.0d
assert MathUtils.factorialLog(10) == log(10*9*8*7*6*5*4*3*2*1)    // 15.104412573075516d

factorialDouble() メソッドは Double Factorial (n!!) ではないので注意。

二項係数 (Binomial Coefficient) { {}_n C_r }

次は二項係数 (binomial coefficient)。 数学的な表記はいくつかあります:

  { \displaystyle
\begin{align*}
    {}_nC_r = \binom{n}{r} = \frac{n!}{(n-r)!r!}
\end{align*}
}

メソッドのポストフィックス 'Double', 'Log' は factorial の場合と同じ:

import static java.lang.Math.log

assert MathUtils.binomialCoefficient(10, 3) == (10*9*8).intdiv(3*2*1)    // 120
assert MathUtils.binomialCoefficientDouble(10, 3) == (10*9*8)/(3*2*1)    // 120.0d
assert MathUtils.binomialCoefficientLog(10, 3) == log((10*9*8)/(3*2*1))    // 4.787491742782046d

Groovy では整数の割り算は intdiv() メソッドを使うんでした。

最大公約数 (GCD) ・最小公倍数 (LCM)

最後は最大公約数 (GCD : Greatest Common Devisor) と最小公倍数 (LCM : Least Common Multiple)。 えーと、定義はいいかな。

assert MathUtils.gcd(210, 196) == 14
assert MathUtils.gcd(2*3*5*7, 2*2*7*7) == 2*7
// 2*3*5*7 = 210, 2*2*7*7 = 196

assert MathUtils.lcm(210, 196) == 2940
assert MathUtils.lcm(2*3*5*7, 2*2*7*7) == 2*2*3*5*7*7
// 2*2*3*5*7*7 = 2940

各行のメソッドの引数に使っている数字は同じです。 書き方を変えてみましたが意味は分かるかな?

Javaによるアルゴリズム事典

Javaによるアルゴリズム事典

*1:MathUtils には double 値を指定した精度で等値評価する equals メソッドが定義されています。