Java에서 분수를 나타내는 가장 좋은 방법은 무엇입니까?


100

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

이전 버전을 모두 제거했습니다. 감사합니다 :


33
코드를 버리고 Apache Commons를 사용하십시오 :) commons.apache.org/math/userguide/fraction.html
Patrick

3
Patrick의 댓글은 답변으로 게시 된 경우 +1을받을 가치가 있습니다. 대부분의 경우 정답입니다. Effective Java는 "라이브러리를 알고 사용"합니다. 원래의 질문도 명확하고 유용합니다.
Jonik

내 대답을 수락했음을 알았습니다. 실제로 해당 코드를 사용하고 있고 문제가 있거나 부족한 것이 있으면 알려주십시오! 내 웹 사이트에서 이메일을 보내주세요 : vacant-nebula.com/contact/kip
Kip

"compareTo"메서드를 편집하고 곱하기 훨씬 전에 "this.getNumerator ()"를 캐스팅하는 것이 좋습니다 . 그렇지 않으면 코드가 여전히 오버플로되기 쉽습니다. 또한 이미 compareTo 메서드를 구현했기 때문에 Comparable <Fraction>을 구현하는 것이 좋을 것이라고 생각합니다.
Hosam Aly

그리고 지금까지 갔으므로 equals와 hashCode도 구현하는 것이 유용 할 수 있습니다.
Hosam Aly

답변:


65

얼마 전에 프로젝트 오일러 문제에 대해 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;
    }
  }

}

인수가 null이면 NullPointerException을 throw합니다. 사실 코드는 어쨌든 그렇게 할 것입니다. 그래서 당신의 수표 (그리고 IllegalArgumentException (으로 대체하는 것은 불필요한 코드 부풀림)입니다.
cletus

24
동의하지 않습니다. 다른 사용자가 내 소스를 보지 않고이 클래스를 사용하고 NullPointerException이 발생하면 코드에 버그가 있다고 생각 합니다. 그러나 IllegalArgumentException은 그가 javadoc에 의해 암시 된 계약을 위반했음을 보여줍니다 (명시 적으로 설명하지 못했지만).
Kip


1
질문입니다. Commons Math의 Fraction과 BigFraction에 무엇이 문제입니까?
Mortimer

@Mortimer : 확실하지 않습니다, 저는 그것을 본 적이 없습니다
Kip

61
  • 불변으로 만드십시오 ;
  • 이를 표준으로 만듭니다 . 즉, 6/4가 3/2가됩니다 ( 최대 공약수 알고리즘이 유용합니다).
  • 당신이 대표하는 것은 합리적인 숫자 이기 때문에 그것을 Rational이라고 부르십시오 .
  • BigInteger임의의 정확한 값을 저장 하는 데 사용할 수 있습니다 . 그렇지 않다면 long구현이 더 쉽습니다.
  • 분모를 항상 양수로 만드십시오. 기호는 분자로 전달되어야합니다.
  • 확장 Number;
  • 구현 Comparable<T>;
  • 구현 equals()hashCode();
  • 로 표시되는 숫자에 대한 팩토리 메서드를 추가합니다 String.
  • 편리한 팩토리 메소드를 추가하십시오.
  • 추가 toString(); 과
  • 그것을 만드십시오 Serializable.

사실, 사이즈를 위해 이것을 시도하십시오. 실행되지만 몇 가지 문제가있을 수 있습니다.

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

30

Java에서 적절한 분수로 작업하려고합니다.

Apache Commons Math 는 꽤 오랫동안 Fraction 클래스를 사용했습니다. 대부분의 경우 "Boy I wish Java가 코어 라이브러리에 X 와 같은 것을 가지고 있었으면 합니다!" Apache Commons 라이브러리 의 우산 아래에서 찾을 수 있습니다 .


2
이것이 왜 그렇게 낮은 지 말씀 드리겠습니다. Apache Commons 라이브러리는 초보자에게 친숙하지 않습니다. 먼저 해당 페이지에 다운로드 할 직접 링크가 없습니다 (사이드 바 메뉴에 숨겨져 있음), 두 번째 사용 방법에 대한 지침이 없습니다 (빌드 경로에 항아리 추가), 세 번째로 모든 것을 추가 한 후 classDefNotFound 오류가 발생했습니다. . 따라서 복사하여 붙여 넣는 방법 만 아는 사람들로부터 찬성표를받지 못합니다.
Noumenon 2013-08-24

@Noumenon 빌드 관리자 (예 : maven)를 사용하고 POM에 종속성을 추가하는 방법은 무엇입니까?
eugene.polschikov 2015

1
멍청한 분들을 위해 "프로젝트에서 이것을 사용하는 방법"에 대한 설명을보고 싶습니다. 그 제안은 거기에 들어갈 수 있습니다. 즉, 나는 그것을하는 방법을 알아 냈고 인치의 분수를 표시 해야하는 공장 앱에서 사용했으며 결코 당신에게 upvote를 제공하기 위해 돌아 오지 않았습니다. 그래서 감사합니다.
Noumenon 2015

공정한 피드백입니다. 뒤늦은 감사합니다! :)
yawmark

이것은 사용하기 매우 쉽습니다.
Eric Wang

24

불변 유형으로 만드십시오! 분수의 값은 변하지 않습니다. 예를 들어 절반이 1/3이되지는 않습니다. setDenominator 대신 분자는 같지만 지정된 분모를 갖는 분수를 반환하는 withDenominator를 사용할 수 있습니다 .

불변 유형으로 삶은 훨씬 더 쉽습니다.

같음 및 해시 코드를 재정의하는 것도 합리적이므로 맵과 집합에서 사용할 수 있습니다. 산술 연산자와 문자열 형식에 대한 Outlaw Programmer의 요점도 좋습니다.

일반적인 가이드로 BigInteger 및 BigDecimal을 살펴보십시오. 그들은 똑같은 일을하지 않지만 좋은 아이디어를 줄만큼 충분히 유사합니다.


5
"불변 유형으로 만드세요! 분수의 값은 변하지 않습니다. 예를 들어 절반이 1/3이되지 않습니다." 목록 / 튜플 / 벡터 (1, 2, 3, 4)도 값 (4, 3, 2, 1)이되지는 않지만 변경 상태를 나열하는 대부분의 사람들을 괴롭히지 않는 것 같습니다. 내가 분수에 대한 불변성에 동의하지 않는다는 것은 아니지만 더 나은 주장을 할 가치가 있습니다. 그것은 상태 묶음 이상의 가치처럼 느껴집니다. 프로그래머의 기대가 올바른 이유인가? 100 % 확실하지는 않지만 좋은 생각처럼 들립니다.
Jonas Kölker

2
실생활에서 목록 변경됩니다. 쇼핑 목록을 어떻게 작성합니까? 빈 종이로 시작하여 그 위에 씁니다. 반쯤 지나면 여전히 "쇼핑 목록"이라고 부릅니다. 하지만 함수형 프로그래밍은 목록조차도 불변으로 만들기 위해 노력하고 있습니다 ...
Jon Skeet

7

음, 하나는 setter를 제거하고 Fractions를 불변으로 만들 것입니다.

당신은 아마도 더하기, 빼기 등의 메소드를 원할 것이고, 다양한 문자열 형식으로 표현을 얻는 방법을 원할 것입니다.

편집 : 나는 아마도 내 의도를 알리기 위해 필드를 '최종'으로 표시 할 것이지만 큰 문제는 아니라고 생각합니다 ...


2
나는 우리가 :) 될 겁니다 답변 "그것은 불변하게"얼마나 많은 궁금
존 소총

5
  • add () 및 multiply () 등과 같은 산술 방법이 없으면 무의미합니다.
  • 분명히 equals ()와 hashCode ()를 재정의해야합니다.
  • 분수를 정규화하는 방법을 추가하거나 자동으로 수행해야합니다. 1/2 및 2/4를 동일하게 간주할지 여부를 생각해보십시오. 이것은 equals (), hashCode () 및 compareTo () 메서드에 영향을줍니다.

5

가장 작은 것부터 가장 큰 것까지 주문해야하므로 결국에는 두 배로 표시해야합니다.

꼭 필요한 것은 아닙니다. (사실 평등을 올바르게 처리하려면 제대로 작동하기 위해 double에 의존하지 마십시오.) b * d가 양수이면 a / b <c / d if ad <bc. 음의 정수가 포함되어 있으면 적절하게 처리 할 수 ​​있습니다.

다음과 같이 다시 작성할 수 있습니다.

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두 개의 큰 ints 를 곱하는 경우 오버플로가 없는지 확인하는 것 입니다. 처리 분모가 항상 음수가 아닌 것을 보장 할 수 있다면 (음수이면 분자와 분모를 모두 부정), b * d가 양수인지 확인하지 않고 몇 단계를 절약 할 수 있습니다. 분모가 0 인 상태에서 원하는 동작이 무엇인지 잘 모르겠습니다.

비교를 위해 double을 사용하는 것과 성능이 어떻게 비교되는지 확실하지 않습니다. (즉, 성능에 관심이 많다면) 여기에 내가 확인한 테스트 방법이 있습니다. (제대로 작동하는 것 같습니다.)

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

(구현 Comparable하거나 Comparator클래스에 대한 재구성을 고려할 수 있습니다 .)


예를 들어 a = 1, b = 3, c = -2, d = -3 인 경우에는 해당되지 않습니다. b와 d가 양수이면 ad <bc 인 경우에만 a / b <c / d입니다.
Luke Woodward

아아, 자격이 잘못되었습니다. (감사합니다!) bd> 0이면 조건이되어야합니다.
Jason S

진실. 보다 정확하게는 a / b <c / d <=> ac <bd는 bd> 0이면 참입니다. bd <0이면 반대가 참입니다. (bd = 0이면 부랑자 분수가 있습니다. :-))
Paul Brinkley

닫기. a / b <c / d <=> ad <bc for bd> 0을 의미합니다. (내 코드 주석에서 처음으로 맞았습니다!)
Jason S

4

아주 사소한 개선 사항 중 하나는 잠재적으로 계산중인 이중 값을 저장하여 첫 번째 액세스에서만 계산하도록하는 것입니다. 이 번호에 많이 액세스하지 않는 한 큰 승리는 아니지만 그렇게하는 것도 그렇게 어렵지 않습니다.

한 가지 추가 요점은 분모에서 수행하는 오류 검사일 수 있습니다. 자동으로 0에서 1로 변경합니다. 이것이 특정 응용 프로그램에 맞는지 확실하지 않지만 일반적으로 누군가가 0으로 나누려고하면 뭔가 매우 잘못된 것입니다. . 사용자에게 알려지지 않은 겉보기에 임의의 방식으로 값을 변경하는 대신 예외 (필요하다고 생각하는 경우 특수한 예외)가 발생하도록합니다.

다른 의견과 함께, 빼기를 추가하는 방법 등을 추가하는 것에 대해 ... 필요하다고 언급하지 않았기 때문에 나는 당신이 필요하지 않다고 가정하고 있습니다. 그리고 실제로 많은 곳에서 또는 다른 사람들이 사용할 라이브러리를 구축하지 않는 한 YAGNI와 함께 가십시오 (필요하지 않을 것이므로 거기에 있으면 안됩니다).


그가 getNumerator () 및 getDenominator ()를 가지고 있다는 사실은 그가이 클래스 외부에 새로운 분수를 만들고 있다고 믿게 만듭니다. 그 논리가 존재한다면 아마도 여기에 속할 것입니다.
Outlaw Programmer

+1 분모의 0을 1로 조용히 바꾸는 것은 재앙의 비결입니다.
maaartinus

4

이를 개선하거나 값 유형을 개선하는 방법에는 여러 가지가 있습니다.

  • 분자와 분모를 최종화하는 것을 포함 하여 클래스를 변경 불가능하게 만드십시오.
  • 분수를 표준 형식으로 자동 변환 ( 예 : 2/4-> 1/2)
  • toString () 구현
  • "public static Fraction valueOf (String s)"를 구현하여 문자열에서 분수로 변환합니다. int, double 등에서 변환하기 위해 유사한 팩토리 메서드를 구현합니다.
  • 덧셈, 곱셈 등 구현
  • 정수에서 생성자 추가
  • equals / hashCode 재정의
  • Fraction을 필요에 따라 BigInteger로 전환하는 구현이있는 인터페이스로 만드는 것을 고려하십시오.
  • 하위 분류 번호 고려
  • 0 및 1과 같은 공통 값에 대해 명명 된 상수를 포함하는 것을 고려하십시오.
  • 직렬화 가능하도록 고려
  • 0으로 나누기 테스트
  • API 문서화

기본적으로 Double , Integer와 같은 다른 값 클래스에 대한 API를 살펴보고 수행하는 작업을 수행합니다. :)


3

한 Fraction의 분자와 분모를 다른 분수의 분모와 곱하거나 그 반대의 경우, 동일한 분모를 가진 두 개의 분수 (여전히 동일한 값)로 끝나고 분자를 직접 비교할 수 있습니다. 따라서 double 값을 계산할 필요가 없습니다.

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

frac.getDenominator ()와 this.denominator에 반대 부호가 있으면 실패합니다. (내 게시물을 참조하십시오.) 또한 곱셈이 오버플로 될 수 있다는 사실을주의해야합니다.
Jason S

아, 맞습니다. 그러나 그 경우에는 적어도 이해할 수있는 Kip의 구현을 선호합니다. ;)
Francisco Canedo

내 구현에서는 분자 만 음수가 될 수 있음을 지적합니다. 또한 BigIntegers를 사용하므로 오버플로가 발생하지 않습니다 (물론 성능 저하).
Kip

2

그 코드를 어떻게 개선 할 것인가 :

  1. String Fraction (String s) 기반 생성자 // "number / number"를 예상합니다.
  2. 복사 생성자 Fraction (Fraction copy)
  3. 복제 방법 재정의
  4. equals, toString 및 hashcode 메소드를 구현합니다.
  5. 인터페이스 java.io.Serializable, Comparable을 구현합니다.
  6. "double getDoubleValue ()"메소드
  7. 메소드 추가 / 나누기 / 기타 ...
  8. 나는 그 클래스를 불변으로 만들 것입니다 (세터 없음)

꽤 좋은 목록입니다. 복제 / 직렬화 가능할 필요는 없지만 다른 모든 것은 합리적입니다.
Outlaw Programmer

@OutlawProgrammer : 예, 8 또는 3. 복제 가능 불변은 말도 안됩니다.
maaartinus 2014 년

2

당신은 이미 compareTo 함수를 가지고 있습니다 ... 저는 Comparable 인터페이스를 구현할 것입니다.

당신이 그것으로 무엇을 할 것인지에 대해서는 정말로 중요하지 않을 수 있습니다.



2

구체적으로 : 분모가 0 인 것을 처리하는 더 좋은 방법이 있습니까? 분모를 1로 설정하는 것은 매우 임의적입니다. 이 작업을 올바르게 수행하려면 어떻게해야합니까?

나는 0으로 나누기 위해 ArithmeticException을 던진다 고 말할 것입니다.

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

"0으로 나누기"대신 "0으로 나누기 : 분수의 분모가 0입니다."라는 메시지를 표시 할 수 있습니다.


1

분수 객체를 만든 후에 다른 객체가 분자 또는 분모를 설정하도록 허용하려는 이유는 무엇입니까? 나는 이것들이 읽기 전용이어야한다고 생각합니다. 그것은 개체를 불변으로 만듭니다 ...

또한 ... 분모를 0으로 설정하면 잘못된 인수 예외가 발생해야합니다 (Java에서 무엇인지 모르겠습니다).


또는 new ArithmeticException ( "Divide by zero.")
Kip

1

Timothy Budd는 "Data Structures in C ++"에서 Rational 클래스를 잘 구현했습니다. 물론 다른 언어이지만 Java로 매우 훌륭하게 이식됩니다.

더 많은 생성자를 추천합니다. 기본 생성자는 분자 0, 분모 1을 갖습니다. 단일 arg 생성자는 분모 1을 가정합니다. 사용자가이 클래스를 어떻게 사용할지 생각해보십시오.

분모가 0인지 확인하지 않습니까? 계약에 의한 프로그래밍은 추가해 주셨으면합니다.


1

나는 세 번째 또는 다섯 번째 또는 분수를 불변으로 만들기위한 권장 사항이 무엇이든 할 것입니다. 또한 Number 클래스를 확장하는 것이 좋습니다 . 아마도 동일한 메서드를 많이 구현하고 싶을 것이므로 Double 클래스를 살펴볼 것입니다 .

이 동작은 예상 할 수 있으므로 ComparableSerializable 도 구현해야합니다 . 따라서 compareTo ()를 구현해야합니다. 또한 equals ()를 재정의해야하며 hashCode ()도 재정의 할만큼 강하게 강조 할 수는 없습니다. 이것은 서로 감소 할 수있는 분수가 반드시 같지는 않기 때문에 compareTo ()와 equals ()가 일관성을 갖기를 원하지 않는 몇 안되는 경우 중 하나 일 수 있습니다.


1

내가 좋아하는 정리 관행은 단 한 번만 반환하는 것입니다.

 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

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

0

초기 비고 :

이것을 쓰지 마십시오 :

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을 하위 클래스로 지정하여 더 복잡한 것을 처리하거나 가장 자주 사용되는 개체에 풀을 사용하기로 결정한 경우 유용 할 수 있습니다.

사실이 아닐 수도 있습니다. 그냥 지적하고 싶었습니다. :)

효과적인 Java 첫 번째 항목을 참조하십시오 .


0

reciprocate, 나머지 가져 오기 및 전체 가져 오기와 같은 간단한 것을 추가하는 데 유용 할 수 있습니다.


이 답변은 의견으로 적합합니다.
Jasonw

늦은 답변 죄송합니다 끔찍한하지만 난 믿고 내가 해달라고 답변에 코멘트에 필요한 담당자의 최소 금액은 (? 50) ...이
다스 여호수아

0

compareTo () 메소드가 있더라도 Collections.sort ()와 같은 유틸리티를 사용하려면 Comparable도 구현해야합니다.

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

또한 예쁜 디스플레이를 위해 toString ()을 재정의하는 것이 좋습니다.

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

마지막으로 다른 패키지에서 사용할 수 있도록 클래스를 공개합니다.


0

이 함수는 유 클레 디안 알고리즘을 사용하여 단순화하여 분수를 정의 할 때 매우 유용합니다.

 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

산업 등급 분수 / 합리적 구현의 경우 부동 소수점 산술에 대한 IEEE 754 표준 상태와 정확히 동일한 연산 의미를 사용하여 NaN, 양의 무한대, 음의 무한대 및 선택적으로 음의 0을 나타낼 수 있도록 구현합니다. 부동 소수점 값으로 /에서 변환). 또한 0, 1 및 위의 특수 값에 대한 비교는 단순하지만 0과 1에 대한 분자와 분모의 결합 비교 만 필요하기 때문에 사용 편의성을 위해 isXXX 및 compareToXXX 메서드를 여러 개 추가합니다 (예 : eq0 () numerator == 0 && denominator! = 0을 사용하여 클라이언트가 값이 0 인 인스턴스와 비교하도록하는 대신 배후에서 사용하십시오. 정적으로 미리 정의 된 일부 값 (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN 등)도 유용합니다. 여러 곳에서 일정한 값으로 나타나기 때문입니다. 이것이 가장 좋은 방법입니다.


0

클래스 분수 :

     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);
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.