Cách tốt nhất để biểu diễn một phân số trong Java?


100

Tôi đang cố gắng làm việc với phân số trong Java.

Tôi muốn triển khai các chức năng số học. Đối với điều này, trước tiên tôi sẽ yêu cầu một cách để chuẩn hóa các chức năng. Tôi biết tôi không thể thêm 1/6 và 1/2 cho đến khi tôi có một mẫu số chung. Tôi sẽ phải thêm 1/6 và 3/6. Một cách tiếp cận ngây thơ là tôi sẽ thêm 2/12 và 6/12 rồi giảm. Làm thế nào tôi có thể đạt được một mẫu số chung với mức phạt hiệu suất ít nhất? Thuật toán nào là tốt nhất cho việc này?


Phiên bản 8 (nhờ hstoerr ):

Các cải tiến bao gồm:

  • phương thức equals () hiện phù hợp với phương thức 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;
    }
}

Tôi đã xóa tất cả các phiên bản trước đó. Tôi cảm ơn:


33
Bỏ mã, sử dụng Apache Commons :) commons.apache.org/math/userguide/fraction.html
Patrick

3
Nhận xét của Patrick sẽ xứng đáng được +1, nếu nó được đăng dưới dạng câu trả lời. Trong hầu hết các trường hợp, đó là câu trả lời đúng; "biết và sử dụng các thư viện", như Java hiệu quả nói. Câu hỏi ban đầu cũng rõ ràng và hữu ích.
Jonik

Nhận thấy rằng bạn đã chấp nhận câu trả lời của tôi .. nếu bạn thực sự đang sử dụng mã đó và tìm thấy bất kỳ vấn đề nào với nó hoặc bất kỳ điều gì thiếu sót, vui lòng cho tôi biết! gửi email cho tôi từ trang web của tôi: vacant-nebula.com/contact/kip
Kip

Tôi khuyên bạn nên chỉnh sửa phương thức "CompareTo" và truyền "this.getNumerator ()" thành rất lâu trước khi thực hiện phép nhân. Nếu không, mã vẫn dễ bị tràn. Ngoài ra, tôi nghĩ sẽ rất hay khi triển khai <Phân số> có thể so sánh được, vì bạn đã triển khai phương thức CompareTo.
Hosam Aly

Và vì bạn đã đi rất xa, nên việc triển khai bằng và mã băm cũng có thể hữu ích.
Hosam Aly

Câu trả lời:


65

Nó chỉ xảy ra như vậy là tôi đã viết một lớp BigFraction cách đây không lâu, cho các vấn đề của Project Euler . Nó giữ tử số và mẫu số BigInteger, vì vậy nó sẽ không bao giờ bị tràn. Nhưng sẽ hơi chậm đối với rất nhiều thao tác mà bạn biết rằng sẽ không bao giờ tràn .. dù sao, hãy sử dụng nó nếu bạn muốn. Tôi đã chết để thể hiện điều này bằng cách nào đó. :)

Chỉnh sửa : Phiên bản mới nhất và tuyệt vời nhất của mã này, bao gồm các bài kiểm tra đơn vị hiện được lưu trữ trên GitHub và cũng có sẵn thông qua Maven Central . Tôi đang để lại mã gốc của mình ở đây để câu trả lời này không chỉ là một liên kết ...


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;
    }
  }

}

Nếu một đối số là null, hãy ném một NullPointerException. Trong thực tế, mã sẽ làm điều đó anyway vì vậy kiểm tra của bạn (và thay thế với IllegalArgumentException (là mã không cần thiết sưng lên.
cletus

24
Tôi không đồng ý; nếu một người dùng khác đang sử dụng lớp này mà không xem mã nguồn của tôi và nhận được NullPointerException, anh ta sẽ nghĩ rằng có lỗi trong mã của tôi . Nhưng một IllegalArgumentException cho thấy rằng anh ta đã phá vỡ hợp đồng mà javadoc ngụ ý (mặc dù tôi đã không trình bày rõ ràng).
Kip


1
chỉ là một câu hỏi, có gì sai với Phân số và Phân số lớn trong Toán học Commons?
Mortimer

@Mortimer: không chắc, tôi chưa bao giờ nhìn nó
Kip

61
  • Làm cho nó bất biến ;
  • Làm cho nó chính tắc , nghĩa là 6/4 trở thành 3/2 ( thuật toán ước số chung lớn nhất rất hữu ích cho việc này);
  • Gọi nó là Rational, vì những gì bạn đang đại diện là một số hữu tỉ ;
  • Bạn có thể sử dụng BigIntegerđể lưu trữ các giá trị tùy ý chính xác. Nếu không phải là sau đó long, có một thực hiện dễ dàng hơn;
  • Làm cho mẫu số luôn dương. Dấu hiệu nên được mang theo tử số;
  • Mở rộng Number;
  • Thực hiện Comparable<T>;
  • Thực hiện equals()hashCode();
  • Thêm phương thức gốc cho một số được biểu thị bằng a String;
  • Thêm một số phương pháp nhà máy tiện lợi;
  • Thêm một toString(); và
  • Làm cho nó Serializable.

Trên thực tế, hãy thử cái này để biết kích thước. Nó chạy nhưng có thể có một số vấn đề:

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();
    }
}

Đầu ra là:

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

30

Tôi đang cố gắng làm việc với các phân số thích hợp trong Java.

Apache Commons Math đã có lớp Phân số khá lâu. Hầu hết câu trả lời cho, "Cậu bé, tôi ước Java có một cái gì đó giống như X trong thư viện lõi!" có thể được tìm thấy dưới sự bảo trợ của thư viện Apache Commons .


2
Tôi sẽ cho bạn biết tại sao con số này quá thấp, thư viện Apache Commons không thân thiện với người mới. Đầu tiên là không có liên kết trực tiếp để tải xuống trên trang đó (nó bị ẩn trong menu thanh bên), thứ hai là không có hướng dẫn về cách sử dụng nó (thêm một jar vào đường dẫn xây dựng của bạn), thứ ba là tôi gặp lỗi classDefNotFound sau khi thêm tất cả vào. . Vì vậy, bạn không nhận được sự ủng hộ nào từ chúng tôi, những người chỉ biết sao chép và dán.
Noumenon

@Noumenon làm thế nào để sử dụng bất kỳ trình quản lý bản dựng nào (ví dụ: maven) và chỉ thêm phụ thuộc vào POM?
eugene.polschikov

1
Tôi muốn xem một đoạn giới thiệu nhỏ về "Cách sử dụng cái này trong dự án của bạn" cho các noobs. Đề xuất đó có thể đi vào đó. Điều đó nói rằng, tôi đã tìm ra cách thực hiện và sử dụng nó trong ứng dụng xuất xưởng của tôi, yêu cầu hiển thị các phần của inch và tôi không bao giờ quay lại để đưa cho bạn ủng hộ của bạn. Vì vậy, cảm ơn, ở đây nó là muộn.
Noumenon,

Đó là phản hồi công bằng. Đây cũng là lời cảm ơn muộn màng của tôi! :)
yawmark

Cái này khá dễ sử dụng.
Eric Wang

24

Hãy biến nó thành một kiểu bất biến! Giá trị của một phân số không thay đổi - ví dụ: một nửa không trở thành một phần ba. Thay vì setDenominator, bạn có thể có withDenominator trả về một phân số mới có cùng tử số nhưng mẫu số được chỉ định.

Cuộc sống dễ dàng hơn nhiều với các loại bất biến.

Ghi đè bằng và mã băm cũng sẽ hợp lý, vì vậy nó có thể được sử dụng trong bản đồ và bộ. Các quan điểm của Outlaw Programmer về toán tử số học và định dạng chuỗi cũng rất tốt.

Như một hướng dẫn chung, hãy xem BigInteger và BigDecimal. Họ không làm những điều giống nhau, nhưng họ đủ giống nhau để cung cấp cho bạn những ý tưởng hay.


5
"Hãy đặt nó thành một kiểu bất biến! Giá trị của một phân số không thay đổi - ví dụ như một nửa không trở thành một phần ba." Danh sách / tuple / vector (1, 2, 3, 4) cũng không trở thành giá trị (4, 3, 2, 1), nhưng nó dường như không làm phiền hầu hết mọi người rằng danh sách thay đổi trạng thái. Không phải tôi không đồng ý với tính bất biến đối với phân số, nhưng nó xứng đáng là một lập luận tốt hơn. Nó giống như một giá trị hơn một gói trạng thái. Kỳ vọng của lập trình viên có phải là lý do chính đáng để được hướng dẫn? Tôi không chắc 100%, nhưng nghe có vẻ là một ý kiến ​​hay.
Jonas Kölker

2
Chà, trong cuộc sống thực, danh sách thay đổi: bạn viết danh sách mua sắm như thế nào? Bạn bắt đầu với một tờ giấy trắng, và viết lên đó. Qua nửa chặng đường, bạn vẫn gọi nó là "danh sách mua sắm". Phải nói rằng, lập trình chức năng cố gắng làm cho các danh sách thậm chí không thể thay đổi ...
Jon Skeet

7

Vâng, đối với một, tôi muốn loại bỏ các setters và làm cho Fraction trở thành bất biến.

Có thể bạn cũng sẽ muốn các phương thức cộng, trừ, v.v., và có thể một số cách để có được biểu diễn ở các định dạng Chuỗi khác nhau.

CHỈNH SỬA: Tôi có thể đánh dấu các trường là 'cuối cùng' để báo hiệu ý định của tôi nhưng tôi đoán đó không phải là vấn đề lớn ...


2
Tôi tự hỏi có bao nhiêu "làm cho nó bất biến" câu trả lời chúng tôi sẽ kết thúc với :)
Jon Skeet

5
  • Thật là vô nghĩa nếu không có các phương thức số học như add () và kernel (), v.v.
  • Bạn chắc chắn nên ghi đè bằng () và hashCode ().
  • Bạn nên thêm một phương thức để chuẩn hóa phân số hoặc thực hiện tự động. Hãy suy nghĩ xem bạn có muốn 1/2 và 2/4 được coi là giống nhau hay không - điều này có ý nghĩa đối với các phương thức equals (), hashCode () và CompareTo ().

5

Tôi sẽ cần sắp xếp chúng từ nhỏ nhất đến lớn nhất, vì vậy cuối cùng tôi sẽ cần phải biểu thị chúng dưới dạng một đôi

Không hoàn toàn cần thiết. (Trong thực tế, nếu bạn muốn xử lý đẳng thức một cách chính xác, đừng dựa vào double để hoạt động đúng.) Nếu b * d là dương, a / b <c / d nếu ad <bc. Nếu có liên quan đến số nguyên âm, điều đó có thể được xử lý một cách thích hợp ...

Tôi có thể viết lại thành:

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

Việc sử dụng longở đây là để đảm bảo không bị tràn nếu bạn nhân haiint s . xử lý Nếu bạn có thể đảm bảo rằng mẫu số luôn không âm (nếu là âm, chỉ cần phủ định cả tử số và mẫu số), thì bạn có thể thoát khỏi việc phải kiểm tra xem b * d có phải là số dương hay không và tiết kiệm một vài bước. Tôi không chắc bạn đang tìm kiếm hành vi nào với mẫu số 0.

Không chắc chắn hiệu suất như thế nào so với sử dụng đồ đôi để so sánh. (có nghĩa là, nếu bạn quan tâm đến hiệu suất nhiều như vậy) Đây là một phương pháp thử nghiệm tôi đã sử dụng để kiểm tra. (Có vẻ hoạt động bình thường.)

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 bạn có thể xem xét việc tái cấu trúc để thực hiện Comparablehoặc Comparatorcho lớp của bạn.)


Điều này không đúng nếu, ví dụ, a = 1, b = 3, c = -2, d = -3. Nếu b và d dương thì a / b <c / d đúng khi và chỉ khi ad <bc.
Luke Woodward

Argh, tôi nhận sai bằng cấp. (! thanks) Các điều kiện nên nếu bd> 0.
Jason S

Thật. Chính xác hơn, a / b <c / d <=> ac <bd là đúng với điều kiện bd> 0. Nếu bd <0, ngược lại là đúng. (Nếu bd = 0, sau đó bạn có một phần nhỏ bum :-).)
Paul Brinkley

Đóng. ý bạn là a / b <c / d <=> ad <bc for bd> 0. (Tôi đã nhận nó ngay lần đầu tiên trong ý kiến mã của tôi!)
Jason S

4

Một cải tiến rất nhỏ có khả năng là lưu giá trị gấp đôi mà bạn đang tính toán để bạn chỉ tính giá trị đó trong lần truy cập đầu tiên. Đây sẽ không phải là một chiến thắng lớn trừ khi bạn đang truy cập vào con số này rất nhiều, nhưng nó cũng không quá khó để thực hiện.

Một điểm bổ sung có thể là lỗi kiểm tra bạn thực hiện trong mẫu số ... bạn tự động thay đổi 0 thành 1. Không chắc liệu điều này có chính xác cho ứng dụng cụ thể của bạn hay không, nhưng nói chung nếu ai đó đang cố gắng chia cho 0, điều gì đó rất sai . Tôi sẽ để điều này tạo ra một ngoại lệ (một ngoại lệ chuyên biệt nếu bạn cảm thấy nó cần thiết) hơn là thay đổi giá trị theo cách có vẻ tùy ý mà người dùng không biết.

Hạn chế với một số nhận xét khác, về việc thêm các phương thức để cộng trừ, v.v ... vì bạn không đề cập đến việc cần chúng, tôi giả sử bạn không cần. Và trừ khi bạn đang xây dựng một thư viện thực sự sẽ được sử dụng ở nhiều nơi hoặc bởi những người khác, hãy sử dụng YAGNI (bạn sẽ không cần nó, vì vậy nó không nên ở đó.)


Việc anh ấy có getNumerator () và getDenominator () khiến tôi tin rằng anh ấy đang tạo ra các phân số mới BÊN NGOÀI của lớp này. Logic đó có lẽ thuộc về đây nếu nó tồn tại.
Lập trình viên Outlaw

+1 Âm thầm thay đổi 0 thành 1 trong mẫu số là công thức dẫn đến thảm họa.
maaartinus

4

Có một số cách để cải thiện điều này hoặc bất kỳ loại giá trị nào:

  • Làm cho lớp của bạn không thay đổi , bao gồm cả việc tạo tử số và mẫu số cuối cùng
  • Tự động chuyển đổi phân số sang dạng chuẩn , ví dụ: 2/4 -> 1/2
  • Triển khai toString ()
  • Triển khai "public static Fraction valueOf (String s)" để chuyển đổi từ chuỗi thành phân số. Thực hiện các phương thức nhà máy tương tự để chuyển đổi từ int, double, v.v.
  • Thực hiện phép cộng, phép nhân, v.v.
  • Thêm hàm tạo từ các số nguyên
  • Ghi đè bằng / mã băm
  • Cân nhắc tạo Fraction một giao diện có triển khai chuyển sang BigInteger nếu cần
  • Xem xét Số phân loại phụ
  • Cân nhắc bao gồm các hằng số được đặt tên cho các giá trị phổ biến như 0 và 1
  • Cân nhắc làm cho nó có thể được tuần tự hóa
  • Kiểm tra phép chia cho 0
  • Ghi lại API của bạn

Về cơ bản, hãy xem API cho các lớp giá trị khác như Double , Integer và làm những gì chúng làm :)


3

Nếu bạn nhân tử số và mẫu số của một phân số với mẫu số của phân số kia và ngược lại, bạn sẽ có hai phân số (vẫn có cùng giá trị) có cùng mẫu số và bạn có thể so sánh trực tiếp các tử số. Do đó, bạn sẽ không cần phải tính toán giá trị kép:

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;
}

Điều này không thành công nếu frac.getDenominator () và this.denominator có dấu hiệu trái ngược nhau. (xem bài đăng của tôi.) Ngoài ra, bạn phải chú ý đến thực tế là nhân có thể tràn.
Jason S

À vâng, đó là sự thật. Nhưng trong trường hợp đó, tôi thích cách triển khai của Kip hơn, điều mà ít nhất tôi có thể hiểu được. ;)
Francisco Canedo

Tôi chỉ ra rằng trong cách triển khai của mình, chỉ tử số có thể là số âm. Tôi cũng sử dụng BigIntegers nên sẽ không bao giờ có hiện tượng tràn (tất nhiên là với một số hiệu suất).
Kip

2

tôi sẽ cải thiện mã đó như thế nào:

  1. một phương thức khởi tạo dựa trên Phân số chuỗi (Chuỗi s) // mong đợi "số / số"
  2. một phương thức tạo bản sao Phân số (Bản sao phân số)
  3. ghi đè phương thức nhân bản
  4. triển khai các phương thức bằng, toString và mã băm
  5. triển khai giao diện java.io.Serializable, có thể so sánh được
  6. một phương thức "double getDoubleValue ()"
  7. một phương thức add / split / etc ...
  8. Tôi sẽ làm cho lớp đó là bất biến (không có bộ định vị)

Một danh sách khá đẹp. Có lẽ không cần clone / serializable nhưng mọi thứ khác đều hợp lý.
Lập trình viên ngoài vòng pháp luật,

@OutlawProgrammer: Đúng, 8 hoặc 3. Bất biến bất biến trong bản sao là một điều phi nghĩa.
maaartinus

2

Bạn đã có một chức năng CompareTo ... Tôi sẽ triển khai giao diện So sánh.

Có thể không thực sự quan trọng đối với bất cứ điều gì bạn sẽ làm với nó.



2

Cụ thể : Có cách nào tốt hơn để xử lý việc được chuyển sang mẫu số 0 không? Đặt mẫu số thành 1 là cảm giác rất tùy ý. Làm thế nào tôi có thể làm điều này đúng?

Tôi sẽ nói rằng ném ArithmeticException để chia cho 0, vì đó thực sự là những gì đang xảy ra:

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

Thay vì "Chia cho 0.", bạn có thể muốn thông báo có nội dung "Chia cho 0: Mẫu số cho Phân số bằng 0".


1

Khi bạn đã tạo một đối tượng phân số, tại sao bạn lại muốn cho phép các đối tượng khác đặt tử số hoặc mẫu số? Tôi nghĩ rằng những thứ này chỉ nên đọc. Nó làm cho đối tượng trở nên bất biến ...

Ngoài ra ... việc đặt mẫu số thành 0 sẽ tạo ra một ngoại lệ đối số không hợp lệ (tôi không biết nó là gì trong Java)


Hoặc ném ArithmeticException mới ("Chia cho số không.")
Kip

1

Timothy Budd đã triển khai tốt lớp Rational trong "Cấu trúc dữ liệu trong C ++" của mình. Tất nhiên, ngôn ngữ khác nhau, nhưng nó chuyển sang Java rất độc đáo.

Tôi muốn giới thiệu nhiều nhà xây dựng hơn. Một hàm tạo mặc định sẽ có tử số 0, mẫu số 1. Một hàm tạo đối số duy nhất sẽ giả sử mẫu số là 1. Hãy nghĩ cách người dùng của bạn có thể sử dụng lớp này.

Không kiểm tra mẫu số 0? Lập trình theo hợp đồng sẽ có bạn thêm nó.


1

Tôi sẽ là thứ ba hoặc thứ năm hoặc bất cứ điều gì đề xuất để biến phần của bạn trở nên bất biến. Tôi cũng khuyên bạn nên mở rộng lớp Số . Tôi có thể sẽ xem xét lớp Double , vì bạn có thể muốn triển khai nhiều phương thức giống nhau.

Bạn nên có lẽ cũng thực hiện tương đươngSerializable kể từ khi hành vi này có thể sẽ được dự kiến. Do đó, bạn sẽ cần triển khai CompareTo (). Bạn cũng sẽ cần ghi đè bằng () và tôi không thể nhấn mạnh đủ để bạn cũng ghi đè mã băm (). Đây có thể là một trong số ít các trường hợp mà bạn không muốn CompareTo () và equals () nhất quán vì các phân số có thể rút gọn cho nhau không nhất thiết phải bằng nhau.


1

Một thực hành dọn dẹp mà tôi thích là chỉ có một lần trở lại.

 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;
    }


1

Tôi đã dọn dẹp câu trả lời của cletus :

  • Đã thêm Javadoc cho tất cả các phương pháp.
  • Đã thêm kiểm tra cho các điều kiện tiên quyết của phương pháp.
  • Đã thay thế phân tích cú pháp tùy chỉnh valueOf(String)bằng tính BigInteger(String)năng này linh hoạt hơn và nhanh hơn.
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);
    }
}

0

Nhận xét ban đầu:

Đừng bao giờ viết cái này:

if ( condition ) statement;

Thế này tốt hơn

if ( condition ) { statement };

Chỉ cần tạo để tạo một thói quen tốt.

Bằng cách làm cho lớp trở nên bất biến như được đề xuất, bạn cũng có thể tận dụng lợi thế của lớp kép để thực hiện các phép toán bằng và mã băm và so sánh

Đây là phiên bản bẩn nhanh của tôi:

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

}

Về phương thức static factory, nó có thể hữu ích sau này, nếu bạn phân lớp Fraction để xử lý những thứ phức tạp hơn hoặc nếu bạn quyết định sử dụng một pool cho các đối tượng được sử dụng thường xuyên nhất.

Nó có thể không phải là trường hợp, tôi chỉ muốn chỉ ra nó. :)

Xem mục Java hiệu quả đầu tiên.


0

Có thể hữu ích khi thêm những thứ đơn giản như đối ứng, nhận phần còn lại và nhận toàn bộ.


câu trả lời này phù hợp như bình luận.
Jasonw

Khủng khiếp xin lỗi vì những lời cuối nhưng tôi tin rằng có một số tiền tối thiểu của đại diện (50?) Cần thiết để bình luận về một câu trả lời mà tôi không có ...
Darth Joshua

0

Mặc dù bạn có các phương thức CompareTo (), nếu bạn muốn sử dụng các tiện ích như Collections.sort (), thì bạn cũng nên triển khai So sánh.

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

Ngoài ra, để hiển thị đẹp, tôi khuyên bạn nên ghi đè toString ()

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

Và cuối cùng, tôi muốn công khai lớp để bạn có thể sử dụng nó từ các gói khác nhau.


0

Hàm này đơn giản hóa bằng cách sử dụng thuật toán eucledian khá hữu ích khi xác định phân số

 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);

 }

0

Đối với triển khai Phân số / Hợp lý cấp ngành, tôi sẽ triển khai nó để nó có thể đại diện cho NaN, dương vô cùng, âm vô cực và tùy chọn âm 0 với ngữ nghĩa hoạt động giống hệt như trạng thái tiêu chuẩn IEEE 754 cho số học dấu phẩy động (nó cũng giảm bớt chuyển đổi sang / từ các giá trị dấu phẩy động). Ngoài ra, vì so sánh với 0, một và các giá trị đặc biệt ở trên chỉ cần so sánh đơn giản, nhưng kết hợp giữa tử số và mẫu số với 0 và 1 - tôi sẽ thêm một số phương thức isXXX và so sánhToXXX để dễ sử dụng (ví dụ: eq0 () sẽ sử dụng tử số == 0 && mẫu số! = 0 phía sau thay vì để khách hàng so sánh với phiên bản có giá trị bằng 0). Một số giá trị được xác định trước tĩnh (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, v.v.) cũng hữu ích, vì chúng xuất hiện ở một số nơi dưới dạng giá trị không đổi. Đây là cách tốt nhất IMHO.


0

Phân số lớp:

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

Chương trình chính:

    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);
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.