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 應始終為真。該方法編寫了滿足有理數基本條件的邏輯。它分別檢查分子和分母的符號並執行運算。它檢查分母值;如果它是零,操作會丟擲一個異常。它還檢查分子和分母值中的最大公約數或 gcdgcd 函式存在於返回 BigInteger 值的 BigInteger 類中。

繼續前進,呼叫 convert 方法,傳遞建立的第一個有理數例項。此方法還在傳遞的有理數例項上呼叫七個不同的函式。

下面,你將更好地理解這些方法:

  • 在列印流中列印有理數。通常,每當列印例項時,都會呼叫預設的 toString 方法。該方法列印類的名稱,後跟記憶體位置的十六進位制表示。但在這裡,該函式被覆蓋以提供另一個表示 p/q 形式的數字的實現。
  • negate 函式將通過呼叫 BigInteger 類的 negate 方法在內部否定有理數。它將在有理數之前新增一個減號。
  • invert 函式在內部呼叫 canonical 方法,但只有一個區別。它在內部將第三個 checkGcd 引數作為 false 傳遞。當輸入值為布林真時,方法邏輯通過將分數除以其最大公約數來簡化有理數。但在這種情況下,需要的是不簡化的反轉。
  • intValue 函式首先檢查例項是否為 Integer 值。Integers 是可以是正數、負數或零但不能是分數的數字。因此,它在內部對分子和分母呼叫 divide 方法。該方法在 BigInteger 類中給出並返回一個 BigInteger 值。當分母為零值時,它也會丟擲 ArithmeticException。使用 intValue 函式將返回值轉換為 int 值。
  • longValue 函式在內部呼叫除法函式 snd 將 BigInteger 輸出解析為 long 資料型別。
  • floatValue 函式提供有理數的浮點值。doubleValue() 函式也是如此,它以 Double 資料型別對輸出進行型別轉換。

整個過程呼叫一個初始有理數例項,即 r1 例項。類似的序列再次重複併為有理例項 r2 列印出來。

Java 中分數的數學運算

現在加、減、除、乘和冪算術運算需要兩個運算元進行計算。因此,讓我們在下面詳細討論這些方法:

  1. add 函式首先檢查分子值是正數、負數還是零。如果非零,則檢查兩個有理數的分母是否相同。如果發現相同,則將分子值相加並返回形成的有理數。否則,如果沒有找到類似的,它定義了有理數加法的邏輯。
  2. subtract 方法在否定傳遞的第二個有理數例項後在內部呼叫 add 方法。
  3. multiply 方法內部有相當複雜的邏輯。它檢查任一有理數的分子是否為零,並將輸出作為零值返回。如果不為零,則使用任一集合的分母檢查分子。即 r1 分子與 r2 分母進行檢查,反之亦然。當沒有條件匹配時,r1 的分子與 r2 例項分子相乘,並且兩者的分母相乘。最後,它呼叫 BigInteger 類的 multiply 函式來執行乘法。
  4. divide 函式將反轉傳遞的例項並使用第一個例項在內部呼叫 multiply 函式。
  5. 最後是 pow 函式,它計算 r2 例項的二次冪。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