在Java中表示分数的最佳方式?

发布于 2024-07-12 13:19:24 字数 3021 浏览 10 评论 0原文

我正在尝试在Java中使用分数

我想实现算术函数。 为此,我首先需要一种标准化功能的方法。 我知道在找到共同分母之前我无法将 1/6 和 1/2 相加。 我必须添加 1/6 和 3/6。 一个幼稚的方法是让我添加 2/12 和 6/12,然后减少。 如何以最小的性能损失实现共同点? 什么算法最适合这个?


版本 8(感谢 hstoerr):

改进包括:

  • equals() 方法现在与compareTo() 方法一致
final class Fraction extends Number {
    private int numerator;
    private int denominator;

    public Fraction(int numerator, int denominator) {
        if(denominator == 0) {
            throw new IllegalArgumentException("denominator is zero");
        }
        if(denominator < 0) {
            numerator *= -1;
            denominator *= -1;
        }
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public Fraction(int numerator) {
        this.numerator = numerator;
        this.denominator = 1;
    }

    public int getNumerator() {
        return this.numerator;
    }

    public int getDenominator() {
        return this.denominator;
    }

    public byte byteValue() {
        return (byte) this.doubleValue();
    }

    public double doubleValue() {
        return ((double) numerator)/((double) denominator);
    }

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

    public int intValue() {
        return (int) this.doubleValue();
    }

    public long longValue() {
        return (long) this.doubleValue();
    }

    public short shortValue() {
        return (short) this.doubleValue();
    }

    public boolean equals(Fraction frac) {
        return this.compareTo(frac) == 0;
    }

    public int compareTo(Fraction frac) {
        long t = this.getNumerator() * frac.getDenominator();
        long f = frac.getNumerator() * this.getDenominator();
        int result = 0;
        if(t>f) {
            result = 1;
        }
        else if(f>t) {
            result = -1;
        }
        return result;
    }
}

我已经删除了所有以前的版本。 我要感谢:

I'm trying to work with fractions in Java.

I want to implement arithmetic functions. For this, I will first require a way to normalize the functions. I know I can't add 1/6 and 1/2 until I have a common denominator. I will have to add 1/6 and 3/6. A naive approach would have me add 2/12 and 6/12 and then reduce. How can I achieve a common denominator with the least performance penalty? What algorithm is best for this?


Version 8 (thanks to hstoerr):

Improvements include:

  • the equals() method is now consistent with the compareTo() method
final class Fraction extends Number {
    private int numerator;
    private int denominator;

    public Fraction(int numerator, int denominator) {
        if(denominator == 0) {
            throw new IllegalArgumentException("denominator is zero");
        }
        if(denominator < 0) {
            numerator *= -1;
            denominator *= -1;
        }
        this.numerator = numerator;
        this.denominator = denominator;
    }

    public Fraction(int numerator) {
        this.numerator = numerator;
        this.denominator = 1;
    }

    public int getNumerator() {
        return this.numerator;
    }

    public int getDenominator() {
        return this.denominator;
    }

    public byte byteValue() {
        return (byte) this.doubleValue();
    }

    public double doubleValue() {
        return ((double) numerator)/((double) denominator);
    }

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

    public int intValue() {
        return (int) this.doubleValue();
    }

    public long longValue() {
        return (long) this.doubleValue();
    }

    public short shortValue() {
        return (short) this.doubleValue();
    }

    public boolean equals(Fraction frac) {
        return this.compareTo(frac) == 0;
    }

    public int compareTo(Fraction frac) {
        long t = this.getNumerator() * frac.getDenominator();
        long f = frac.getNumerator() * this.getDenominator();
        int result = 0;
        if(t>f) {
            result = 1;
        }
        else if(f>t) {
            result = -1;
        }
        return result;
    }
}

I have removed all previous versions. My thanks to:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(26

勿忘心安 2024-07-19 13:19:24

碰巧我不久前为 Project Euler 问题编写了一个 BigFraction 类。 它保留 BigInteger 分子和分母,因此永远不会溢出。 但对于许多你知道永远不会溢出的操作来说,它会有点慢......无论如何,如果你想要的话就使用它。 我一直渴望以某种方式展示这一点。 :)

编辑:此代码的最新、最好的版本,包括单元测试现在托管在 GitHub 上 以及 可通过 Maven Central 获取。 我将原始代码留在这里,以便这个答案不仅仅是一个链接......


import java.math.*;

/**
 * Arbitrary-precision fractions, utilizing BigIntegers for numerator and
 * denominator.  Fraction is always kept in lowest terms.  Fraction is
 * immutable, and guaranteed not to have a null numerator or denominator.
 * Denominator will always be positive (so sign is carried by numerator,
 * and a zero-denominator is impossible).
 */
public final class BigFraction extends Number implements Comparable<BigFraction>
{
  private static final long serialVersionUID = 1L; //because Number is Serializable
  private final BigInteger numerator;
  private final BigInteger denominator;

  public final static BigFraction ZERO = new BigFraction(BigInteger.ZERO, BigInteger.ONE, true);
  public final static BigFraction ONE = new BigFraction(BigInteger.ONE, BigInteger.ONE, true);

  /**
   * Constructs a BigFraction with given numerator and denominator.  Fraction
   * will be reduced to lowest terms.  If fraction is negative, negative sign will
   * be carried on numerator, regardless of how the values were passed in.
   */
  public BigFraction(BigInteger numerator, BigInteger denominator)
  {
    if(numerator == null)
      throw new IllegalArgumentException("Numerator is null");
    if(denominator == null)
      throw new IllegalArgumentException("Denominator is null");
    if(denominator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero.");

    //only numerator should be negative.
    if(denominator.signum() < 0)
    {
      numerator = numerator.negate();
      denominator = denominator.negate();
    }

    //create a reduced fraction
    BigInteger gcd = numerator.gcd(denominator);
    this.numerator = numerator.divide(gcd);
    this.denominator = denominator.divide(gcd);
  }

  /**
   * Constructs a BigFraction from a whole number.
   */
  public BigFraction(BigInteger numerator)
  {
    this(numerator, BigInteger.ONE, true);
  }

  public BigFraction(long numerator, long denominator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
  }

  public BigFraction(long numerator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.ONE, true);
  }

  /**
   * Constructs a BigFraction from a floating-point number.
   * 
   * Warning: round-off error in IEEE floating point numbers can result
   * in answers that are unexpected.  For example, 
   *     System.out.println(new BigFraction(1.1))
   * will print:
   *     2476979795053773/2251799813685248
   * 
   * This is because 1.1 cannot be expressed exactly in binary form.  The
   * given fraction is exactly equal to the internal representation of
   * the double-precision floating-point number.  (Which, for 1.1, is:
   * (-1)^0 * 2^0 * (1 + 0x199999999999aL / 0x10000000000000L).)
   * 
   * NOTE: In many cases, BigFraction(Double.toString(d)) may give a result
   * closer to what the user expects.
   */
  public BigFraction(double d)
  {
    if(Double.isInfinite(d))
      throw new IllegalArgumentException("double val is infinite");
    if(Double.isNaN(d))
      throw new IllegalArgumentException("double val is NaN");

    //special case - math below won't work right for 0.0 or -0.0
    if(d == 0)
    {
      numerator = BigInteger.ZERO;
      denominator = BigInteger.ONE;
      return;
    }

    final long bits = Double.doubleToLongBits(d);
    final int sign = (int)(bits >> 63) & 0x1;
    final int exponent = ((int)(bits >> 52) & 0x7ff) - 0x3ff;
    final long mantissa = bits & 0xfffffffffffffL;

    //number is (-1)^sign * 2^(exponent) * 1.mantissa
    BigInteger tmpNumerator = BigInteger.valueOf(sign==0 ? 1 : -1);
    BigInteger tmpDenominator = BigInteger.ONE;

    //use shortcut: 2^x == 1 << x.  if x is negative, shift the denominator
    if(exponent >= 0)
      tmpNumerator = tmpNumerator.multiply(BigInteger.ONE.shiftLeft(exponent));
    else
      tmpDenominator = tmpDenominator.multiply(BigInteger.ONE.shiftLeft(-exponent));

    //1.mantissa == 1 + mantissa/2^52 == (2^52 + mantissa)/2^52
    tmpDenominator = tmpDenominator.multiply(BigInteger.valueOf(0x10000000000000L));
    tmpNumerator = tmpNumerator.multiply(BigInteger.valueOf(0x10000000000000L + mantissa));

    BigInteger gcd = tmpNumerator.gcd(tmpDenominator);
    numerator = tmpNumerator.divide(gcd);
    denominator = tmpDenominator.divide(gcd);
  }

  /**
   * Constructs a BigFraction from two floating-point numbers.
   * 
   * Warning: round-off error in IEEE floating point numbers can result
   * in answers that are unexpected.  See BigFraction(double) for more
   * information.
   * 
   * NOTE: In many cases, BigFraction(Double.toString(numerator) + "/" + Double.toString(denominator))
   * may give a result closer to what the user expects.
   */
  public BigFraction(double numerator, double denominator)
  {
    if(denominator == 0)
      throw new ArithmeticException("Divide by zero.");

    BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
    this.numerator = tmp.numerator;
    this.denominator = tmp.denominator;
  }

  /**
   * Constructs a new BigFraction from the given BigDecimal object.
   */
  public BigFraction(BigDecimal d)
  {
    this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
         d.scale() < 0 ? BigInteger.ONE                                             : BigInteger.TEN.pow(d.scale()));
  }

  public BigFraction(BigDecimal numerator, BigDecimal denominator)
  {
    if(denominator.equals(BigDecimal.ZERO))
      throw new ArithmeticException("Divide by zero.");

    BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
    this.numerator = tmp.numerator;
    this.denominator = tmp.denominator;
  }

  /**
   * Constructs a BigFraction from a String.  Expected format is numerator/denominator,
   * but /denominator part is optional.  Either numerator or denominator may be a floating-
   * point decimal number, which in the same format as a parameter to the
   * <code>BigDecimal(String)</code> constructor.
   * 
   * @throws NumberFormatException  if the string cannot be properly parsed.
   */
  public BigFraction(String s)
  {
    int slashPos = s.indexOf('/');
    if(slashPos < 0)
    {
      BigFraction res = new BigFraction(new BigDecimal(s));
      this.numerator = res.numerator;
      this.denominator = res.denominator;
    }
    else
    {
      BigDecimal num = new BigDecimal(s.substring(0, slashPos));
      BigDecimal den = new BigDecimal(s.substring(slashPos+1, s.length()));
      BigFraction res = new BigFraction(num, den);
      this.numerator = res.numerator;
      this.denominator = res.denominator;
    }
  }

  /**
   * Returns this + f.
   */
  public BigFraction add(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    //n1/d1 + n2/d2 = (n1*d2 + d1*n2)/(d1*d2) 
    return new BigFraction(numerator.multiply(f.denominator).add(denominator.multiply(f.numerator)),
                           denominator.multiply(f.denominator));
  }

  /**
   * Returns this + b.
   */
  public BigFraction add(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    //n1/d1 + n2 = (n1 + d1*n2)/d1
    return new BigFraction(numerator.add(denominator.multiply(b)),
                           denominator, true);
  }

  /**
   * Returns this + n.
   */
  public BigFraction add(long n)
  {
    return add(BigInteger.valueOf(n));
  }

  /**
   * Returns this - f.
   */
  public BigFraction subtract(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(f.denominator).subtract(denominator.multiply(f.numerator)),
                           denominator.multiply(f.denominator));
  }

  /**
   * Returns this - b.
   */
  public BigFraction subtract(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.subtract(denominator.multiply(b)),
                           denominator, true);
  }

  /**
   * Returns this - n.
   */
  public BigFraction subtract(long n)
  {
    return subtract(BigInteger.valueOf(n));
  }

  /**
   * Returns this * f.
   */
  public BigFraction multiply(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(f.numerator), denominator.multiply(f.denominator));
  }

  /**
   * Returns this * b.
   */
  public BigFraction multiply(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(b), denominator);
  }

  /**
   * Returns this * n.
   */
  public BigFraction multiply(long n)
  {
    return multiply(BigInteger.valueOf(n));
  }

  /**
   * Returns this / f.
   */
  public BigFraction divide(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    if(f.numerator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(numerator.multiply(f.denominator), denominator.multiply(f.numerator));
  }

  /**
   * Returns this / b.
   */
  public BigFraction divide(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    if(b.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(numerator, denominator.multiply(b));
  }

  /**
   * Returns this / n.
   */
  public BigFraction divide(long n)
  {
    return divide(BigInteger.valueOf(n));
  }

  /**
   * Returns this^exponent.
   */
  public BigFraction pow(int exponent)
  {
    if(exponent == 0)
      return BigFraction.ONE;
    else if (exponent == 1)
      return this;
    else if (exponent < 0)
      return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent), true);
    else
      return new BigFraction(numerator.pow(exponent), denominator.pow(exponent), true);
  }

  /**
   * Returns 1/this.
   */
  public BigFraction reciprocal()
  {
    if(this.numerator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(denominator, numerator, true);
  }

  /**
   * Returns the complement of this fraction, which is equal to 1 - this.
   * Useful for probabilities/statistics.

   */
  public BigFraction complement()
  {
    return new BigFraction(denominator.subtract(numerator), denominator, true);
  }

  /**
   * Returns -this.
   */
  public BigFraction negate()
  {
    return new BigFraction(numerator.negate(), denominator, true);
  }

  /**
   * Returns -1, 0, or 1, representing the sign of this fraction.
   */
  public int signum()
  {
    return numerator.signum();
  }

  /**
   * Returns the absolute value of this.
   */
  public BigFraction abs()
  {
    return (signum() < 0 ? negate() : this);
  }

  /**
   * Returns a string representation of this, in the form
   * numerator/denominator.
   */
  public String toString()
  {
    return numerator.toString() + "/" + denominator.toString();
  }

  /**
   * Returns if this object is equal to another object.
   */
  public boolean equals(Object o)
  {
    if(!(o instanceof BigFraction))
      return false;

    BigFraction f = (BigFraction)o;
    return numerator.equals(f.numerator) && denominator.equals(f.denominator);
  }

  /**
   * Returns a hash code for this object.
   */
  public int hashCode()
  {
    //using the method generated by Eclipse, but streamlined a bit..
    return (31 + numerator.hashCode())*31 + denominator.hashCode();
  }

  /**
   * Returns a negative, zero, or positive number, indicating if this object
   * is less than, equal to, or greater than f, respectively.
   */
  public int compareTo(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    //easy case: this and f have different signs
    if(signum() != f.signum())
      return signum() - f.signum();

    //next easy case: this and f have the same denominator
    if(denominator.equals(f.denominator))
      return numerator.compareTo(f.numerator);

    //not an easy case, so first make the denominators equal then compare the numerators 
    return numerator.multiply(f.denominator).compareTo(denominator.multiply(f.numerator));
  }

  /**
   * Returns the smaller of this and f.
   */
  public BigFraction min(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return (this.compareTo(f) <= 0 ? this : f);
  }

  /**
   * Returns the maximum of this and f.
   */
  public BigFraction max(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return (this.compareTo(f) >= 0 ? this : f);
  }

  /**
   * Returns a positive BigFraction, greater than or equal to zero, and less than one.
   */
  public static BigFraction random()
  {
    return new BigFraction(Math.random());
  }

  public final BigInteger getNumerator() { return numerator; }
  public final BigInteger getDenominator() { return denominator; }

  //implementation of Number class.  may cause overflow.
  public byte   byteValue()   { return (byte) Math.max(Byte.MIN_VALUE,    Math.min(Byte.MAX_VALUE,    longValue())); }
  public short  shortValue()  { return (short)Math.max(Short.MIN_VALUE,   Math.min(Short.MAX_VALUE,   longValue())); }
  public int    intValue()    { return (int)  Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, longValue())); }
  public long   longValue()   { return Math.round(doubleValue()); }
  public float  floatValue()  { return (float)doubleValue(); }
  public double doubleValue() { return toBigDecimal(18).doubleValue(); }

  /**
   * Returns a BigDecimal representation of this fraction.  If possible, the
   * returned value will be exactly equal to the fraction.  If not, the BigDecimal
   * will have a scale large enough to hold the same number of significant figures
   * as both numerator and denominator, or the equivalent of a double-precision
   * number, whichever is more.
   */
  public BigDecimal toBigDecimal()
  {
    //Implementation note:  A fraction can be represented exactly in base-10 iff its
    //denominator is of the form 2^a * 5^b, where a and b are nonnegative integers.
    //(In other words, if there are no prime factors of the denominator except for
    //2 and 5, or if the denominator is 1).  So to determine if this denominator is
    //of this form, continually divide by 2 to get the number of 2's, and then
    //continually divide by 5 to get the number of 5's.  Afterward, if the denominator
    //is 1 then there are no other prime factors.

    //Note: number of 2's is given by the number of trailing 0 bits in the number
    int twos = denominator.getLowestSetBit();
    BigInteger tmpDen = denominator.shiftRight(twos); // x / 2^n === x >> n

    final BigInteger FIVE = BigInteger.valueOf(5);
    int fives = 0;
    BigInteger[] divMod = null;

    //while(tmpDen % 5 == 0) { fives++; tmpDen /= 5; }
    while(BigInteger.ZERO.equals((divMod = tmpDen.divideAndRemainder(FIVE))[1]))
    {
      fives++;
      tmpDen = divMod[0];
    }

    if(BigInteger.ONE.equals(tmpDen))
    {
      //This fraction will terminate in base 10, so it can be represented exactly as
      //a BigDecimal.  We would now like to make the fraction of the form
      //unscaled / 10^scale.  We know that 2^x * 5^x = 10^x, and our denominator is
      //in the form 2^twos * 5^fives.  So use max(twos, fives) as the scale, and
      //multiply the numerator and deminator by the appropriate number of 2's or 5's
      //such that the denominator is of the form 2^scale * 5^scale.  (Of course, we
      //only have to actually multiply the numerator, since all we need for the
      //BigDecimal constructor is the scale.
      BigInteger unscaled = numerator;
      int scale = Math.max(twos, fives);

      if(twos < fives)
        unscaled = unscaled.shiftLeft(fives - twos); //x * 2^n === x << n
      else if (fives < twos)
        unscaled = unscaled.multiply(FIVE.pow(twos - fives));

      return new BigDecimal(unscaled, scale);
    }

    //else: this number will repeat infinitely in base-10.  So try to figure out
    //a good number of significant digits.  Start with the number of digits required
    //to represent the numerator and denominator in base-10, which is given by
    //bitLength / log[2](10).  (bitLenth is the number of digits in base-2).
    final double LG10 = 3.321928094887362; //Precomputed ln(10)/ln(2), a.k.a. log[2](10)
    int precision = Math.max(numerator.bitLength(), denominator.bitLength());
    precision = (int)Math.ceil(precision / LG10);

    //If the precision is less than 18 digits, use 18 digits so that the number
    //will be at least as accurate as a cast to a double.  For example, with
    //the fraction 1/3, precision will be 1, giving a result of 0.3.  This is
    //quite a bit different from what a user would expect.
    if(precision < 18)
      precision = 18;

    return toBigDecimal(precision);
  }

  /**
   * Returns a BigDecimal representation of this fraction, with a given precision.
   * @param precision  the number of significant figures to be used in the result.
   */
  public BigDecimal toBigDecimal(int precision)
  {
    return new BigDecimal(numerator).divide(new BigDecimal(denominator), new MathContext(precision, RoundingMode.HALF_EVEN));
  }

  //--------------------------------------------------------------------------
  //  PRIVATE FUNCTIONS
  //--------------------------------------------------------------------------

  /**
   * Private constructor, used when you can be certain that the fraction is already in
   * lowest terms.  No check is done to reduce numerator/denominator.  A check is still
   * done to maintain a positive denominator.
   * 
   * @param throwaway  unused variable, only here to signal to the compiler that this
   *                   constructor should be used.
   */
  private BigFraction(BigInteger numerator, BigInteger denominator, boolean throwaway)
  {
    if(denominator.signum() < 0)
    {
      this.numerator = numerator.negate();
      this.denominator = denominator.negate();
    }
    else
    {
      this.numerator = numerator;
      this.denominator = denominator;
    }
  }

}

It just so happens that I wrote a BigFraction class not too long ago, for Project Euler problems. It keeps a BigInteger numerator and denominator, so it'll never overflow. But it'll be a tad slow for a lot of operations that you know will never overflow.. anyway, use it if you want it. I've been dying to show this off somehow. :)

Edit: Latest and greatest version of this code, including unit tests is now hosted on GitHub and also available via Maven Central. I'm leaving my original code here so that this answer isn't just a link...


import java.math.*;

/**
 * Arbitrary-precision fractions, utilizing BigIntegers for numerator and
 * denominator.  Fraction is always kept in lowest terms.  Fraction is
 * immutable, and guaranteed not to have a null numerator or denominator.
 * Denominator will always be positive (so sign is carried by numerator,
 * and a zero-denominator is impossible).
 */
public final class BigFraction extends Number implements Comparable<BigFraction>
{
  private static final long serialVersionUID = 1L; //because Number is Serializable
  private final BigInteger numerator;
  private final BigInteger denominator;

  public final static BigFraction ZERO = new BigFraction(BigInteger.ZERO, BigInteger.ONE, true);
  public final static BigFraction ONE = new BigFraction(BigInteger.ONE, BigInteger.ONE, true);

  /**
   * Constructs a BigFraction with given numerator and denominator.  Fraction
   * will be reduced to lowest terms.  If fraction is negative, negative sign will
   * be carried on numerator, regardless of how the values were passed in.
   */
  public BigFraction(BigInteger numerator, BigInteger denominator)
  {
    if(numerator == null)
      throw new IllegalArgumentException("Numerator is null");
    if(denominator == null)
      throw new IllegalArgumentException("Denominator is null");
    if(denominator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero.");

    //only numerator should be negative.
    if(denominator.signum() < 0)
    {
      numerator = numerator.negate();
      denominator = denominator.negate();
    }

    //create a reduced fraction
    BigInteger gcd = numerator.gcd(denominator);
    this.numerator = numerator.divide(gcd);
    this.denominator = denominator.divide(gcd);
  }

  /**
   * Constructs a BigFraction from a whole number.
   */
  public BigFraction(BigInteger numerator)
  {
    this(numerator, BigInteger.ONE, true);
  }

  public BigFraction(long numerator, long denominator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
  }

  public BigFraction(long numerator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.ONE, true);
  }

  /**
   * Constructs a BigFraction from a floating-point number.
   * 
   * Warning: round-off error in IEEE floating point numbers can result
   * in answers that are unexpected.  For example, 
   *     System.out.println(new BigFraction(1.1))
   * will print:
   *     2476979795053773/2251799813685248
   * 
   * This is because 1.1 cannot be expressed exactly in binary form.  The
   * given fraction is exactly equal to the internal representation of
   * the double-precision floating-point number.  (Which, for 1.1, is:
   * (-1)^0 * 2^0 * (1 + 0x199999999999aL / 0x10000000000000L).)
   * 
   * NOTE: In many cases, BigFraction(Double.toString(d)) may give a result
   * closer to what the user expects.
   */
  public BigFraction(double d)
  {
    if(Double.isInfinite(d))
      throw new IllegalArgumentException("double val is infinite");
    if(Double.isNaN(d))
      throw new IllegalArgumentException("double val is NaN");

    //special case - math below won't work right for 0.0 or -0.0
    if(d == 0)
    {
      numerator = BigInteger.ZERO;
      denominator = BigInteger.ONE;
      return;
    }

    final long bits = Double.doubleToLongBits(d);
    final int sign = (int)(bits >> 63) & 0x1;
    final int exponent = ((int)(bits >> 52) & 0x7ff) - 0x3ff;
    final long mantissa = bits & 0xfffffffffffffL;

    //number is (-1)^sign * 2^(exponent) * 1.mantissa
    BigInteger tmpNumerator = BigInteger.valueOf(sign==0 ? 1 : -1);
    BigInteger tmpDenominator = BigInteger.ONE;

    //use shortcut: 2^x == 1 << x.  if x is negative, shift the denominator
    if(exponent >= 0)
      tmpNumerator = tmpNumerator.multiply(BigInteger.ONE.shiftLeft(exponent));
    else
      tmpDenominator = tmpDenominator.multiply(BigInteger.ONE.shiftLeft(-exponent));

    //1.mantissa == 1 + mantissa/2^52 == (2^52 + mantissa)/2^52
    tmpDenominator = tmpDenominator.multiply(BigInteger.valueOf(0x10000000000000L));
    tmpNumerator = tmpNumerator.multiply(BigInteger.valueOf(0x10000000000000L + mantissa));

    BigInteger gcd = tmpNumerator.gcd(tmpDenominator);
    numerator = tmpNumerator.divide(gcd);
    denominator = tmpDenominator.divide(gcd);
  }

  /**
   * Constructs a BigFraction from two floating-point numbers.
   * 
   * Warning: round-off error in IEEE floating point numbers can result
   * in answers that are unexpected.  See BigFraction(double) for more
   * information.
   * 
   * NOTE: In many cases, BigFraction(Double.toString(numerator) + "/" + Double.toString(denominator))
   * may give a result closer to what the user expects.
   */
  public BigFraction(double numerator, double denominator)
  {
    if(denominator == 0)
      throw new ArithmeticException("Divide by zero.");

    BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
    this.numerator = tmp.numerator;
    this.denominator = tmp.denominator;
  }

  /**
   * Constructs a new BigFraction from the given BigDecimal object.
   */
  public BigFraction(BigDecimal d)
  {
    this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
         d.scale() < 0 ? BigInteger.ONE                                             : BigInteger.TEN.pow(d.scale()));
  }

  public BigFraction(BigDecimal numerator, BigDecimal denominator)
  {
    if(denominator.equals(BigDecimal.ZERO))
      throw new ArithmeticException("Divide by zero.");

    BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
    this.numerator = tmp.numerator;
    this.denominator = tmp.denominator;
  }

  /**
   * Constructs a BigFraction from a String.  Expected format is numerator/denominator,
   * but /denominator part is optional.  Either numerator or denominator may be a floating-
   * point decimal number, which in the same format as a parameter to the
   * <code>BigDecimal(String)</code> constructor.
   * 
   * @throws NumberFormatException  if the string cannot be properly parsed.
   */
  public BigFraction(String s)
  {
    int slashPos = s.indexOf('/');
    if(slashPos < 0)
    {
      BigFraction res = new BigFraction(new BigDecimal(s));
      this.numerator = res.numerator;
      this.denominator = res.denominator;
    }
    else
    {
      BigDecimal num = new BigDecimal(s.substring(0, slashPos));
      BigDecimal den = new BigDecimal(s.substring(slashPos+1, s.length()));
      BigFraction res = new BigFraction(num, den);
      this.numerator = res.numerator;
      this.denominator = res.denominator;
    }
  }

  /**
   * Returns this + f.
   */
  public BigFraction add(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    //n1/d1 + n2/d2 = (n1*d2 + d1*n2)/(d1*d2) 
    return new BigFraction(numerator.multiply(f.denominator).add(denominator.multiply(f.numerator)),
                           denominator.multiply(f.denominator));
  }

  /**
   * Returns this + b.
   */
  public BigFraction add(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    //n1/d1 + n2 = (n1 + d1*n2)/d1
    return new BigFraction(numerator.add(denominator.multiply(b)),
                           denominator, true);
  }

  /**
   * Returns this + n.
   */
  public BigFraction add(long n)
  {
    return add(BigInteger.valueOf(n));
  }

  /**
   * Returns this - f.
   */
  public BigFraction subtract(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(f.denominator).subtract(denominator.multiply(f.numerator)),
                           denominator.multiply(f.denominator));
  }

  /**
   * Returns this - b.
   */
  public BigFraction subtract(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.subtract(denominator.multiply(b)),
                           denominator, true);
  }

  /**
   * Returns this - n.
   */
  public BigFraction subtract(long n)
  {
    return subtract(BigInteger.valueOf(n));
  }

  /**
   * Returns this * f.
   */
  public BigFraction multiply(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(f.numerator), denominator.multiply(f.denominator));
  }

  /**
   * Returns this * b.
   */
  public BigFraction multiply(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    return new BigFraction(numerator.multiply(b), denominator);
  }

  /**
   * Returns this * n.
   */
  public BigFraction multiply(long n)
  {
    return multiply(BigInteger.valueOf(n));
  }

  /**
   * Returns this / f.
   */
  public BigFraction divide(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    if(f.numerator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(numerator.multiply(f.denominator), denominator.multiply(f.numerator));
  }

  /**
   * Returns this / b.
   */
  public BigFraction divide(BigInteger b)
  {
    if(b == null)
      throw new IllegalArgumentException("Null argument");

    if(b.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(numerator, denominator.multiply(b));
  }

  /**
   * Returns this / n.
   */
  public BigFraction divide(long n)
  {
    return divide(BigInteger.valueOf(n));
  }

  /**
   * Returns this^exponent.
   */
  public BigFraction pow(int exponent)
  {
    if(exponent == 0)
      return BigFraction.ONE;
    else if (exponent == 1)
      return this;
    else if (exponent < 0)
      return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent), true);
    else
      return new BigFraction(numerator.pow(exponent), denominator.pow(exponent), true);
  }

  /**
   * Returns 1/this.
   */
  public BigFraction reciprocal()
  {
    if(this.numerator.equals(BigInteger.ZERO))
      throw new ArithmeticException("Divide by zero");

    return new BigFraction(denominator, numerator, true);
  }

  /**
   * Returns the complement of this fraction, which is equal to 1 - this.
   * Useful for probabilities/statistics.

   */
  public BigFraction complement()
  {
    return new BigFraction(denominator.subtract(numerator), denominator, true);
  }

  /**
   * Returns -this.
   */
  public BigFraction negate()
  {
    return new BigFraction(numerator.negate(), denominator, true);
  }

  /**
   * Returns -1, 0, or 1, representing the sign of this fraction.
   */
  public int signum()
  {
    return numerator.signum();
  }

  /**
   * Returns the absolute value of this.
   */
  public BigFraction abs()
  {
    return (signum() < 0 ? negate() : this);
  }

  /**
   * Returns a string representation of this, in the form
   * numerator/denominator.
   */
  public String toString()
  {
    return numerator.toString() + "/" + denominator.toString();
  }

  /**
   * Returns if this object is equal to another object.
   */
  public boolean equals(Object o)
  {
    if(!(o instanceof BigFraction))
      return false;

    BigFraction f = (BigFraction)o;
    return numerator.equals(f.numerator) && denominator.equals(f.denominator);
  }

  /**
   * Returns a hash code for this object.
   */
  public int hashCode()
  {
    //using the method generated by Eclipse, but streamlined a bit..
    return (31 + numerator.hashCode())*31 + denominator.hashCode();
  }

  /**
   * Returns a negative, zero, or positive number, indicating if this object
   * is less than, equal to, or greater than f, respectively.
   */
  public int compareTo(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    //easy case: this and f have different signs
    if(signum() != f.signum())
      return signum() - f.signum();

    //next easy case: this and f have the same denominator
    if(denominator.equals(f.denominator))
      return numerator.compareTo(f.numerator);

    //not an easy case, so first make the denominators equal then compare the numerators 
    return numerator.multiply(f.denominator).compareTo(denominator.multiply(f.numerator));
  }

  /**
   * Returns the smaller of this and f.
   */
  public BigFraction min(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return (this.compareTo(f) <= 0 ? this : f);
  }

  /**
   * Returns the maximum of this and f.
   */
  public BigFraction max(BigFraction f)
  {
    if(f == null)
      throw new IllegalArgumentException("Null argument");

    return (this.compareTo(f) >= 0 ? this : f);
  }

  /**
   * Returns a positive BigFraction, greater than or equal to zero, and less than one.
   */
  public static BigFraction random()
  {
    return new BigFraction(Math.random());
  }

  public final BigInteger getNumerator() { return numerator; }
  public final BigInteger getDenominator() { return denominator; }

  //implementation of Number class.  may cause overflow.
  public byte   byteValue()   { return (byte) Math.max(Byte.MIN_VALUE,    Math.min(Byte.MAX_VALUE,    longValue())); }
  public short  shortValue()  { return (short)Math.max(Short.MIN_VALUE,   Math.min(Short.MAX_VALUE,   longValue())); }
  public int    intValue()    { return (int)  Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, longValue())); }
  public long   longValue()   { return Math.round(doubleValue()); }
  public float  floatValue()  { return (float)doubleValue(); }
  public double doubleValue() { return toBigDecimal(18).doubleValue(); }

  /**
   * Returns a BigDecimal representation of this fraction.  If possible, the
   * returned value will be exactly equal to the fraction.  If not, the BigDecimal
   * will have a scale large enough to hold the same number of significant figures
   * as both numerator and denominator, or the equivalent of a double-precision
   * number, whichever is more.
   */
  public BigDecimal toBigDecimal()
  {
    //Implementation note:  A fraction can be represented exactly in base-10 iff its
    //denominator is of the form 2^a * 5^b, where a and b are nonnegative integers.
    //(In other words, if there are no prime factors of the denominator except for
    //2 and 5, or if the denominator is 1).  So to determine if this denominator is
    //of this form, continually divide by 2 to get the number of 2's, and then
    //continually divide by 5 to get the number of 5's.  Afterward, if the denominator
    //is 1 then there are no other prime factors.

    //Note: number of 2's is given by the number of trailing 0 bits in the number
    int twos = denominator.getLowestSetBit();
    BigInteger tmpDen = denominator.shiftRight(twos); // x / 2^n === x >> n

    final BigInteger FIVE = BigInteger.valueOf(5);
    int fives = 0;
    BigInteger[] divMod = null;

    //while(tmpDen % 5 == 0) { fives++; tmpDen /= 5; }
    while(BigInteger.ZERO.equals((divMod = tmpDen.divideAndRemainder(FIVE))[1]))
    {
      fives++;
      tmpDen = divMod[0];
    }

    if(BigInteger.ONE.equals(tmpDen))
    {
      //This fraction will terminate in base 10, so it can be represented exactly as
      //a BigDecimal.  We would now like to make the fraction of the form
      //unscaled / 10^scale.  We know that 2^x * 5^x = 10^x, and our denominator is
      //in the form 2^twos * 5^fives.  So use max(twos, fives) as the scale, and
      //multiply the numerator and deminator by the appropriate number of 2's or 5's
      //such that the denominator is of the form 2^scale * 5^scale.  (Of course, we
      //only have to actually multiply the numerator, since all we need for the
      //BigDecimal constructor is the scale.
      BigInteger unscaled = numerator;
      int scale = Math.max(twos, fives);

      if(twos < fives)
        unscaled = unscaled.shiftLeft(fives - twos); //x * 2^n === x << n
      else if (fives < twos)
        unscaled = unscaled.multiply(FIVE.pow(twos - fives));

      return new BigDecimal(unscaled, scale);
    }

    //else: this number will repeat infinitely in base-10.  So try to figure out
    //a good number of significant digits.  Start with the number of digits required
    //to represent the numerator and denominator in base-10, which is given by
    //bitLength / log[2](10).  (bitLenth is the number of digits in base-2).
    final double LG10 = 3.321928094887362; //Precomputed ln(10)/ln(2), a.k.a. log[2](10)
    int precision = Math.max(numerator.bitLength(), denominator.bitLength());
    precision = (int)Math.ceil(precision / LG10);

    //If the precision is less than 18 digits, use 18 digits so that the number
    //will be at least as accurate as a cast to a double.  For example, with
    //the fraction 1/3, precision will be 1, giving a result of 0.3.  This is
    //quite a bit different from what a user would expect.
    if(precision < 18)
      precision = 18;

    return toBigDecimal(precision);
  }

  /**
   * Returns a BigDecimal representation of this fraction, with a given precision.
   * @param precision  the number of significant figures to be used in the result.
   */
  public BigDecimal toBigDecimal(int precision)
  {
    return new BigDecimal(numerator).divide(new BigDecimal(denominator), new MathContext(precision, RoundingMode.HALF_EVEN));
  }

  //--------------------------------------------------------------------------
  //  PRIVATE FUNCTIONS
  //--------------------------------------------------------------------------

  /**
   * Private constructor, used when you can be certain that the fraction is already in
   * lowest terms.  No check is done to reduce numerator/denominator.  A check is still
   * done to maintain a positive denominator.
   * 
   * @param throwaway  unused variable, only here to signal to the compiler that this
   *                   constructor should be used.
   */
  private BigFraction(BigInteger numerator, BigInteger denominator, boolean throwaway)
  {
    if(denominator.signum() < 0)
    {
      this.numerator = numerator.negate();
      this.denominator = denominator.negate();
    }
    else
    {
      this.numerator = numerator;
      this.denominator = denominator;
    }
  }

}
睡美人的小仙女 2024-07-19 13:19:24

事实上,请试穿一下尺寸。 它运行但可能有一些问题:

public class BigRational extends Number implements Comparable<BigRational>, Serializable {
    public final static BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
    private final static long serialVersionUID = 1099377265582986378L;

    private final BigInteger numerator, denominator;

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

    private static BigRational 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 BigRational(numerator, denominator);
    }

    public static BigRational getInstance(BigInteger numerator, BigInteger denominator) {
        return canonical(numerator, denominator, true);
    }

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

    public static BigRational getInstance(String numerator, String denominator) {
        return canonical(new BigInteger(numerator), new BigInteger(denominator), true);
    }

    public static BigRational 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 + "'");
        }

        // this translates 23.123e5 to 25,123 / 1000 * 10^5 = 2,512,300 / 1 (GCD)
        String whole = m.group(1);
        String decimal = m.group(2);
        String exponent = m.group(3);
        String n = whole;

        // 23.123 => 23123
        if (decimal != null) {
            n += decimal;
        }
        BigInteger numerator = new BigInteger(n);

        // exponent is an int because BigInteger.pow() takes an int argument
        // it gets more difficult if exponent needs to be outside {-2 billion,2 billion}
        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;
        }

        // done
        return canonical(numerator, denominator, true);
    }

    // Comparable
    public int compareTo(BigRational o) {
        // note: this is a bit of cheat, relying on BigInteger.compareTo() returning
        // -1, 0 or 1.  For the more general contract of compareTo(), you'd need to do
        // more checking
        if (numerator.signum() != o.numerator.signum()) {
            return numerator.signum() - o.numerator.signum();
        } else {
            // oddly BigInteger has gcd() but no lcm()
            BigInteger i1 = numerator.multiply(o.denominator);
            BigInteger i2 = o.numerator.multiply(denominator);
            return i1.compareTo(i2); // expensive!
        }
    }

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


    public BigRational multiply(BigRational 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 BigInteger getNumerator() { return numerator; }
    public BigInteger getDenominator() { return denominator; }
    public boolean isInteger() { return numerator.signum() == 0 || denominator.equals(BigInteger.ONE); }
    public BigRational negate() { return new BigRational(numerator.negate(), denominator); }
    public BigRational invert() { return canonical(denominator, numerator, false); }
    public BigRational abs() { return numerator.signum() < 0 ? negate() : this; }
    public BigRational pow(int exp) { return canonical(numerator.pow(exp), denominator.pow(exp), true); }
    public BigRational subtract(BigRational o) { return add(o.negate()); }
    public BigRational divide(BigRational o) { return multiply(o.invert()); }
    public BigRational min(BigRational o) { return compareTo(o) <= 0 ? this : o; }
    public BigRational max(BigRational o) { return compareTo(o) >= 0 ? this : o; }

    public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode) {
        return isInteger() ? new BigDecimal(numerator) : new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
    }

    // Number
    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); }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BigRational that = (BigRational) o;

        if (denominator != null ? !denominator.equals(that.denominator) : that.denominator != null) return false;
        if (numerator != null ? !numerator.equals(that.numerator) : that.numerator != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = numerator != null ? numerator.hashCode() : 0;
        result = 31 * result + (denominator != null ? denominator.hashCode() : 0);
        return result;
    }

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

    public static void dump(String name, BigRational 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();
    }
}

输出是:

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

In fact, try this on for size. It runs but may have some issues:

public class BigRational extends Number implements Comparable<BigRational>, Serializable {
    public final static BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
    private final static long serialVersionUID = 1099377265582986378L;

    private final BigInteger numerator, denominator;

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

    private static BigRational 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 BigRational(numerator, denominator);
    }

    public static BigRational getInstance(BigInteger numerator, BigInteger denominator) {
        return canonical(numerator, denominator, true);
    }

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

    public static BigRational getInstance(String numerator, String denominator) {
        return canonical(new BigInteger(numerator), new BigInteger(denominator), true);
    }

    public static BigRational 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 + "'");
        }

        // this translates 23.123e5 to 25,123 / 1000 * 10^5 = 2,512,300 / 1 (GCD)
        String whole = m.group(1);
        String decimal = m.group(2);
        String exponent = m.group(3);
        String n = whole;

        // 23.123 => 23123
        if (decimal != null) {
            n += decimal;
        }
        BigInteger numerator = new BigInteger(n);

        // exponent is an int because BigInteger.pow() takes an int argument
        // it gets more difficult if exponent needs to be outside {-2 billion,2 billion}
        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;
        }

        // done
        return canonical(numerator, denominator, true);
    }

    // Comparable
    public int compareTo(BigRational o) {
        // note: this is a bit of cheat, relying on BigInteger.compareTo() returning
        // -1, 0 or 1.  For the more general contract of compareTo(), you'd need to do
        // more checking
        if (numerator.signum() != o.numerator.signum()) {
            return numerator.signum() - o.numerator.signum();
        } else {
            // oddly BigInteger has gcd() but no lcm()
            BigInteger i1 = numerator.multiply(o.denominator);
            BigInteger i2 = o.numerator.multiply(denominator);
            return i1.compareTo(i2); // expensive!
        }
    }

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


    public BigRational multiply(BigRational 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 BigInteger getNumerator() { return numerator; }
    public BigInteger getDenominator() { return denominator; }
    public boolean isInteger() { return numerator.signum() == 0 || denominator.equals(BigInteger.ONE); }
    public BigRational negate() { return new BigRational(numerator.negate(), denominator); }
    public BigRational invert() { return canonical(denominator, numerator, false); }
    public BigRational abs() { return numerator.signum() < 0 ? negate() : this; }
    public BigRational pow(int exp) { return canonical(numerator.pow(exp), denominator.pow(exp), true); }
    public BigRational subtract(BigRational o) { return add(o.negate()); }
    public BigRational divide(BigRational o) { return multiply(o.invert()); }
    public BigRational min(BigRational o) { return compareTo(o) <= 0 ? this : o; }
    public BigRational max(BigRational o) { return compareTo(o) >= 0 ? this : o; }

    public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode) {
        return isInteger() ? new BigDecimal(numerator) : new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
    }

    // Number
    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); }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BigRational that = (BigRational) o;

        if (denominator != null ? !denominator.equals(that.denominator) : that.denominator != null) return false;
        if (numerator != null ? !numerator.equals(that.numerator) : that.numerator != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = numerator != null ? numerator.hashCode() : 0;
        result = 31 * result + (denominator != null ? denominator.hashCode() : 0);
        return result;
    }

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

    public static void dump(String name, BigRational 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();
    }
}

Output is:

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
枕头说它不想醒 2024-07-19 13:19:24

我正在尝试在 Java 中使用真分数。

Apache Commons Math 已经有一个 Fraction 类已经有一段时间了。 大多数时候的答案是:“我希望 Java 在核心库中有像 X 这样的东西!” 可以在 Apache Commons 库 下找到。

I'm trying to work with proper fractions in Java.

Apache Commons Math has had a Fraction class for quite some time. Most times the answer to, "Boy I wish Java had something like X in the core library!" can be found under the umbrella of the Apache Commons library.

用心笑 2024-07-19 13:19:24

请使其成为不可变类型! 分数的值不会改变——例如,二分之一不会变成三分之一。 您可以使用 withDenominator 代替 setDenominator,它返回一个新分数,该分数具有相同的分子但具有指定的分母。

使用不可变类型,生活会变得容易

覆盖 equals 和 hashcode 也是明智的,因此它可以在映射和集合中使用。 Outlaw Programmer 关于算术运算符和字符串格式的观点也很好。

作为一般指南,请查看 BigInteger 和 BigDecimal。 他们做的事情并不相同,但他们足够相似,可以给你提供很好的想法。

Please make it an immutable type! The value of a fraction doesn't change - a half doesn't become a third, for example. Instead of setDenominator, you could have withDenominator which returns a new fraction which has the same numerator but the specified denominator.

Life is much easier with immutable types.

Overriding equals and hashcode would be sensible too, so it can be used in maps and sets. Outlaw Programmer's points about arithmetic operators and string formatting are good too.

As a general guide, have a look at BigInteger and BigDecimal. They're not doing the same thing, but they're similar enough to give you good ideas.

西瓜 2024-07-19 13:19:24

嗯,一方面,我会去掉 setter 并使 Fractions 不可变。

您可能还需要加法、减法等方法,也许还需要某种方法来获取各种字符串格式的表示形式。

编辑:我可能会将这些字段标记为“最终”以表明我的意图,但我想这没什么大不了的......

Well, for one, I'd get rid of the setters and make Fractions immutable.

You'll probably also want methods to add, subtract, etc., and maybe some way to get the representation in various String formats.

EDIT: I'd probably mark the fields as 'final' to signal my intent but I guess it's not a big deal...

南街女流氓 2024-07-19 13:19:24
  • 如果没有像add()和multiply()等算术方法,这有点毫无意义。
  • 你绝对应该重写equals()和hashCode()。
  • 您应该添加一个方法来标准化分数,或者自动执行。 考虑一下您是否希望 1/2 和 2/4 被视为相同 - 这对 equals()、hashCode() 和compareTo() 方法有影响。
  • It's kinda pointless without arithmetic methods like add() and multiply(), etc.
  • You should definitely override equals() and hashCode().
  • You should either add a method to normalize the fraction, or do it automatically. Think about whether you want 1/2 and 2/4 to be considered the same or not - this has implications for the equals(), hashCode() and compareTo() methods.
最笨的告白 2024-07-19 13:19:24

我需要将它们从小到大排序,
所以最终我还需要将它们表示为双精度

是绝对必要的。 (事实上​​,如果您想正确处理相等性,请不要依赖 double 来正常工作。)如果 b*d 为正,则 a/b < c/d 如果 ad < 公元前。 如果涉及到负整数,可以适当处理...

我可能会重写为:

public int compareTo(Fraction frac)
{
    // we are comparing this=a/b with frac=c/d 
    // by multiplying both sides by bd.
    // If bd is positive, then a/b < c/d <=> ad < bc.
    // If bd is negative, then a/b < c/d <=> ad > bc.
    // If bd is 0, then you've got other problems (either b=0 or d=0)
    int d = frac.getDenominator();
    long ad = (long)this.numerator * d;
    long bc = (long)this.denominator * frac.getNumerator();
    long diff = ((long)d*this.denominator > 0) ? (ad-bc) : (bc-ad);
    return (diff > 0 ? 1 : (diff < 0 ? -1 : 0));
}

这里使用 long 是为了确保在将两个大 int 相乘时不会溢出。代码>s. 如果你能保证分母总是非负的(如果是负数,只需将分子和分母都取负即可),那么你就可以不必检查 b*d 是否为正,并节省一些步骤。 我不确定您正在寻找零分母的什么行为。

不确定与使用双打进行比较相比性能如何。 (也就是说,如果您非常关心性能)这是我用来检查的测试方法。 (看起来工作正常。)

public static void main(String[] args)
{
    int a = Integer.parseInt(args[0]);
    int b = Integer.parseInt(args[1]);
    int c = Integer.parseInt(args[2]);
    int d = Integer.parseInt(args[3]);
    Fraction f1 = new Fraction(a,b); 
    Fraction f2 = new Fraction(c,d);
    int rel = f1.compareTo(f2);
    String relstr = "<=>";
    System.out.println(a+"/"+b+" "+relstr.charAt(rel+1)+" "+c+"/"+d);
}

(ps,您可能会考虑重组以实现您的类的 ComparableComparator 。)

I will need to order them from smallest to largest,
so eventually I will need to represent them as a double also

Not strictly necessary. (In fact if you want to handle equality correctly, don't rely on double to work properly.) If b*d is positive, a/b < c/d if ad < bc. If there are negative integers involved, that can be handled appropriately...

I might rewrite as:

public int compareTo(Fraction frac)
{
    // we are comparing this=a/b with frac=c/d 
    // by multiplying both sides by bd.
    // If bd is positive, then a/b < c/d <=> ad < bc.
    // If bd is negative, then a/b < c/d <=> ad > bc.
    // If bd is 0, then you've got other problems (either b=0 or d=0)
    int d = frac.getDenominator();
    long ad = (long)this.numerator * d;
    long bc = (long)this.denominator * frac.getNumerator();
    long diff = ((long)d*this.denominator > 0) ? (ad-bc) : (bc-ad);
    return (diff > 0 ? 1 : (diff < 0 ? -1 : 0));
}

The use of long here is to ensure there's not an overflow if you multiply two large ints. handle If you can guarantee that the denominator is always nonnegative (if it's negative, just negate both numerator and denominator), then you can get rid of having to check whether b*d is positive and save a few steps. I'm not sure what behavior you're looking for with zero denominator.

Not sure how performance compares to using doubles to compare. (that is, if you care about performance that much) Here's a test method I used to check. (Appears to work properly.)

public static void main(String[] args)
{
    int a = Integer.parseInt(args[0]);
    int b = Integer.parseInt(args[1]);
    int c = Integer.parseInt(args[2]);
    int d = Integer.parseInt(args[3]);
    Fraction f1 = new Fraction(a,b); 
    Fraction f2 = new Fraction(c,d);
    int rel = f1.compareTo(f2);
    String relstr = "<=>";
    System.out.println(a+"/"+b+" "+relstr.charAt(rel+1)+" "+c+"/"+d);
}

(p.s. you might consider restructuring to implement Comparable or Comparator for your class.)

人间不值得 2024-07-19 13:19:24

一个非常小的改进可能是保存您正在计算的双精度值,以便您仅在第一次访问时计算它。 除非您经常访问这个数字,否则这不会是一个巨大的胜利,但这也不是太难做到。

另外一点可能是您在分母中进行的错误检查...您会自动将 0 更改为 1。不确定这对于您的特定应用程序是否正确,但一般来说,如果有人尝试除以 0,则表示出现了非常错误的情况。 我会让它抛出一个异常(如果您认为需要的话,这是一个专门的异常),而不是以用户不知道的看似任意的方式更改值。

与其他一些评论相比,关于添加方法来添加减法等......因为您没有提到需要它们,所以我假设您不需要。 除非您正在构建一个真正要在许多地方或由其他人使用的库,否则请使用 YAGNI(您不会需要它,所以它不应该在那里。)

One very minor improvement could potentially be to save the double value that you're computing so that you only compute it on the first access. This won't be a big win unless you're accessing this number a lot, but it's not overly difficult to do, either.

One additional point might be the error checking you do in the denominator...you automatically change 0 to 1. Not sure if this is correct for your particular application, but in general if someone is trying to divide by 0, something is very wrong. I'd let this throw an exception (a specialized exception if you feel it's needed) rather than change the value in a seemingly arbitrary way that isn't known to the user.

In constrast with some other comments, about adding methods to add subtract, etc...since you didn't mention needing them, I'm assuming you don't. And unless you're building a library that is really going to be used in many places or by other people, go with YAGNI (you ain't going to need it, so it shouldn't be there.)

狼性发作 2024-07-19 13:19:24

有多种方法可以改进此值类型或任何值类型:

  • 使您的类不可变,包括使分子和分母 最终
  • 自动将分数转换为规范形式,例如 2/4 -> 1/2
  • 实现 toString()
  • 实现“public static Fraction valueOf(String s)”以将字符串转换为分数。 实现类似的工厂方法,用于从 int、double 等进行转换。
  • 实现加法、乘法等
  • 从整数添加构造函数
  • 覆盖 equals/hashCode
  • 考虑使 Fraction 成为一个接口,并根据需要切换到 BigInteger 的实现
  • 考虑子类化 Number
  • 考虑包含常见值(如 0 和 1)的命名常量
  • 考虑使其可序列化
  • 测试除以零
  • 记录您的 API

基本上,查看其他值类的 API,例如 Double,整数并做他们所做的:)

There are several ways to improve this or any value type:

  • Make your class immutable, including making numerator and denominator final
  • Automatically convert fractions to a canonical form, e.g. 2/4 -> 1/2
  • Implement toString()
  • Implement "public static Fraction valueOf(String s)" to convert from strings to fractions. Implement similar factory methods for converting from int, double, etc.
  • Implement addition, multiplication, etc
  • Add constructor from whole numbers
  • Override equals/hashCode
  • Consider making Fraction an interface with an implementation that switches to BigInteger as necessary
  • Consider sub-classing Number
  • Consider including named constants for common values like 0 and 1
  • Consider making it serializable
  • Test for division by zero
  • Document your API

Basically, take a look at the API for other value classes like Double, Integer and do what they do :)

舂唻埖巳落 2024-07-19 13:19:24

如果将一个分数的分子和分母与另一个分数的分母相乘,反之亦然,最终会得到两个具有相同分母的分数(仍然是相同的值),您可以直接比较分子。 因此您不需要计算双精度值:

public int compareTo(Fraction frac) {
    int t = this.numerator * frac.getDenominator();
    int f = frac.getNumerator() * this.denominator;
    if(t>f) return 1;
    if(f>t) return -1;
    return 0;
}

If you multiply the numerator and denominator of one Fraction with the denominator of the other and vice versa, you end up with two fractions (that are still the same values) with the same denominator and you can compare the numerators directly. Therefore you wouldn't need to calculate the double value:

public int compareTo(Fraction frac) {
    int t = this.numerator * frac.getDenominator();
    int f = frac.getNumerator() * this.denominator;
    if(t>f) return 1;
    if(f>t) return -1;
    return 0;
}
碍人泪离人颜 2024-07-19 13:19:24

我将如何改进该代码:

  1. 基于 String Fraction(String s) //expect "number/number" 的构造函数 复制
  2. 构造函数 Fraction(Fraction copy)
  3. 重写克隆方法
  4. 实现 equals、toString 和 hashcode 方法
  5. 实现接口 java.lang.String Fraction (String s) //expect "number/number" io.Serialized,可比较
  6. 方法“double getDoubleValue()”
  7. 方法添加/划分/等等...
  8. 我会让该类成为不可变的(没有设置器)

how I would improve that code:

  1. a constructor based on String Fraction(String s) //expect "number/number"
  2. a copy constructor Fraction(Fraction copy)
  3. override the clone method
  4. implements the equals, toString and hashcode methods
  5. implements the interface java.io.Serializable, Comparable
  6. a method "double getDoubleValue()"
  7. a method add/divide/etc...
  8. I would make that class as immutable (no setters)
月棠 2024-07-19 13:19:24

你已经有一个compareTo函数...我将实现Comparable接口。

不过,对于你要用它做什么可能并不重要。

You have a compareTo function already ... I would implement the Comparable interface.

May not really matter for whatever you're going to do with it though.

七月上 2024-07-19 13:19:24

如果您喜欢冒险,请查看 JScience。 它有一个 Rational 类代表分数。

If you're feeling adventurous, take a look at JScience. It has a Rational class that represents fractions.

善良天后 2024-07-19 13:19:24

具体:是否有更好的方法来处理传递零分母? 将分母设置为 1 感觉非常随意。 我怎样才能正确地做到这一点?

我会说抛出一个 ArithmeticException 来除以零,因为这确实发生了:

public Fraction(int numerator, int denominator) {
    if(denominator == 0)
        throw new ArithmeticException("Divide by zero.");
    this.numerator = numerator;
    this.denominator = denominator;
}

而不是“除以零。”,您可能想让消息说“除以零:分数的分母为零。”

Specifically: Is there a better way to handle being passed a zero denominator? Setting the denominator to 1 is feels mighty arbitrary. How can I do this right?

I would say throw a ArithmeticException for divide by zero, since that's really what's happening:

public Fraction(int numerator, int denominator) {
    if(denominator == 0)
        throw new ArithmeticException("Divide by zero.");
    this.numerator = numerator;
    this.denominator = denominator;
}

Instead of "Divide by zero.", you might want to make the message say "Divide by zero: Denominator for Fraction is zero."

温柔嚣张 2024-07-19 13:19:24

创建分数对象后,为什么要允许其他对象设置分子或分母? 我认为这些应该是只读的。 它使对象不可变...

另外...将分母设置为零应该抛出无效参数异常(我不知道它在Java中是什么)

Once you've created a fraction object why would you want to allow other objects to set the numerator or the denominator? I would think these should be read only. It makes the object immutable...

Also...setting the denominator to zero should throw an invalid argument exception (I don't know what it is in Java)

奢华的一滴泪 2024-07-19 13:19:24

Timothy Budd 在他的“C++ 数据结构”中对 Rational 类进行了很好的实现。 当然,不同的语言,但它可以很好地移植到 Java。

我会推荐更多的构造函数。 默认构造函数的分子为 0,分母为 1。单个 arg 构造函数将假定分母为 1。想想您的用户可能如何使用此类。

不检查零分母吗? 按合同编程会让你添加它。

Timothy Budd has a fine implementation of a Rational class in his "Data Structures in C++". Different language, of course, but it ports over to Java very nicely.

I'd recommend more constructors. A default constructor would have numerator 0, denominator 1. A single arg constructor would assume a denominator of 1. Think how your users might use this class.

No check for zero denominator? Programming by contract would have you add it.

小草泠泠 2024-07-19 13:19:24

我将提出第三个或第五个或任何使您的分数不可变的建议。 我还建议您扩展 Number 类。 我可能会看看 Double类,因为您可能想要实现许多相同的方法。

您可能还应该实现 Comparable 和 < a href="http://java.sun.com/javase/6/docs/api/java/io/Serializing.html" rel="nofollow noreferrer">Serialized 因为这种行为可能是预期的。 因此,您需要实现compareTo()。 您还需要重写 equals(),并且我必须强调您还需要重写 hashCode()。 这可能是您不希望compareTo()和equals()保持一致的少数情况之一,因为可彼此约简的分数不一定相等。

I'll third or fifth or whatever the recommendation for making your fraction immutable. I'd also recommend that you have it extend the Number class. I'd probably look at the Double class, since you're probably going to want to implement many of the same methods.

You should probably also implement Comparable and Serializable since this behavior will probably be expected. Thus, you will need to implement compareTo(). You will also need to override equals() and I cannot stress strongly enough that you also override hashCode(). This might be one of the few cases though where you don't want compareTo() and equals() to be consistent since fractions reducable to each other are not necessarily equal.

迷鸟归林 2024-07-19 13:19:24

我喜欢的一种清理做法是只有一次返回。

 public int compareTo(Fraction frac) {
        int result = 0
        double t = this.doubleValue();
        double f = frac.doubleValue();
        if(t>f) 
           result = 1;
        else if(f>t) 
           result -1;
        return result;
    }

A clean up practice that I like is to only have only one return.

 public int compareTo(Fraction frac) {
        int result = 0
        double t = this.doubleValue();
        double f = frac.doubleValue();
        if(t>f) 
           result = 1;
        else if(f>t) 
           result -1;
        return result;
    }
萌化 2024-07-19 13:19:24

使用 JScience 库中的 Rational 类。 这是我在 Java 中看到的最适合小数算术的东西。

Use Rational class from JScience library. It's the best thing for fractional arithmetic I seen in Java.

﹉夏雨初晴づ 2024-07-19 13:19:24

我清理了 cletus 的答案

  • 为所有方法添加了 Javadoc。
  • 添加了对方法先决条件的检查。
  • valueOf(String) 中的自定义解析替换为 BigInteger(String),后者更灵活、更快。
import com.google.common.base.Splitter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import org.bitbucket.cowwoc.preconditions.Preconditions;

/**
 * A rational fraction, represented by {@code numerator / denominator}.
 * <p>
 * This implementation is based on <a
 * href="https://stackoverflow.com/a/474577/14731">https://stackoverflow.com/a/474577/14731</a>
 * <p>
 * @author Gili Tzabari
 */
public final class BigRational extends Number implements Comparable<BigRational>
{
    private static final long serialVersionUID = 0L;
    public static final BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
    public static final BigRational ONE = new BigRational(BigInteger.ONE, BigInteger.ONE);

    /**
     * Ensures the fraction the denominator is positive and optionally divides the numerator and
     * denominator by the greatest common factor.
     * <p>
     * @param numerator   a numerator
     * @param denominator a denominator
     * @param checkGcd    true if the numerator and denominator should be divided by the greatest
     *                    common factor
     * @return the canonical representation of the rational fraction
     */
    private static BigRational canonical(BigInteger numerator, BigInteger denominator,
        boolean checkGcd)
    {
        assert (numerator != null);
        assert (denominator != null);
        if (denominator.signum() == 0)
            throw new IllegalArgumentException("denominator is zero");
        if (numerator.signum() == 0)
            return ZERO;
        BigInteger newNumerator = numerator;
        BigInteger newDenominator = denominator;
        if (newDenominator.signum() < 0)
        {
            newNumerator = newNumerator.negate();
            newDenominator = newDenominator.negate();
        }
        if (checkGcd)
        {
            BigInteger gcd = newNumerator.gcd(newDenominator);
            if (!gcd.equals(BigInteger.ONE))
            {
                newNumerator = newNumerator.divide(gcd);
                newDenominator = newDenominator.divide(gcd);
            }
        }
        return new BigRational(newNumerator, newDenominator);
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     * @throws NullPointerException if numerator or denominator are null
     */
    public static BigRational valueOf(BigInteger numerator, BigInteger denominator)
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull();
        Preconditions.requireThat(denominator, "denominator").isNotNull();
        return canonical(numerator, denominator, true);
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     */
    public static BigRational valueOf(long numerator, long denominator)
    {
        BigInteger bigNumerator = BigInteger.valueOf(numerator);
        BigInteger bigDenominator = BigInteger.valueOf(denominator);
        return canonical(bigNumerator, bigDenominator, true);
    }

    /**
     * @param value the parameter value
     * @param name  the parameter name
     * @return the BigInteger representation of the parameter
     * @throws NumberFormatException if value is not a valid representation of BigInteger
     */
    private static BigInteger requireBigInteger(String value, String name)
        throws NumberFormatException
    {
        try
        {
            return new BigInteger(value);
        }
        catch (NumberFormatException e)
        {
            throw (NumberFormatException) new NumberFormatException("Invalid " + name + ": " + value).
                initCause(e);
        }
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     * @throws NullPointerException     if numerator or denominator are null
     * @throws IllegalArgumentException if numerator or denominator are empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    public static BigRational valueOf(String numerator, String denominator)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull().isNotEmpty();
        Preconditions.requireThat(denominator, "denominator").isNotNull().isNotEmpty();
        BigInteger bigNumerator = requireBigInteger(numerator, "numerator");
        BigInteger bigDenominator = requireBigInteger(denominator, "denominator");
        return canonical(bigNumerator, bigDenominator, true);
    }

    /**
     * @param value a string representation of a rational fraction (e.g. "12.34e5" or "3/4")
     * @return a BigRational representation of the String
     * @throws NullPointerException     if value is null
     * @throws IllegalArgumentException if value is empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    public static BigRational valueOf(String value)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
        List<String> fractionParts = Splitter.on('/').splitToList(value);
        if (fractionParts.size() == 1)
            return valueOfRational(value);
        if (fractionParts.size() == 2)
            return BigRational.valueOf(fractionParts.get(0), fractionParts.get(1));
        throw new IllegalArgumentException("Too many slashes: " + value);
    }

    /**
     * @param value a string representation of a rational fraction (e.g. "12.34e5")
     * @return a BigRational representation of the String
     * @throws NullPointerException     if value is null
     * @throws IllegalArgumentException if value is empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    private static BigRational valueOfRational(String value)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
        BigDecimal bigDecimal = new BigDecimal(value);
        int scale = bigDecimal.scale();
        BigInteger numerator = bigDecimal.unscaledValue();
        BigInteger denominator;
        if (scale > 0)
            denominator = BigInteger.TEN.pow(scale);
        else
        {
            numerator = numerator.multiply(BigInteger.TEN.pow(-scale));
            denominator = BigInteger.ONE;
        }

        return canonical(numerator, denominator, true);
    }

    private final BigInteger numerator;
    private final BigInteger denominator;

    /**
     * @param numerator   the numerator
     * @param denominator the denominator
     * @throws NullPointerException if numerator or denominator are null
     */
    private BigRational(BigInteger numerator, BigInteger denominator)
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull();
        Preconditions.requireThat(denominator, "denominator").isNotNull();
        this.numerator = numerator;
        this.denominator = denominator;
    }

    /**
     * @return the numerator
     */
    public BigInteger getNumerator()
    {
        return numerator;
    }

    /**
     * @return the denominator
     */
    public BigInteger getDenominator()
    {
        return denominator;
    }

    @Override
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public int compareTo(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();

        // canonical() ensures denominator is positive
        if (numerator.signum() != other.numerator.signum())
            return numerator.signum() - other.numerator.signum();

        // Set the denominator to a common multiple before comparing the numerators
        BigInteger first = numerator.multiply(other.denominator);
        BigInteger second = other.numerator.multiply(denominator);
        return first.compareTo(second);
    }

    /**
     * @param other another rational fraction
     * @return the result of adding this object to {@code other}
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational add(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();
        if (other.numerator.signum() == 0)
            return this;
        if (numerator.signum() == 0)
            return other;
        if (denominator.equals(other.denominator))
            return new BigRational(numerator.add(other.numerator), denominator);
        return canonical(numerator.multiply(other.denominator).
            add(other.numerator.multiply(denominator)),
            denominator.multiply(other.denominator), true);
    }

    /**
     * @param other another rational fraction
     * @return the result of subtracting {@code other} from this object
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational subtract(BigRational other)
    {
        return add(other.negate());
    }

    /**
     * @param other another rational fraction
     * @return the result of multiplying this object by {@code other}
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational multiply(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();
        if (numerator.signum() == 0 || other.numerator.signum() == 0)
            return ZERO;
        if (numerator.equals(other.denominator))
            return canonical(other.numerator, denominator, true);
        if (other.numerator.equals(denominator))
            return canonical(numerator, other.denominator, true);
        if (numerator.negate().equals(other.denominator))
            return canonical(other.numerator.negate(), denominator, true);
        if (other.numerator.negate().equals(denominator))
            return canonical(numerator.negate(), other.denominator, true);
        return canonical(numerator.multiply(other.numerator), denominator.multiply(other.denominator),
            true);
    }

    /**
     * @param other another rational fraction
     * @return the result of dividing this object by {@code other}
     * @throws NullPointerException if other is null
     */
    public BigRational divide(BigRational other)
    {
        return multiply(other.invert());
    }

    /**
     * @return true if the object is a whole number
     */
    public boolean isInteger()
    {
        return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
    }

    /**
     * Returns a BigRational whose value is (-this).
     * <p>
     * @return -this
     */
    public BigRational negate()
    {
        return new BigRational(numerator.negate(), denominator);
    }

    /**
     * @return a rational fraction with the numerator and denominator swapped
     */
    public BigRational invert()
    {
        return canonical(denominator, numerator, false);
    }

    /**
     * @return the absolute value of this {@code BigRational}
     */
    public BigRational abs()
    {
        if (numerator.signum() < 0)
            return negate();
        return this;
    }

    /**
     * @param exponent exponent to which both numerator and denominator is to be raised.
     * @return a BigRational whose value is (this<sup>exponent</sup>).
     */
    public BigRational pow(int exponent)
    {
        return canonical(numerator.pow(exponent), denominator.pow(exponent), true);
    }

    /**
     * @param other another rational fraction
     * @return the minimum of this object and the other fraction
     */
    public BigRational min(BigRational other)
    {
        if (compareTo(other) <= 0)
            return this;
        return other;
    }

    /**
     * @param other another rational fraction
     * @return the maximum of this object and the other fraction
     */
    public BigRational max(BigRational other)
    {
        if (compareTo(other) >= 0)
            return this;
        return other;
    }

    /**
     * @param scale        scale of the BigDecimal quotient to be returned
     * @param roundingMode the rounding mode to apply
     * @return a BigDecimal representation of this object
     * @throws NullPointerException if roundingMode is null
     */
    public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode)
    {
        Preconditions.requireThat(roundingMode, "roundingMode").isNotNull();
        if (isInteger())
            return new BigDecimal(numerator);
        return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
    }

    @Override
    public int intValue()
    {
        return (int) longValue();
    }

    @Override
    public long longValue()
    {
        if (isInteger())
            return numerator.longValue();
        return numerator.divide(denominator).longValue();
    }

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

    @Override
    public double doubleValue()
    {
        if (isInteger())
            return numerator.doubleValue();
        return numerator.doubleValue() / denominator.doubleValue();
    }

    @Override
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (!(o instanceof BigRational))
            return false;
        BigRational other = (BigRational) o;

        return numerator.equals(other.denominator) && Objects.equals(denominator, other.denominator);
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(numerator, denominator);
    }

    /**
     * Returns the String representation: {@code numerator / denominator}.
     */
    @Override
    public String toString()
    {
        if (isInteger())
            return String.format("%,d", numerator);
        return String.format("%,d / %,d", numerator, denominator);
    }
}

I cleaned up cletus' answer:

  • Added Javadoc for all methods.
  • Added checks for method preconditions.
  • Replaced custom parsing in valueOf(String) with the BigInteger(String) which is both more flexible and faster.
import com.google.common.base.Splitter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import org.bitbucket.cowwoc.preconditions.Preconditions;

/**
 * A rational fraction, represented by {@code numerator / denominator}.
 * <p>
 * This implementation is based on <a
 * href="https://stackoverflow.com/a/474577/14731">https://stackoverflow.com/a/474577/14731</a>
 * <p>
 * @author Gili Tzabari
 */
public final class BigRational extends Number implements Comparable<BigRational>
{
    private static final long serialVersionUID = 0L;
    public static final BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
    public static final BigRational ONE = new BigRational(BigInteger.ONE, BigInteger.ONE);

    /**
     * Ensures the fraction the denominator is positive and optionally divides the numerator and
     * denominator by the greatest common factor.
     * <p>
     * @param numerator   a numerator
     * @param denominator a denominator
     * @param checkGcd    true if the numerator and denominator should be divided by the greatest
     *                    common factor
     * @return the canonical representation of the rational fraction
     */
    private static BigRational canonical(BigInteger numerator, BigInteger denominator,
        boolean checkGcd)
    {
        assert (numerator != null);
        assert (denominator != null);
        if (denominator.signum() == 0)
            throw new IllegalArgumentException("denominator is zero");
        if (numerator.signum() == 0)
            return ZERO;
        BigInteger newNumerator = numerator;
        BigInteger newDenominator = denominator;
        if (newDenominator.signum() < 0)
        {
            newNumerator = newNumerator.negate();
            newDenominator = newDenominator.negate();
        }
        if (checkGcd)
        {
            BigInteger gcd = newNumerator.gcd(newDenominator);
            if (!gcd.equals(BigInteger.ONE))
            {
                newNumerator = newNumerator.divide(gcd);
                newDenominator = newDenominator.divide(gcd);
            }
        }
        return new BigRational(newNumerator, newDenominator);
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     * @throws NullPointerException if numerator or denominator are null
     */
    public static BigRational valueOf(BigInteger numerator, BigInteger denominator)
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull();
        Preconditions.requireThat(denominator, "denominator").isNotNull();
        return canonical(numerator, denominator, true);
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     */
    public static BigRational valueOf(long numerator, long denominator)
    {
        BigInteger bigNumerator = BigInteger.valueOf(numerator);
        BigInteger bigDenominator = BigInteger.valueOf(denominator);
        return canonical(bigNumerator, bigDenominator, true);
    }

    /**
     * @param value the parameter value
     * @param name  the parameter name
     * @return the BigInteger representation of the parameter
     * @throws NumberFormatException if value is not a valid representation of BigInteger
     */
    private static BigInteger requireBigInteger(String value, String name)
        throws NumberFormatException
    {
        try
        {
            return new BigInteger(value);
        }
        catch (NumberFormatException e)
        {
            throw (NumberFormatException) new NumberFormatException("Invalid " + name + ": " + value).
                initCause(e);
        }
    }

    /**
     * @param numerator   a numerator
     * @param denominator a denominator
     * @return a BigRational having value {@code numerator / denominator}
     * @throws NullPointerException     if numerator or denominator are null
     * @throws IllegalArgumentException if numerator or denominator are empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    public static BigRational valueOf(String numerator, String denominator)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull().isNotEmpty();
        Preconditions.requireThat(denominator, "denominator").isNotNull().isNotEmpty();
        BigInteger bigNumerator = requireBigInteger(numerator, "numerator");
        BigInteger bigDenominator = requireBigInteger(denominator, "denominator");
        return canonical(bigNumerator, bigDenominator, true);
    }

    /**
     * @param value a string representation of a rational fraction (e.g. "12.34e5" or "3/4")
     * @return a BigRational representation of the String
     * @throws NullPointerException     if value is null
     * @throws IllegalArgumentException if value is empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    public static BigRational valueOf(String value)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
        List<String> fractionParts = Splitter.on('/').splitToList(value);
        if (fractionParts.size() == 1)
            return valueOfRational(value);
        if (fractionParts.size() == 2)
            return BigRational.valueOf(fractionParts.get(0), fractionParts.get(1));
        throw new IllegalArgumentException("Too many slashes: " + value);
    }

    /**
     * @param value a string representation of a rational fraction (e.g. "12.34e5")
     * @return a BigRational representation of the String
     * @throws NullPointerException     if value is null
     * @throws IllegalArgumentException if value is empty
     * @throws NumberFormatException    if numerator or denominator are not a valid representation of
     *                                  BigDecimal
     */
    private static BigRational valueOfRational(String value)
        throws NullPointerException, IllegalArgumentException, NumberFormatException
    {
        Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
        BigDecimal bigDecimal = new BigDecimal(value);
        int scale = bigDecimal.scale();
        BigInteger numerator = bigDecimal.unscaledValue();
        BigInteger denominator;
        if (scale > 0)
            denominator = BigInteger.TEN.pow(scale);
        else
        {
            numerator = numerator.multiply(BigInteger.TEN.pow(-scale));
            denominator = BigInteger.ONE;
        }

        return canonical(numerator, denominator, true);
    }

    private final BigInteger numerator;
    private final BigInteger denominator;

    /**
     * @param numerator   the numerator
     * @param denominator the denominator
     * @throws NullPointerException if numerator or denominator are null
     */
    private BigRational(BigInteger numerator, BigInteger denominator)
    {
        Preconditions.requireThat(numerator, "numerator").isNotNull();
        Preconditions.requireThat(denominator, "denominator").isNotNull();
        this.numerator = numerator;
        this.denominator = denominator;
    }

    /**
     * @return the numerator
     */
    public BigInteger getNumerator()
    {
        return numerator;
    }

    /**
     * @return the denominator
     */
    public BigInteger getDenominator()
    {
        return denominator;
    }

    @Override
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public int compareTo(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();

        // canonical() ensures denominator is positive
        if (numerator.signum() != other.numerator.signum())
            return numerator.signum() - other.numerator.signum();

        // Set the denominator to a common multiple before comparing the numerators
        BigInteger first = numerator.multiply(other.denominator);
        BigInteger second = other.numerator.multiply(denominator);
        return first.compareTo(second);
    }

    /**
     * @param other another rational fraction
     * @return the result of adding this object to {@code other}
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational add(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();
        if (other.numerator.signum() == 0)
            return this;
        if (numerator.signum() == 0)
            return other;
        if (denominator.equals(other.denominator))
            return new BigRational(numerator.add(other.numerator), denominator);
        return canonical(numerator.multiply(other.denominator).
            add(other.numerator.multiply(denominator)),
            denominator.multiply(other.denominator), true);
    }

    /**
     * @param other another rational fraction
     * @return the result of subtracting {@code other} from this object
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational subtract(BigRational other)
    {
        return add(other.negate());
    }

    /**
     * @param other another rational fraction
     * @return the result of multiplying this object by {@code other}
     * @throws NullPointerException if other is null
     */
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public BigRational multiply(BigRational other)
    {
        Preconditions.requireThat(other, "other").isNotNull();
        if (numerator.signum() == 0 || other.numerator.signum() == 0)
            return ZERO;
        if (numerator.equals(other.denominator))
            return canonical(other.numerator, denominator, true);
        if (other.numerator.equals(denominator))
            return canonical(numerator, other.denominator, true);
        if (numerator.negate().equals(other.denominator))
            return canonical(other.numerator.negate(), denominator, true);
        if (other.numerator.negate().equals(denominator))
            return canonical(numerator.negate(), other.denominator, true);
        return canonical(numerator.multiply(other.numerator), denominator.multiply(other.denominator),
            true);
    }

    /**
     * @param other another rational fraction
     * @return the result of dividing this object by {@code other}
     * @throws NullPointerException if other is null
     */
    public BigRational divide(BigRational other)
    {
        return multiply(other.invert());
    }

    /**
     * @return true if the object is a whole number
     */
    public boolean isInteger()
    {
        return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
    }

    /**
     * Returns a BigRational whose value is (-this).
     * <p>
     * @return -this
     */
    public BigRational negate()
    {
        return new BigRational(numerator.negate(), denominator);
    }

    /**
     * @return a rational fraction with the numerator and denominator swapped
     */
    public BigRational invert()
    {
        return canonical(denominator, numerator, false);
    }

    /**
     * @return the absolute value of this {@code BigRational}
     */
    public BigRational abs()
    {
        if (numerator.signum() < 0)
            return negate();
        return this;
    }

    /**
     * @param exponent exponent to which both numerator and denominator is to be raised.
     * @return a BigRational whose value is (this<sup>exponent</sup>).
     */
    public BigRational pow(int exponent)
    {
        return canonical(numerator.pow(exponent), denominator.pow(exponent), true);
    }

    /**
     * @param other another rational fraction
     * @return the minimum of this object and the other fraction
     */
    public BigRational min(BigRational other)
    {
        if (compareTo(other) <= 0)
            return this;
        return other;
    }

    /**
     * @param other another rational fraction
     * @return the maximum of this object and the other fraction
     */
    public BigRational max(BigRational other)
    {
        if (compareTo(other) >= 0)
            return this;
        return other;
    }

    /**
     * @param scale        scale of the BigDecimal quotient to be returned
     * @param roundingMode the rounding mode to apply
     * @return a BigDecimal representation of this object
     * @throws NullPointerException if roundingMode is null
     */
    public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode)
    {
        Preconditions.requireThat(roundingMode, "roundingMode").isNotNull();
        if (isInteger())
            return new BigDecimal(numerator);
        return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
    }

    @Override
    public int intValue()
    {
        return (int) longValue();
    }

    @Override
    public long longValue()
    {
        if (isInteger())
            return numerator.longValue();
        return numerator.divide(denominator).longValue();
    }

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

    @Override
    public double doubleValue()
    {
        if (isInteger())
            return numerator.doubleValue();
        return numerator.doubleValue() / denominator.doubleValue();
    }

    @Override
    @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (!(o instanceof BigRational))
            return false;
        BigRational other = (BigRational) o;

        return numerator.equals(other.denominator) && Objects.equals(denominator, other.denominator);
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(numerator, denominator);
    }

    /**
     * Returns the String representation: {@code numerator / denominator}.
     */
    @Override
    public String toString()
    {
        if (isInteger())
            return String.format("%,d", numerator);
        return String.format("%,d / %,d", numerator, denominator);
    }
}
御守 2024-07-19 13:19:24

最初的评论:

永远不要这样写:

if ( condition ) statement;

这样要好得多,

if ( condition ) { statement };

只是为了养成一个好习惯。

通过按照建议使类不可变,您还可以利用 double 来执行 equals 和 hashCode 以及compareTo 操作

这是我的快速肮脏版本:

public final class Fraction implements Comparable {

    private final int numerator;
    private final int denominator;
    private final Double internal;

    public static Fraction createFraction( int numerator, int denominator ) { 
        return new Fraction( numerator, denominator );
    }

    private Fraction(int numerator, int denominator) {
        this.numerator   = numerator;
        this.denominator = denominator;
        this.internal = ((double) numerator)/((double) denominator);
    }


    public int getNumerator() {
        return this.numerator;
    }

    public int getDenominator() {
        return this.denominator;
    }


    private double doubleValue() {
        return internal;
    }

    public int compareTo( Object o ) {
        if ( o instanceof Fraction ) { 
            return internal.compareTo( ((Fraction)o).internal );
        }
        return 1;
    }

    public boolean equals( Object o ) {
          if ( o instanceof Fraction ) {  
             return this.internal.equals( ((Fraction)o).internal );
          } 
          return false;
    }

    public int hashCode() { 
        return internal.hashCode();
    }



    public String toString() { 
        return String.format("%d/%d", numerator, denominator );
    }

    public static void main( String [] args ) { 
        System.out.println( Fraction.createFraction( 1 , 2 ) ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).hashCode() ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).compareTo( Fraction.createFraction(2,4) ) ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).equals( Fraction.createFraction(4,8) ) ) ;
        System.out.println( Fraction.createFraction( 3 , 9 ).equals( Fraction.createFraction(1,3) ) ) ;
    }       

}

关于静态工厂方法,如果您对要处理的 Fraction 进行子类化,以后它可能会很有用更复杂的事情,或者如果您决定对最常用的对象使用池。

事实可能并非如此,我只是想指出这一点。 :)

请参阅 Effective Java 第一项。

Initial remark:

Never write this:

if ( condition ) statement;

This is much better

if ( condition ) { statement };

Just create to create a good habit.

By making the class immutable as suggested, you can also take advantage of the double to perform the equals and hashCode and compareTo operations

Here's my quick dirty version:

public final class Fraction implements Comparable {

    private final int numerator;
    private final int denominator;
    private final Double internal;

    public static Fraction createFraction( int numerator, int denominator ) { 
        return new Fraction( numerator, denominator );
    }

    private Fraction(int numerator, int denominator) {
        this.numerator   = numerator;
        this.denominator = denominator;
        this.internal = ((double) numerator)/((double) denominator);
    }


    public int getNumerator() {
        return this.numerator;
    }

    public int getDenominator() {
        return this.denominator;
    }


    private double doubleValue() {
        return internal;
    }

    public int compareTo( Object o ) {
        if ( o instanceof Fraction ) { 
            return internal.compareTo( ((Fraction)o).internal );
        }
        return 1;
    }

    public boolean equals( Object o ) {
          if ( o instanceof Fraction ) {  
             return this.internal.equals( ((Fraction)o).internal );
          } 
          return false;
    }

    public int hashCode() { 
        return internal.hashCode();
    }



    public String toString() { 
        return String.format("%d/%d", numerator, denominator );
    }

    public static void main( String [] args ) { 
        System.out.println( Fraction.createFraction( 1 , 2 ) ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).hashCode() ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).compareTo( Fraction.createFraction(2,4) ) ) ;
        System.out.println( Fraction.createFraction( 1 , 2 ).equals( Fraction.createFraction(4,8) ) ) ;
        System.out.println( Fraction.createFraction( 3 , 9 ).equals( Fraction.createFraction(1,3) ) ) ;
    }       

}

About the static factory method, it may be useful later, if you subclass the Fraction to handle more complex things, or if you decide to use a pool for the most frequently used objects.

It may not be the case, I just wanted to point it out. :)

See Effective Java first item.

澉约 2024-07-19 13:19:24

添加一些简单的东西可能会有用,比如往复、取余数和得到整体。

Might be useful to add simple things like reciprocate, get remainder and get whole.

中性美 2024-07-19 13:19:24

即使您有compareTo()方法,如果您想使用像Collections.sort()这样的实用程序,那么您还应该实现Comparable。

public class Fraction extends Number implements Comparable<Fraction> {
 ...
}

另外,为了漂亮的显示,我建议重写 toString()

public String toString() {
    return this.getNumerator() + "/" + this.getDenominator();
}

最后,我将类公开,以便您可以从不同的包中使用它。

Even though you have the methods compareTo(), if you want to make use of utilities like Collections.sort(), then you should also implement Comparable.

public class Fraction extends Number implements Comparable<Fraction> {
 ...
}

Also, for pretty display I recommend overriding toString()

public String toString() {
    return this.getNumerator() + "/" + this.getDenominator();
}

And finally, I'd make the class public so that you can use it from different packages.

对风讲故事 2024-07-19 13:19:24

这个函数使用欧几里得算法进行简化在定义分数时非常有用

 public Fraction simplify(){


     int safe;
     int h= Math.max(numerator, denominator);
     int h2 = Math.min(denominator, numerator);

     if (h == 0){

         return new Fraction(1,1);
     }

     while (h>h2 && h2>0){

          h = h - h2;
          if (h>h2){

              safe = h;
              h = h2;
              h2 = safe;

          }  

     }

  return new Fraction(numerator/h,denominator/h);

 }

This function simplify using the eucledian algorithm is quite useful when defining fractions

 public Fraction simplify(){


     int safe;
     int h= Math.max(numerator, denominator);
     int h2 = Math.min(denominator, numerator);

     if (h == 0){

         return new Fraction(1,1);
     }

     while (h>h2 && h2>0){

          h = h - h2;
          if (h>h2){

              safe = h;
              h = h2;
              h2 = safe;

          }  

     }

  return new Fraction(numerator/h,denominator/h);

 }
萌化 2024-07-19 13:19:24

对于工业级分数/有理数实现,我将实现它,以便它可以表示 NaN、正无穷大、负无穷大和可选的负零,其操作语义与浮点算术的 IEEE 754 标准状态完全相同(它还简化了与浮点值的转换)。 另外,由于与零、一和上面的特殊值进行比较只需要简单地将分子和分母与 0 和 1 进行组合比较 - 我将添加几个 isXXX 和 CompareToXXX 方法以方便使用(例如 eq0() 会在幕后使用分子 == 0 && 分母 != 0 而不是让客户端与零值实例进行比较)。 一些静态预定义值(ZERO、ONE、TWO、TEN、ONE_TENTH、NAN 等)也很有用,因为它们作为常量值出现在多个位置。 恕我直言,这是最好的方法。

For industry-grade Fraction/Rational implementation, I would implement it so it can represent NaN, positive infinity, negative infinity, and optionally negative zero with operational semantics exactly the same as the IEEE 754 standard states for floating point arithmetics (it also eases the conversion to/from floating point values). Plus, since comparison to zero, one, and the special values above only needs simple, but combined comparison of the numerator and denominator against 0 and 1 - i would add several isXXX and compareToXXX methods for ease of use (eg. eq0() would use numerator == 0 && denominator != 0 behind the scenes instead of letting the client to compare against a zero valued instance). Some statically predefined values (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, etc.) are also useful, since they appear at several places as constant values. This is the best way IMHO.

对风讲故事 2024-07-19 13:19:24

类分数:

     public class Fraction {
        private int num;            // numerator 
        private int denom;          // denominator 
        // default constructor
        public Fraction() {}
        // constructor
        public Fraction( int a, int b ) {
            num = a;
            if ( b == 0 )
                throw new ZeroDenomException();
            else
                denom = b;
        }
        // return string representation of ComplexNumber
        @Override
        public String toString() {
            return "( " + num + " / " + denom + " )";
        }
        // the addition operation
        public Fraction add(Fraction x){
            return new Fraction(
                    x.num * denom + x.denom * num, x.denom * denom );
        }
        // the multiplication operation
        public Fraction multiply(Fraction x) {
            return new Fraction(x.num * num, x.denom * denom);
        } 
}

主程序:

    static void main(String[] args){
    Scanner input = new Scanner(System.in);
    System.out.println("Enter numerator and denominator of first fraction");
    int num1 =input.nextInt();
    int denom1 =input.nextInt();
    Fraction x = new Fraction(num1, denom1);
    System.out.println("Enter numerator and denominator of second fraction");
    int num2 =input.nextInt();
    int denom2 =input.nextInt();
    Fraction y = new Fraction(num2, denom2);
    Fraction result = new Fraction();
    System.out.println("Enter required operation: A (Add), M (Multiply)");
    char op = input.next().charAt(0);
    if(op == 'A') {
        result = x.add(y);
        System.out.println(x + " + " + y + " = " + result);
    }

Class Fraction:

     public class Fraction {
        private int num;            // numerator 
        private int denom;          // denominator 
        // default constructor
        public Fraction() {}
        // constructor
        public Fraction( int a, int b ) {
            num = a;
            if ( b == 0 )
                throw new ZeroDenomException();
            else
                denom = b;
        }
        // return string representation of ComplexNumber
        @Override
        public String toString() {
            return "( " + num + " / " + denom + " )";
        }
        // the addition operation
        public Fraction add(Fraction x){
            return new Fraction(
                    x.num * denom + x.denom * num, x.denom * denom );
        }
        // the multiplication operation
        public Fraction multiply(Fraction x) {
            return new Fraction(x.num * num, x.denom * denom);
        } 
}

The main program:

    static void main(String[] args){
    Scanner input = new Scanner(System.in);
    System.out.println("Enter numerator and denominator of first fraction");
    int num1 =input.nextInt();
    int denom1 =input.nextInt();
    Fraction x = new Fraction(num1, denom1);
    System.out.println("Enter numerator and denominator of second fraction");
    int num2 =input.nextInt();
    int denom2 =input.nextInt();
    Fraction y = new Fraction(num2, denom2);
    Fraction result = new Fraction();
    System.out.println("Enter required operation: A (Add), M (Multiply)");
    char op = input.next().charAt(0);
    if(op == 'A') {
        result = x.add(y);
        System.out.println(x + " + " + y + " = " + result);
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文