Groovy で複素数の計算処理を実装する。

最終更新:2014/09/06

calc.groovy 複素数の計算式を処理するスクリプト
// calc.groovy
// Version 1.0
// KAKU PROJECT (2014)
// 概要:複素数の計算式を処理するスクリプト
// 実行例: groovy calc.groovy file

// ビルトイン定数とビルトイン関数の定義
def binding = new Binding([
  // i: 虚数
  i: new Complex(0, 1),
  // Re: 複素数から実数部を取り出す関数
  Re: {z -> z.r},
  // Im: 複素数から虚数部を取り出す関数
  Im: {z -> z.i},
  // conjugate: 複素数の共役複素数を作る関数
  conjugate: {z ->
    def z2 = new Complex(z)
    new Complex(z2.r, -z2.i)},
  // conjugate: 複素数の絶対値を計算する関数
  abs: {z ->
    def z2 = new Complex(z)
    java.lang.Math.sqrt(z2.r**2 + z2.i**2)}
])

// Number クラスに複素数オブジェクト用の演算を追加する。
// 加算
Number.metaClass.plus = {Complex b ->
  new Complex (delegate, 0)+b
}
// 減算
Number.metaClass.minus = {Complex b ->
  new Complex (delegate, 0)-b
}
// 乗算
Number.metaClass.multiply = {Complex b ->
  new Complex (delegate * b.r, delegate * b.i)
}
// 除算
Number.metaClass.div = {Complex b ->
  new Complex (delegate, 0)/b
}

// 入力ファイルの各行の式を加工し、ストリングバッファに保存。
def sb = new StringBuffer()
new File(args[0]).eachLine {
  sb << "println (\"${it}\\n==>\${${it}}\\n\")\n"
//  println sb
}
// メモ:
// 入力行→xxx
// 評価される行→println ("xxx\n==>${xxx}\n")
// エスケープした行→"println (\"xxx\\n==>\${xxx}\\n\")\n"


// 式の評価。
def shell = new GroovyShell(binding)
shell.evaluate(
  sb.toString()
)
Complex.groovy 複素数のクラス。
// Complex.groovy
// Version 1.0
// KAKU PROJECT (2014)
// 概要:複素数を表現するクラス

public class Complex
{
// ○複素数の実数部・虚数部プロパティ

// ・実数部
  private Number r = 0
// ・虚数部
  private Number i = 0

// メモ:実数部・虚数部の値は Number クラスにしておくことで、任意の数値クラス
// (Integer,Long,Double,float,BigDecimal)を使えるようにする。

// ・実数部の参照メソッド
  public Number getR () {r}
// ・虚数部の参照メソッド
  public Number getI () {i}

// メモ:変更メソッド(setR, setI) は用意しない。

// ○コンストラクタ

// ・実数部のみの複素数
  public Complex (Number r) {
    this.r = r
    this.i = 0
  }

// ・実数部+虚数部からなる複素数
  public Complex (Number r, Number i) {
    this.r = r
    this.i = i
  }

// 既存の複素数オブジェクト用コンストラクタ
  public Complex (Complex z) {
    this.r = z.r
    this.i = z.i
  }

// ○演算子用メソッド。

// メモ:各演算子の結果は常に新しいオブジェクトを作成し、既存のオブジェクトは
// 変化させないこととする。++ や -- などの単項演算子は用意しない。

// ・加算(二項演算・引数:複素数)
  public Complex plus(Complex b) {
    new Complex(r+b.r, i+b.i)
  }
// ・加算(二項演算・引数:実数)
  public Complex plus(Number n) {
    new Complex(r+n, i)
  }

// ・減算(二項演算・引数:複素数)
  public Complex minus (Complex b) {
    new Complex(r-b.r, i-b.i)
  }
// ・減算(二項演算・引数:実数)
  public Complex minus(Number n) {
    new Complex(r-n, i)
  }

// ・乗算(二項演算・引数:複素数)
  public Complex multiply (Complex b) {
    new Complex((r*b.r)-(i*b.i), (r*b.i)+(i*b.r))
  }
// ・乗算(二項演算・引数:実数)
  public Complex multiply(Number n) {
    new Complex(r*n, i*n)
  }

// ・除算(二項演算・引数:複素数)
  public Complex div (Complex b) {
    if (b.isZero()) {
      // ゼロ除算例外
      throw new java.lang.ArithmeticException("Division by zero")
    }

    if (this.equals(b)) {
      // 同じ複素数なら1を返す。
      return new Complex(1, 0)
    }

    double t = b.r*b.r + b.i*b.i
    new Complex(
     (r*b.r+i*b.i)/t,
     (i*b.r-r*b.i)/t)
  }

// ・除算(二項演算・引数:実数)
  public Complex div (Number n) {
    if (n == 0) {
      // ゼロ除算例外
      throw new java.lang.ArithmeticException("Division by zero")
    }

    new Complex(r/n, i/n)
  }

// ・累乗(二項演算・引数:整数)
  public Complex power (int n) {
//println "n:$n"
    Complex z = this
    if (n < 0) {
      z = new Complex(1, 0).div(this.power(-n))
    } else if (n == 0) {
      z = new Complex(1, 0)
    } else {
      z = this
      for (int i=1; i<n; i++) {
//println "i:$i"
        z = z.multiply(this)
//println "z:$z"
      }
    }
    z
  }

// ・マイナス(単項演算)
  public Complex negative () {
    new Complex(-r, -i)
  }

// ・プラス(単項演算)
  public Complex positive () {
    this
  }

// ・比較(二項演算・引数:複素数)
  public boolean equals (Complex b) {
    this.r == b.r && this.i == b.i
  }

// ・補助メソッド。0 か否かを調べる
  public boolean isZero () {
    this.r == 0 && this.i == 0
  }

// 文字列化(toString)

// ・補助メソッド。虚数部の文字列化。プラス符号なし。
  private String toImgString2 () {
    if (i == 0) {
      ""
    } else if (i == 1) {
      "i"
    } else if (i == -1) {
      "-i"
    } else if (i < 0) {
          "-${i*(-1)}i"
    } else {
          "${i}i"
    }
  }

// ・補助メソッド。虚数部の文字列化。プラス符号あり。
  private String toImgString1 () {
    if (i == 0) {
      ""
    } else if (i == 1) {
      "+i"
    } else if (i == -1) {
      "-i"
    } else if (i < 0) {
          "-${(double)i*(-1)}i"
    } else {
          "+${(double)i}i"
    }
  }

// ・複素数の文字列化
  public String toString () {
    if (i == 0) {
      if (r == 0) {
        "0"
      } else {
        "${(double)r}"
      }
    } else {
      if (r == 0) {
        toImgString2()
      } else {
        "${(double)r}${toImgString1()}"
      }
    }
  }

} // End of class Complex
複素数の計算式のファイル例。
3+4*i
4.0+3.0*i
(3+4*i)*(4.0+3.0*i)
(3+4*i)*(4.0+3.0*i)*i
(3+4*i)/(4.0+3.0*i)
(0.96+0.28*i)*(4.0+3.0*i)
実行例
C:\work\complex>groovy calc.groovy z.txt
3+4*i
==>3.0+4.0i

4.0+3.0*i
==>4.0+3.0i

(3+4*i)*(4.0+3.0*i)
==>25.0i

(3+4*i)*(4.0+3.0*i)*i
==>-25.0

(3+4*i)/(4.0+3.0*i)
==>0.96+0.28i

(0.96+0.28*i)*(4.0+3.0*i)
==>3.0+4.0i

●メモ。演算子のオーバーロードについて。

(0.96+0.28*i)*(4.0+3.0*i)
の式において、各演算子は次のように展開される:
(0.96.plus(0.28.multiply(i)).multiply(4.0.plus(3.0.multiply(i)))
この際に各演算子の優先順位は Groovy の処理系が決定する。 例えば、0.96 への plus メソッドの適用時には、calc.groovy の 27~28 で Number クラスに追加したメソッドが使われる。 また、"i" は calc.groovy の 10 行目で定義されたビルトイン定数が使われる。

Copyright (C) KAKU PROJECT (2014) KAKU PROJECT (2014)