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

倭算数理研究所

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

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

commons-math

今回は冪等関数(一覧)。

冪等 (idenpotent)」とは、1度写像を施すとその後何度その写像を施しても値が変わらない性質のこと*1。 数学っぽく書くと、任意の { x } に対して { f(f(x)) = f(x) } が成り立つ関数 { f } のことです。 射影 (projection) なんかがそういう性質を持ってます。 とか言っても、今回は別にこの性質がどうだこうだって話ではありません。 単なるカテゴリー分けの都合。

今回扱う関数は下表の通り:

メソッド 返り値の型 説明
sign(byte x)
sign(short x)
sign(int x)
sign(long x)
sign(float x)
sign(double x)
byte
short
int
long
float
double
符号関数
indicator(byte x)
indicator(short x)
indicator(int x)
indicator(long x)
indicator(float x)
indicator(double x)
byte
short
int
long
float
double
符号関数
(引数が0の場合のみ符号関数と値が異なる)
round(float x, int scale)
round(float x, int scale, int roundingMethod)
round(double x, int scale)
round(double x, int scale, int roundingMethod)
float
float
double
double
四捨五入など

sign(), indicator() は各プリミティブ型に対してオーバーロードされています。 引数の型と返り値の型は同じになっています。

符号関数 sign

まずは符号関数 (sign)。 定義はこんな感じ:

  { \displaystyle
\begin{align*}
    \textrm {sign}(x) =
    \begin{cases}1
        & (x > 0)\\
        0 & (x = 0) \\
        -1 & (x < 0)
    \end{cases}
\end{align*}
}

読み方は「サイン」で sin と同じなので、数学では signum (シグナム)関数と書かれることもあるようです(wikipedia:符号関数*2。 java.lang.Math では、こちらの名前で定義されています:

package java.lang;

public final class Math{
    ...
    public static float signum(float f){ ... }
    public static double signum(double d){ ... }
}

サンプルコード。 値のアサーションと、java.lang.Math.signum() メソッドの挙動との比較をしてみます:

import static java.lang.Math.signum

assert MathUtils.sign(2d) == 1d
assert MathUtils.sign(0d) == 0d
assert MathUtils.sign(-2d) == -1d

// java.lang.Math.signum() メソッドと同じ挙動。
assert MathUtils.sign(2d) == signum(2d)
assert MathUtils.sign(0d) == signum(0d)
assert MathUtils.sign(-2d) == signum(-2d)

まぁ、問題なし。

indicator

次は indicator 関数。 この関数はほとんど符号関数と同じですが、x = 0 の時の値だけが異なります*3

  { \displaystyle
\begin{align*}
    \textrm{indicator}(x)
        = \begin{cases}
            1 & (x \ge 0)\\
            -1 & (x < 0)
        \end{cases}
\end{align*}
}

サンプルコードはこんな感じ:

assert MathUtils.indicator(2d) == 1d
assert MathUtils.indicator(0d) == 1d
assert MathUtils.indicator(-2d) == -1d

値を丸める関数 round

最後は値の丸め(切り上げ・切り下げ・四捨五入など)を行う round 関数。 似たような関数は java.lang.Math にも定義されています:

package java.lang;

public final class Math{
    ...
    public static double rint(double a)
    public static int round(float a)
    public static long round(double a)
}

ただし、これらは値の丸め方が決め打ちにされているのと、round() はメソッドの戻り値の型が整数になっている点で、MathUtils#round() メソッドとは違います。 MathUtils#round() では関数としての引数以外に

  • 丸めを行う桁の指定(第2引数)
  • 値の丸め方(第3引数)

を指定する引数があります。

第2引数の「桁の指定」は、小数点から右に何桁目か?を指定する整数で行います:

assert MathUtils.round(123.456d, 0) == 123.0d
assert MathUtils.round(123.456d, 1) == 123.5d
assert MathUtils.round(123.456d, 2) == 123.46d
assert MathUtils.round(123.456d, -1) == 120d

負の整数を渡してもきちんと動作します。 少し注意が必要なのは、指定した数字は小数第何位の数字ではないと言うところでしょうか。 第2引数に n を渡すと、小数第(n+1)位で値の丸めが行われます。

第3引数の「丸め方の指定」は BigDecimal の static フィールド(int 型の)で行います。 丸めの種類はざっとこんなのがあります:

ROUND_CEILING 大きい値(+∞の方へ)切り上げる。
ROUND_FLOOR 小さい値(-∞の方へ)切り下げる。
ROUND_UP 0から遠ざかる方へ丸める(正の数の場合は切り上げ、負の数の場合は切り下げ)。
ROUND_DOWN 0に近づく方へ丸める(正の数の場合は切り下げ、負の数の場合は切り上げ)。
ROUND_HALF_UP (第2引数が0の場合)最も近い整数へ丸める。
2つの整数への距離が等しければ大きい方に丸める。
ROUND_HALF_DOWN (第2引数が0の場合)最も近い整数へ丸める。
2つの整数への距離が等しければ小さい方に丸める。
ROUND_HALF_EVEN (第2引数が0の場合)最も近い整数へ丸める。
2つの整数への距離が等しければ偶数に丸める。
ROUND_UNNECESSARY 厳密な値が必要なときに指定。 丸めを行おうとすると ArithmeticException が投げられる

ROUND_HALF_XXX は第2引数が0として説明してますが、0でなくても使えると思います(試してないけど)。 Java SE 5 から導入された列挙型 java.math.RoundingMode は使えないようです。

まぁ、こんな感じで。 そういえば冪零性って性質は全く使ってませんが・・・気にしないことにしとこう。

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

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

*1:余談ですが、2度(以上)施すと必ず0になる「冪零 (nilpotent)」なんて性質もあります。

*2:他にも「sgn(x)」と書かれることも。

*3:単位ステップ関数(wikipedia:単位ステップ関数{ U(x) } を使って { 2U(x)-1 } と表すことができます。