HOWTO · Java

Java の分数

この記事では、Java での分数の表現を紹介します。

このページの内容

数学的には、分数は値の一部またはセクションです。オブジェクトが一定の割合で均等に分割される場合、形成される値は分数と呼ばれます。分数は有理数と無理数に分類されます。

Java プログラミング言語には、数学的な手順のような分数に対してさまざまな操作を実行する特権があります。分数を足し算、引き算、掛け算、割り算することができます。

以下は、ユーザー定義クラスでの有理数演算を示すコードブロックです。

import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RationalNumber {
  public final static RationalNumber ZERO = new RationalNumber(BigInteger.ZERO, BigInteger.ONE);

  private final BigInteger numerator, denominator;

  private RationalNumber(BigInteger numerator, BigInteger denominator) {
    this.numerator = numerator;
    this.denominator = denominator;
  }

  private static RationalNumber canonical(
      BigInteger numerator, BigInteger denominator, boolean checkGcd) {
    if (denominator.signum() == 0) {
      throw new IllegalArgumentException("denominator is zero");
    }
    if (numerator.signum() == 0) {
      return ZERO;
    }
    if (denominator.signum() < 0) {
      numerator = numerator.negate();
      denominator = denominator.negate();
    }
    if (checkGcd) {
      BigInteger gcd = numerator.gcd(denominator);
      if (!gcd.equals(BigInteger.ONE)) {
        numerator = numerator.divide(gcd);
        denominator = denominator.divide(gcd);
      }
    }
    return new RationalNumber(numerator, denominator);
  }

  public static RationalNumber getInstance(long numerator, long denominator) {
    return canonical(new BigInteger("" + numerator), new BigInteger("" + denominator), true);
  }

  public static RationalNumber valueOf(String s) {
    Pattern p = Pattern.compile("(-?\\d+)(?:.(\\d+)?)?0*(?:e(-?\\d+))?");
    Matcher m = p.matcher(s);
    if (!m.matches()) {
      throw new IllegalArgumentException("Unknown format '" + s + "'");
    }
    String whole = m.group(1);
    String decimal = m.group(2);
    String exponent = m.group(3);
    String n = whole;
    if (decimal != null) {
      n += decimal;
    }
    BigInteger numerator = new BigInteger(n);
    int exp = exponent == null ? 0 : Integer.valueOf(exponent);
    int decimalPlaces = decimal == null ? 0 : decimal.length();
    exp -= decimalPlaces;
    BigInteger denominator;
    if (exp < 0) {
      denominator = BigInteger.TEN.pow(-exp);
    } else {
      numerator = numerator.multiply(BigInteger.TEN.pow(exp));
      denominator = BigInteger.ONE;
    }

    return canonical(numerator, denominator, true);
  }

  public static void main(String[] args) {
    RationalNumber r1 = RationalNumber.valueOf("3.14e4");
    RationalNumber r2 = RationalNumber.getInstance(111, 7);
    convert("r1", r1);
    convert("r2", r2);
    convert("r1 + r2", r1.add(r2));
    convert("r1 - r2", r1.subtract(r2));
    convert("r1 * r2", r1.multiply(r2));
    convert("r1 / r2", r1.divide(r2));
    convert("r2 ^ 2", r2.pow(2));
  }

  public static void convert(String name, RationalNumber r) {
    System.out.printf("%s = %s%n", name, r);
    System.out.printf("%s.negate() = %s%n", name, r.negate());
    System.out.printf("%s.invert() = %s%n", name, r.invert());
    System.out.printf("%s.intValue() = %,d%n", name, r.intValue());
    System.out.printf("%s.longValue() = %,d%n", name, r.longValue());
    System.out.printf("%s.floatValue() = %,f%n", name, r.floatValue());
    System.out.printf("%s.doubleValue() = %,f%n", name, r.doubleValue());
    System.out.println();
  }

  public RationalNumber add(RationalNumber o) {
    if (o.numerator.signum() == 0) {
      return this;
    } else if (numerator.signum() == 0) {
      return o;
    } else if (denominator.equals(o.denominator)) {
      return new RationalNumber(numerator.add(o.numerator), denominator);
    } else {
      return canonical(numerator.multiply(o.denominator).add(o.numerator.multiply(denominator)),
          denominator.multiply(o.denominator), true);
    }
  }

  public RationalNumber multiply(RationalNumber o) {
    if (numerator.signum() == 0 || o.numerator.signum() == 0) {
      return ZERO;
    } else if (numerator.equals(o.denominator)) {
      return canonical(o.numerator, denominator, true);
    } else if (o.numerator.equals(denominator)) {
      return canonical(numerator, o.denominator, true);
    } else if (numerator.negate().equals(o.denominator)) {
      return canonical(o.numerator.negate(), denominator, true);
    } else if (o.numerator.negate().equals(denominator)) {
      return canonical(numerator.negate(), o.denominator, true);
    } else {
      return canonical(numerator.multiply(o.numerator), denominator.multiply(o.denominator), true);
    }
  }

  public boolean isInteger() {
    return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
  }

  public RationalNumber negate() {
    return new RationalNumber(numerator.negate(), denominator);
  }

  public RationalNumber invert() {
    return canonical(denominator, numerator, false);
  }

  public RationalNumber pow(int exp) {
    return canonical(numerator.pow(exp), denominator.pow(exp), true);
  }

  public RationalNumber subtract(RationalNumber o) {
    return add(o.negate());
  }

  public RationalNumber divide(RationalNumber o) {
    return multiply(o.invert());
  }

  public int intValue() {
    return isInteger() ? numerator.intValue() : numerator.divide(denominator).intValue();
  }

  public long longValue() {
    return isInteger() ? numerator.longValue() : numerator.divide(denominator).longValue();
  }

  public float floatValue() {
    return (float) doubleValue();
  }

  public double doubleValue() {
    return isInteger() ? numerator.doubleValue()
                       : numerator.doubleValue() / denominator.doubleValue();
  }

  @Override
  public String toString() {
    return isInteger() ? String.format("%,d", numerator)
                       : String.format("%,d / %,d", numerator, denominator);
  }
}

上記のプログラムでは、実行は main メソッドから始まります。まず、クラスで定義された静的 valueOf() 関数を使用して有理数が初期化されます。文字列値を受け取り、入力文字列に対して操作を実行します。

ユーザー定義型の RationalNumber を返します。この関数は、最初に、適切な検証とパターンを使用して入力文字列をチェックします。入力文字列は、指数、小数、および全体に分けられます。これらの個々の壊れた値が組み合わさって、有理数を形成します。

有理数を作成する別の方法は、instanceOf() 静的ファクトリ関数を使用することです。このメソッドは、クラスのプライベートコンストラクターを呼び出す canonical 関数を内部的に呼び出します。コンストラクターは、特定のインスタンスの分子と分母の値を分離して設定します。標準形とは、単に有理数の標準表現形式を意味します。

与えられた場合、p/q は標準形または標準形であり、q!=0 は常に真でなければなりません。このメソッドには、有理数の基本条件を満たすように記述されたロジックがあります。分子と分母の符号を別々にチェックし、演算を実行します。分母の値をチェックします。ゼロの場合、操作は exception をスローします。また、分子と分母の値で最大公約数または gcd をチェックします。gcd 関数は、BigInteger 値を返す BigInteger クラスに存在します。

先に進むと、convert メソッドが呼び出され、作成された最初の有理数インスタンスが渡されます。このメソッドは、渡される有理数インスタンスに対して 7つの異なる関数も呼び出します。

以下では、これらの各方法をよりよく理解します。

  • 出力ストリームに有理数を出力します。通常、インスタンスが出力されるたびに、デフォルトの toString メソッドが呼び出されます。このメソッドは、クラスの名前に続いて、メモリ位置の 16 進表現を出力します。ただし、ここでは、関数がオーバーライドされて、p/q 形式で数値を表す別の実装が提供されます。
  • negate 関数は、BigInteger クラスの negate メソッドを呼び出すことにより、内部的に有理数を否定します。有理数の前にマイナス記号を追加します。
  • 逆関数は、内部的に canonical メソッドを呼び出しますが、違いは 1つだけです。内部的に 3 番目の checkGcd パラメータを false として渡します。入力値がブール値 true の場合、メソッドロジックは、分数を最大公約数で除算することにより、有理数を単純化します。しかし、この場合、単純化せずに反転する必要があります。
  • intValue 関数は、最初にインスタンスが整数値であるかどうかをチェックします。整数は、正、負、またはゼロの数値ですが、分数にすることはできません。したがって、内部的には、分子と分母に対して divide メソッドを呼び出します。このメソッドは BigInteger クラスで指定され、BigInteger 値を返します。また、分母がゼロの値を求めると、ArithmeticException をスローします。戻り値は、intValue 関数を使用して int 値に変換されます。
  • longValue 関数は内部で除算関数を呼び出し、BigInteger 出力を long データ型に解析します。
  • floatValue 関数は、有理数の浮動小数点値を提供します。そして、doubleValue() 関数のように、Double データ型で出力をタイプキャストします。

プロセス全体は、r1 インスタンスである最初の有理数インスタンスで呼び出されます。同様のシーケンスが再び繰り返され、合理的なインスタンス r2 に対して出力されます。

Java の分数の数学演算

ここで、加算、減算、除算、乗算、および累乗算術演算には、評価のために 2つのオペランドが必要です。それでは、以下で方法について詳しく説明しましょう。

  1. add 関数は、分子値が正、負、またはゼロであるかどうかを最初にチェックします。ゼロ以外の場合は、両方の有理数の分母が同じであるかどうかを確認します。同じものが見つかった場合は、分子値を加算し、形成された有理数を返します。それ以外の場合、類似していない場合は、有理数の加算のロジックを定義します。
  2. subtract メソッドは、渡された 2 番目の有理数インスタンスを否定した後、内部的に add メソッドを呼び出します。
  3. multiply メソッドは、内部的に非常に複雑なロジックを持っています。分子がいずれかの有理数のゼロであるかどうかをチェックし、出力をゼロ値として返します。ゼロでない場合は、分子をいずれかのセットの分母でチェックします。つまり、r1 分子は r2 分母でチェックされ、その逆も同様です。一致する条件がない場合、r1 の分子は r2 インスタンス分子に乗算され、両方の分母が乗算されます。最後に、BigInteger クラスの multiply 関数を呼び出して乗算を実行します。
  4. divide 関数は渡されたインスタンスを反転し、最初のインスタンスを使用して multiply 関数を内部的に呼び出します。
  5. 最後は、r2 インスタンスの 2 乗を評価する pow 関数です。pow の実装は、評価のために指数をとる BigInteger クラスで定義されます。指数が負の値の場合、メソッドは ArithmeticException 例外をスローします。

以下は、上記の複雑なコードの出力です。

r1 = 31, 400 r1.negate() = -31, 400 r1.invert() = 1 / 31, 400 r1.intValue() = 31,
             400 r1.longValue() = 31, 400 r1.floatValue() = 31, 400.000000 r1.doubleValue() = 31,
             400.000000

    r2 = 111 / 7 r2.negate() = -111 / 7 r2.invert() = 7 / 111 r2.intValue() = 15 r2.longValue() =
        15 r2.floatValue() = 15.857142 r2.doubleValue() = 15.857143

    r1
    + r2 = 219,
             911 / 7 r1 + r2.negate() = -219, 911 / 7 r1 + r2.invert() = 7 / 219,
             911 r1 + r2.intValue() = 31, 415 r1 + r2.longValue() = 31,
             415 r1 + r2.floatValue() = 31, 415.857422 r1 + r2.doubleValue() = 31,
             415.857143

    r1
    - r2 = 219,
             689 / 7 r1 - r2.negate() = -219, 689 / 7 r1 - r2.invert() = 7 / 219,
             689 r1 - r2.intValue() = 31, 384 r1 - r2.longValue() = 31,
             384 r1 - r2.floatValue() = 31, 384.142578 r1 - r2.doubleValue() = 31,
             384.142857

    r1* r2 = 3,
             485, 400 / 7 r1* r2.negate() = -3, 485, 400 / 7 r1* r2.invert() = 7 / 3, 485,
             400 r1* r2.intValue() = 497, 914 r1* r2.longValue() = 497,
             914 r1* r2.floatValue() = 497, 914.281250 r1* r2.doubleValue() = 497,
             914.285714

    r1
    / r2 = 219,
             800 / 111 r1 / r2.negate() = -219, 800 / 111 r1 / r2.invert() = 111 / 219,
             800 r1 / r2.intValue() = 1, 980 r1 / r2.longValue() = 1, 980 r1 / r2.floatValue() = 1,
             980.180176 r1 / r2.doubleValue() = 1,
             980.180180

    r2
    ^ 2 = 12,
             321 / 49 r2 ^ 2.negate() = -12, 321 / 49 r2 ^ 2.invert() = 49 / 12,
             321 r2 ^ 2.intValue() = 251 r2 ^ 2.longValue() = 251 r2
    ^ 2.floatValue() = 251.448975 r2 ^ 2.doubleValue() = 251.448980