FastMath

昔書いた、携帯用の Math クラスが出てきたので貼り付け。
ProGuard もかけてやると、生成される class ファイルは 2 キロバイト弱になるようだ。


コサインテーブルを静的に持ったりなんかするとすごく容量を取るので、昔はこういうクラスは重宝したんだけど、容量ほぼ使い放題になった今となっては全くいらないクラスだなぁ……。

/**
 * FastMath クラスは、サインやコサインなどの数学関数を提供します。<br>
 * テーブルを用いて計算しているので、高速に動作します。<br>
 * <br>
 * サイン・コサインの1周は 1024 です。ユーザが変更することは出来ません。<br>
 * また、返ってくる値は左16ビットシフトされた値が返ってきます。これもユーザが変更することは出来ません。<br>
 * <br>
 * アークタンジェントが返す値は 0〜65536 です。<br>
 * サイン・コサインは 1024 単位で1周するため、戻り値をサイン・コサインに与える場合は、<br>
 * <pre>
 * s = FastMath.sin(FastMath.atan(x, y) >> 6);
 * </pre>
 * このように、右に6ビットシフトするか、64で割ってください。<br>
 */
public class FastMath
{
    /**
     * サインを取得します。<br>
     * <br>
     * @param s 角度
     * @return s に対するサイン(0〜65536)
     */
    public static int sin(int s)
    {
        return get()._sin(s);
    }

    /**
     * コサインを取得します。<br>
     * <br>
     * @param s 角度
     * @return s に対するコサイン(0〜65536)
     */
    public static int cos(int s)
    {
        return get()._cos(s);
    }

    /**
     * アークタンジェントを取得します。<br>
     * <br>
     * @param x X 方向
     * @param y Y 方向
     * @return 指定した方向の角度(0〜65536)
     */
    public static int atan(int x, int y)
    {
        return get()._atan(x, y);
    }

    /**
     * 平方根を求めます。{@link #sqrt(long) sqrt(long)} より高速に求められます。<br>
     * <br>
     * @return x に対する平方根
     */
    public static int sqrt(int x)
    {
        return get()._sqrt(x);
    }

    /**
     * 平方根を求めます。<br>
     * <br>
     * @return x に対する平方根
     */
    public static int sqrt(long x)
    {
        return get()._sqrt(x);
    }

    /**
     * {@link #sin(int) sin()} を使った乗算を行います。<br>
     * このメソッドは、次のように定義されています。<br>
     * <pre>
     * return (int)(((long)n * FastMath.sin(s)) >> 16);
     * </pre>
     */
    public static int mul_sin(int n, int s)
    {
        return (int)(((long)n * (long)sin(s)) >> 16);
    }

    /**
     * {@link #cos(int) cos()} を使った乗算を行います。<br>
     * このメソッドは、次のように定義されています。<br>
     * <pre>
     * return (int)(((long)n * FastMath.cos(s)) >> 16);
     * </pre>
     */
    public static int mul_cos(int n, int s)
    {
        return (int)(((long)n * (long)cos(s)) >> 16);
    }

    /**
     * Y 方向と角度から、X 方向の値を取得します。<br>
     * このメソッドは、次のように定義されています。<br>
     * <pre>
     * return (int)((long)y * FastMath.cos(s) / FastMath.sin(s));
     * </pre>
     * @param y Y 方向
     * @param s 角度
     * @return X 方向
     */
    public static int xtan(int y, int s)
    {
        FastMath math = get();
        return (int)(((long)y * (long)math._cos(s)) / (long)math._sin(s));
    }

    /**
     * X 方向と角度から、Y 方向の値を取得します。<br>
     * このメソッドは、次のように定義されています。<br>
     * <pre>
     * return (int)((long)x * FastMath.sin(s) / FastMath.cos(s));
     * </pre>
     * @param x X方向
     * @param s 角度
     * @return Y方向
     */
    public static int ytan(int x, int s)
    {
        FastMath math = get();
        return (int)(((long)x * (long)math._sin(s)) / (long)math._cos(s));
    }

    /**
     * FastMath オブジェクトを事前に生成するメソッドです。<br>
     * FastMath の全てのメソッドは、初回の呼び出しのみ FastMath オブジェクトを生成しています。<br>
     * そして、FastMath オブジェクトの生成段階でコサインテーブル、アークタンジェントテーブルを生成しているため、<br>
     * 最初の FastMath メソッドの呼び出しのみ、非常に時間が掛かる場合があります。<br>
     * そのため、FastMath オブジェクトをこのメソッドによって事前に生成しておくことが推奨されます。<br>
     * <br>
     * @return なし
     */
    public static void create()
    {
        // このメソッドが初めて呼ばれると FastMath が生成されるので、
        // この中はからっぽで良い。
    }

    /**
     * singleton オブジェクトです。<br>
     * <br>
     */
    private static FastMath fastMath = new FastMath();
    /**
     * singleton パターンを使用してオブジェクトを生成し、取得します。<br>
     * <br>
     */
    private static FastMath get()
    {
        create();
        return fastMath;
    }

    /**
     * コンストラクタ<br>
     * ここでコサインテーブル、アークタンジェントテーブルを生成します。<br>
     * <br>
     */
    private FastMath()
    {
        //コサインテーブルが十分に大きくないと
        //アークタンジェントテーブルが生成出来ないので、
        //まずは 4096 個のコサインテーブルを生成する
        makeCosTable(12, 1073740561);
        makeAtanTable();
        //1024 個のコサインテーブルに変更
        makeCosTable(10, 1073721611);
        System.gc();
    }

    /**
     * {@link #sqrt(int) sqrt(int)} が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int _sqrt(int x)
    {
        int s, t;
        // 本当は例外を投げるべきなんだろうけど、
        // その容量がもったいないのでやらない。
        if (x <= 0) return 0;
        s = 1;
        t = x;
        while (s < t)
        {
            s <<= 1;
            t >>= 1;
        }
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        return s;
    }
    /**
     * {@link #sqrt(long) sqrt(long)}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int _sqrt(long x)
    {
        long s, t;
        // 本当は例外を投げるべきなんだろうけど、
        // その容量がもったいないのでやらない。
        if (x <= 0) return 0;
        s = 1;
        t = x;
        while (s < t)
        {
            s <<= 1;
            t >>= 1;
        }
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        s = (x / s + s) >> 1;
        return (int)s;
    }

    /**
     * コサインテーブルの大きさを表します。<br>
     * これは、2^n のコサインテーブルを生成した場合、<br>
     * 2^(n-2) の値になります。<br>
     * <br>
     */
    private int ts;
    /**
     * コサインテーブルの大きさを表します。<br>
     * これは、2^n のコサインテーブルを生成した場合、<br>
     * n-2 の値になります。<br>
     * <br>
     */
    private int tn;
    /**
     * コサインテーブルです。<br>
     * <br>
     */
    private int[] cos_table;

    /**
     * コサインテーブルを生成するメソッドです。<br>
     * <br>
     * @see <a href="http://melpon.tank.jp/pukiwiki147/index.php?cmd=read&page=Memo%2F%A5%B3%A5%B5%A5%A4%A5%F3%A5%C6%A1%BC%A5%D6%A5%EB">コサインテーブル</a>
     */
    public void makeCosTable(int n, long next)
    {
        int t = 1 << (n - 2);
        long[] tmp_table = new long[t];
        this.cos_table = new int[t+1];
        tmp_table[0] = 1 << 30;
        tmp_table[1] = next;
        for (int i = 2; i < t; i++)        //第1象限だけテーブルを計算
        {
            tmp_table[i] = ((next * tmp_table[i - 1] + 0x10000000) >> 29) - tmp_table[i - 2];
        }
        for (int i = 0; i < t; i++)        //値が大きすぎるので、16ビットのコサインテーブルに変換
        {
            this.cos_table[i] = (int)((tmp_table[i] + 0x2000) >> 14);
        }
        this.cos_table[t] = 0;
        this.ts = t;
        this.tn = n - 2;
    }

    /**
     * {@link #sin(int) sin()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int _sin(int s)
    {
        return _cos(s - this.ts);
    }

        /**
     * {@link #cos(int) cos()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int _cos(int s)
    {
        int t, t2;

        t = this.ts;
        s &= (t << 2) - 1;
        if (s < t) return this.cos_table[s];
        t2 = t << 1;
        if (s < t2) return -this.cos_table[t2 - s];
        if (s < t * 3) return -this.cos_table[s - t2];
        return this.cos_table[(t << 2) - s];
    }

    /**
     * アークタンジェントテーブルの一周の大きさを表します。<br>
     * 1周の大きさは 2^atan_max になります。<br>
     * <br>
     */
    private final int atan_max = 16;
    /**
     * アークタンジェントテーブルの大きさを表します。<br>
     * 2^atan_n の大きさのテーブルが作られることになります。<br>
     * テーブルは8分の1の大きさなので、精度としては 2^(atan_n+3) となります。<br>
     * <br>
     */
    private final int atan_n = 8;
    /**
     * アークタンジェントテーブルです。
     */
    private int[] atan_table;

    /**
     * アークタンジェントテーブルを生成するメソッドです。<br>
     * コサインテーブルが十分に大きくないと、テーブルに穴が空いてしまうので注意。<br>
     * <br>
     */
    private void makeAtanTable()
    {
        int cn, i, s;

        cn = this.tn + 2;
        this.atan_table = new int[1 << this.atan_n];
        for (i = 0; i < 1 << (this.atan_max - 3); i++)
        {
            s = i >> (this.atan_max - cn);
            this.atan_table[(_sin(s) << this.atan_n) / _cos(s)] = i;
        }
    }

    /**
     * {@link #atan(int, int) atan()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int _atan(int x, int y)
    {
        // 本当は例外を投げるべきなんだろうけど……。
        if (x == 0 && y == 0) return -1;
        if (y < 0) return atan0(-x, -y) + (1 << (this.atan_max - 1));
        return atan0(x, y);
    }
    /**
     * {@link #atan(int, int) atan()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int atan0(int x, int y)
    {
        if (x < 0) return atan1(y, -x) + (1 << (this.atan_max - 2));
        return atan1(x, y);
    }
    /**
     * {@link #atan(int, int) atan()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int atan1(int x, int y)
    {
        if (x == y) return 1 << (atan_max - 3);
        if (y > x) return (1 << (atan_max - 2)) - atan2(y, x);
        return atan2(x, y);
    }
    /**
     * {@link #atan(int, int) atan()}が内部的に使用するメソッドです。<br>
     * <br>
     */
    private int atan2(int x, int y)
    {
        if (x == 0) return 0;
        return this.atan_table[(int)(((long)y << this.atan_n) / x)];
    }
}