Java'da bir kesri temsil etmenin en iyi yolu?


100

Kesirlerle çalışmaya çalışıyorumJava'da .

Aritmetik fonksiyonları uygulamak istiyorum. Bunun için önce fonksiyonları normalleştirmenin bir yolunu bulacağım. Ortak bir paydaya sahip olana kadar 1/6 ve 1/2 ekleyemeyeceğimi biliyorum. 1/6 ve 3/6 eklemem gerekecek. Saf bir yaklaşım bana 2/12 ve 6/12 eklememi ve sonra azaltmamı sağlar. En düşük performans cezası ile ortak bir paydaya nasıl ulaşabilirim? Bunun için en iyi algoritma hangisidir?


Sürüm 8 ( hstoerr sayesinde ):

İyileştirmeler şunları içerir:

  • equals () yöntemi artık CompareTo () yöntemiyle tutarlıdır
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;
    }
}

Önceki tüm sürümleri kaldırdım. Teşekkürler:


33
Kodu atın, Apache Commons kullanın :) commons.apache.org/math/userguide/fraction.html
Patrick

3
Patrick'in yorumu, yanıt olarak gönderilmiş olsaydı + 1'i hak ederdi. Çoğu durumda doğru cevap budur; Etkili Java'nın dediği gibi "kitaplıkları öğrenin ve kullanın". Asıl soru da açık ve kullanışlı.
Jonik

Cevabımı kabul ettiğinizi fark ettiniz .. Bu kodu gerçekten kullanıyorsanız ve onunla ilgili herhangi bir sorun veya eksik olan herhangi bir şey bulursanız lütfen bana bildirin! web sitemden
Kip

"CompareTo" yönteminizi düzenlemenizi ve "this.getNumerator ()" ı çarpmadan çok önce çevirmenizi öneririm . Aksi takdirde kod yine de taşmaya eğilimlidir. Ayrıca CompareTo yöntemini zaten uyguladığınız için, Comparable <Fraction> 'ı uygulamanın iyi olacağını düşünüyorum.
Hosam Aly

Ve şimdiye kadar gittiğiniz için, equals ve hashCode'u uygulamak da yararlı olabilir.
Hosam Aly

Yanıtlar:


65

Kısa bir süre önce Project Euler problemleri için bir BigFraction sınıfı yazdım. . Bir BigInteger payını ve paydasını tutar, böylece asla taşmaz. Ama asla taşmayacağını bildiğiniz bir çok işlem için biraz yavaş olacak .. neyse, isterseniz kullanın. Bunu bir şekilde göstermek için can atıyordum. :)

Düzenleme : Birim testleri de dahil olmak üzere bu kodun en son ve en büyük sürümü artık GitHub'da barındırılıyor ve ayrıca Maven Central aracılığıyla da kullanılabilir . Orijinal kodumu burada bırakıyorum, böylece bu cevap sadece bir bağlantı değil ...


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

}

Bir argüman null ise, bir NullPointerException oluşturun. Aslında kod bunu yine de yapacak, böylece çekiniz (ve IllegalArgumentException ile değiştirilecek (gereksiz kod bloat.
cletus

24
Katılmıyorum; Başka bir kullanıcı Kaynağıma bakmadan bu sınıfı kullanarak vardı ve bir NullPointerException var ise, o bir hata olduğunu düşünürdüm benim kod. Ancak bir IllegalArgumentException, javadoc tarafından ima edilen sözleşmeyi ihlal ettiğini gösteriyor (açıkça belirtmeme rağmen).
Kip


1
sadece bir soru, Commons Math'daki Kesir ve BigFraction'ın nesi var?
Mortimer

@Mortimer: Emin değilim, hiç bakmadım
Kip

61

Aslında, bunu boyut için deneyin. Çalışır ancak bazı sorunları olabilir:

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

Çıktı:

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'da uygun kesirler ile çalışmaya çalışıyorum.

Apache Commons Math , epeydir bir Kesir sınıfına sahiptir . Çoğu zaman, "Oğlum, keşke Java'nın çekirdek kütüphanesinde X gibi bir şey olsaydı !" Apache Commons kütüphanesi çatısı altında bulunabilir .


2
Bunun neden bu kadar düşük olduğunu size anlatacağım, Apache Commons kitaplığı yeni başlayanlar için uygun değil. Birincisi, o sayfada indirilecek doğrudan bir bağlantı yok (kenar çubuğu menüsünde gizlidir), ikincisi nasıl kullanılacağına dair talimat yok (oluşturma yolunuza bir kavanoz ekleyerek), üçüncü, yine de hepsini ekledikten sonra bir classDefNotFound hatası aldım . Yani bizden yalnızca kopyalayıp yapıştırmayı bilen kişilerden hiçbir olumlu oy alamazsınız.
Numen

@Noumenon herhangi bir yapı yöneticisini (örneğin maven) kullanmaya ve POM'a bağımlılık eklemeye ne dersiniz?
eugene.polschikov

1
Çaylaklar için küçük bir "Bunu projenizde nasıl kullanırsınız" yazısı görmek istiyorum. Bu öneri oraya gidebilir. Bununla birlikte, bunu nasıl yapacağımı buldum ve fabrika uygulamamda inç kesirlerini göstermeyi gerektiren kullandım ve size olumlu oyunuzu vermek için asla geri dönmedim. Yani teşekkürler, işte gecikmeli.
Noumenon

Bu adil bir geri bildirim. İşte benim de gecikmiş teşekkürlerim! :)
yawmark

Bunun kullanımı oldukça kolaydır.
Eric Wang

24

Lütfen onu değişmez bir tip yapın! Bir kesirin değeri değişmez - örneğin, bir buçuk üçte bir olmaz. SetDenominator yerine , aynı payda ancak belirtilen paydaya sahip yeni bir kesir döndüren withDenominator'a sahip olabilirsiniz .

Değişmez tiplerle hayat çok daha kolay.

Eşittir ve karma kodu geçersiz kılmak da mantıklıdır, bu nedenle haritalarda ve kümelerde kullanılabilir. Outlaw Programmer'ın aritmetik operatörler ve dize biçimlendirme hakkındaki noktaları da iyidir.

Genel bir kılavuz olarak BigInteger ve BigDecimal'e bir göz atın. Aynı şeyi yapmıyorlar ama size iyi fikirler verecek kadar benzerler.


5
"Lütfen onu değişmez bir tür yapın! Bir kesirin değeri değişmez - örneğin bir buçuk üçte bir olamaz." Liste / tuple / vektör (1, 2, 3, 4) değeri (4, 3, 2, 1) haline gelmez, ancak durum değişikliğini listeleyen çoğu insanı rahatsız etmiyor gibi görünüyor. Kesirler için değişmezliğe katılmadığımdan değil, ama daha iyi bir tartışmayı hak ediyor. Bir demet devletten daha fazla bir değer gibi geliyor. Programcı beklentisi, rehberlik edilmesi gereken doğru sebep mi? % 100 emin değilim ama kulağa iyi bir fikir gibi geliyor.
Jonas Kölker

2
Eh, gerçek hayatta listeleri yapmak değişikliği: nasıl bir alışveriş listesi yazabilirim? Boş bir kağıt parçasıyla başlayın ve üzerine yazın. Yolun yarısında ona hala "alışveriş listesi" diyorsun. Bunu söyledikten sonra, işlevsel programlama listeleri bile değişmez hale getirmeye çalışıyor ...
Jon Skeet

7

Birincisi, ayarlayıcılardan kurtulup Kesirler'i değişmez hale getirirdim.

Muhtemelen toplama, çıkarma vb. Yöntemleri ve belki de çeşitli String formatlarında gösterimi elde etmenin bir yolunu isteyeceksiniz.

DÜZENLEME: Muhtemelen niyetimi belirtmek için alanları 'son' olarak işaretlerdim ama sanırım bu büyük bir anlaşma değil ...


2
Kaç tane "değişmez yap" cevabı alacağımızı merak ediyorum :)
Jon Skeet

5
  • Add () ve multiply () gibi aritmetik yöntemler olmadan bir nevi anlamsızdır.
  • Eşittir () ve hashCode () öğelerini kesinlikle geçersiz kılmalısınız.
  • Kesiri normalleştirmek için bir yöntem eklemelisiniz veya bunu otomatik olarak yapmalısınız. 1/2 ve 2 / 4'ün aynı kabul edilmesini isteyip istemediğinizi düşünün - bunun equals (), hashCode () ve CompareTo () yöntemleri için çıkarımları vardır.

5

Bunları en küçüğünden en büyüğüne sıralamam gerekecek, bu yüzden sonunda onları bir çift olarak da temsil etmem gerekecek

Kesinlikle gerekli değil. (Aslında, eşitliği doğru bir şekilde ele almak istiyorsanız, düzgün çalışmak için ikiye güvenmeyin.) B * d pozitifse, a / b <c / d eğer ad <bc. Negatif tamsayılar varsa, bu uygun şekilde ele alınabilir ...

Şu şekilde yeniden yazabilirim:

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

Buradaki kullanım, longiki büyük ile çarparsanız taşma olmamasını sağlamaktır.int s'yi . handle Paydanın her zaman negatif olmadığını garanti edebiliyorsanız (negatifse, hem pay hem de paydayı yok sayın), o zaman b * d'nin pozitif olup olmadığını kontrol etmekten kurtulabilir ve birkaç adım kaydedebilirsiniz. Sıfır payda ile hangi davranışı aradığınızdan emin değilim.

Karşılaştırmak için çiftleri kullanmaya kıyasla performansın nasıl olduğundan emin değilim. (yani, performansı bu kadar önemsiyorsanız) İşte kontrol etmek için kullandığım bir test yöntemi. (Düzgün çalışıyor gibi görünüyor.)

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, uygulamak Comparableveya Comparatorsınıfınız için yeniden yapılandırmayı düşünebilirsiniz .)


Örneğin, a = 1, b = 3, c = -2, d = -3 ise bu doğru değildir. B ve d pozitifse, o zaman a / b <c / d ancak ve ancak ad <bc ise doğrudur.
Luke Woodward

Argh, nitelendirmeyi yanlış anladım. (teşekkürler!) Eğer bd> 0 ise durum olmalıdır.
Jason S

Doğru. Daha kesin olarak, a / b <c / d <=> ac <bd, bd> 0 sağlandığında doğrudur. Bd <0 ise, tersi doğrudur. (Eğer bd = 0 ise, o zaman bir serseri kesrine sahip olursunuz. :-))
Paul Brinkley

Kapat. a / b <c / d <=> ad <bc for bd> 0 demek istiyorsun. (Kod yorumlarımda ilk seferde doğru anladım!)
Jason S

4

Çok küçük bir gelişme, potansiyel olarak hesapladığınız çifte değeri kaydetmek olabilir, böylece bunu yalnızca ilk erişimde hesaplayabilirsiniz. Bu sayıya çok fazla erişmediğiniz sürece bu büyük bir kazanç olmayacak, ancak bunu yapmak da çok zor değil.

Ek bir nokta, paydada yaptığınız hata kontrolü olabilir ... otomatik olarak 0'ı 1'e değiştirirsiniz. Bunun sizin özel uygulamanız için doğru olup olmadığından emin değilsiniz, ancak genel olarak birisi 0'a bölmeye çalışıyorsa, bir şeyler çok yanlış. . Değeri, kullanıcı tarafından bilinmeyen görünüşte keyfi bir şekilde değiştirmek yerine, bunun bir istisna (gerekli olduğunu düşünüyorsanız özel bir istisna) atmasına izin verirdim.

Diğer bazı yorumların yanı sıra, çıkarma eklemek için yöntemler vb. Eklemekle ilgili olarak ... bunlara ihtiyaç duymadığınızdan bahsetmediğiniz için, istemediğinizi varsayıyorum. Ve gerçekten birçok yerde veya başkaları tarafından kullanılacak bir kütüphane inşa etmiyorsanız, YAGNI ile gidin (buna ihtiyacınız olmayacak, bu yüzden orada olmamalı.)


GetNumerator () ve getDenominator () öğelerine sahip olması, bu sınıfın DIŞINDA yeni kesirler yarattığına inanmamı sağladı. Bu mantık, varsa, muhtemelen buraya aittir.
Outlaw Programmer

+1 Paydada sessizce 0'ı 1'e değiştirmek felaket için bir reçetedir.
maaartinus

4

Bunu veya herhangi bir değer türünü geliştirmenin birkaç yolu vardır:

  • Pay ve payda finali yapmak da dahil olmak üzere sınıfınızı değişmez yapın
  • Kesirleri otomatik olarak kurallı bir biçime dönüştürün , örneğin 2/4 -> 1/2
  • ToString () uygulayın
  • Dizelerden kesirlere dönüştürmek için "public static Fraction valueOf (String s)" uygulayın. İnt, double, vb.'den dönüştürmek için benzer fabrika yöntemlerini uygulayın.
  • Toplama, çarpma vb. Uygulayın
  • Tam sayılardan kurucu ekle
  • Override equals / hashCode
  • Fraction'ı gerektiğinde BigInteger'a geçiş yapan bir uygulamaya sahip bir arayüz yapmayı düşünün
  • Alt sınıflandırmayı düşünün Numarayı
  • 0 ve 1 gibi ortak değerler için adlandırılmış sabitleri dahil etmeyi düşünün
  • Serileştirilebilir hale getirmeyi düşünün
  • Sıfıra bölme testi
  • API'nizi belgeleyin

Temel olarak, Double , Integer gibi diğer değer sınıfları için API'ye bir göz atın ve yaptıklarını yapın :)


3

Bir Kesrin payını ve paydasını diğerinin paydası ile çarparsanız ve tersini yaparsanız, aynı paydaya sahip iki kesir (yine aynı değerler) elde edersiniz ve payları doğrudan karşılaştırabilirsiniz. Bu nedenle, çift değeri hesaplamanıza gerek kalmaz:

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 () ve this.denominator'ın ters işaretleri varsa bu başarısız olur. (yazıma bakın.) Ayrıca çarpmanın taşabileceği gerçeğine de dikkat etmelisiniz.
Jason S

Ah evet, bu doğru. Ama bu durumda en azından anlayabildiğim Kip'in uygulamasını tercih ediyorum. ;)
Francisco Canedo

Uygulamamda sadece pay negatif olabilir. Ayrıca BigIntegers kullanıyorum, böylece asla bir taşma olmayacak (tabii ki biraz performans pahasına).
Kip

2

bu kodu nasıl geliştirebilirim:

  1. Dize Kesirine (String ler) dayalı bir yapıcı // "sayı / sayı" bekliyoruz
  2. bir kopya yapıcı Kesir (Kesir kopya)
  3. klon yöntemini geçersiz kıl
  4. equals, toString ve hashcode yöntemlerini uygular
  5. java.io arayüzünü uygular.Serializable, Comparable
  6. "double getDoubleValue ()" yöntemi
  7. bir yöntem ekle / böl / vb ...
  8. Bu sınıfı değişmez yapardım (ayarlayıcı yok)

Oldukça güzel bir liste. Muhtemelen klonlama / serileştirilebilirliğe gerek yoktur, ancak diğer her şey makul.
Outlaw Programmer

@OutlawProgrammer: Evet, 8 veya 3. Klonlanabilir değişmez bir anlamsızdır.
maaartinus

2

Zaten bir CompareTo fonksiyonunuz var ... Ben Comparable arayüzünü uygularım.

Bununla ne yapacağın gerçekten önemli olmayabilir.



2

Özellikle : Sıfır paydayı geçirmenin daha iyi bir yolu var mı? Paydayı 1 olarak ayarlamak çok keyfi bir duygu. Bunu nasıl doğru yapabilirim?

Sıfıra bölmek için bir Aritmetik İstisna at derdim, çünkü gerçekten olan bu:

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

"Sıfıra böl." Yerine, mesajın "Sıfıra Böl: Kesir için Payda sıfırdır" demesini isteyebilirsiniz.


1

Bir kesir nesnesi oluşturduktan sonra, neden diğer nesnelerin payı veya paydayı ayarlamasına izin vermek istersiniz? Bunların sadece okunması gerektiğini düşünüyorum. Nesneyi değişmez kılar ...

Ayrıca ... paydayı sıfıra ayarlamak, geçersiz bir bağımsız değişken istisnası oluşturmalıdır (Java'da ne olduğunu bilmiyorum)


Veya yeni ArithmeticException ("Sıfıra böl.")
Kip

1

Timothy Budd, "C ++ 'da Veri Yapıları" nda Rational sınıfının iyi bir uygulamasına sahiptir. Elbette farklı bir dil ama Java'ya çok güzel bir şekilde bağlanıyor.

Daha fazla kurucu tavsiye ederim. Varsayılan bir kurucu, pay 0, payda 1'e sahip olacaktır. Tek bir arg oluşturucu, 1 paydasını varsayacaktır. Kullanıcılarınızın bu sınıfı nasıl kullanabileceğini düşünün.

Sıfır payda için kontrol yok mu? Kontratla programlama eklemenizi sağlar.


1

Üçüncü veya beşinci ya da kesirinizi değişmez kılmak için öneri her neyse. Ayrıca Number sınıfını genişletmenizi tavsiye ederim . Muhtemelen Double sınıfına bakardım, çünkü muhtemelen aynı yöntemlerin çoğunu uygulamak isteyeceksiniz.

Muhtemelen, bu davranış muhtemelen bekleneceği için, Karşılaştırılabilir ve Serileştirilebilir'i de uygulamalısınız . Bu nedenle, CompareTo () uygulamanız gerekecektir. Ayrıca equals () öğesini geçersiz kılmanız gerekecek ve hashCode () öğesini de geçersiz kılmanız için yeterince vurgu yapamıyorum. Bu, ComparTo () ve equals () öğelerinin tutarlı olmasını istemediğiniz birkaç durumdan biri olabilir, çünkü birbirine indirgenebilen kesirler mutlaka eşit değildir.


1

Sevdiğim bir temizlik uygulaması, yalnızca bir geri dönüşün olmasıdır.

 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'un cevabını temizledim :

  • Tüm yöntemler için Javadoc eklendi.
  • Yöntem ön koşulları için kontroller eklendi.
  • Özel ayrıştırma , hem daha esnek hem de daha hızlı olan valueOf(String)ile değiştirildi 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

İlk açıklama:

Bunu asla yazma:

if ( condition ) statement;

Bu daha iyi

if ( condition ) { statement };

Sadece iyi bir alışkanlık yaratmak için yaratın.

Sınıfı önerildiği gibi değiştirilemez hale getirerek, eşittir ve hashCode ve CompareTo işlemlerini gerçekleştirmek için ikiye katlama avantajından da yararlanabilirsiniz.

İşte benim hızlı kirli versiyonum:

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

}

Statik fabrika yöntemi hakkında, daha karmaşık şeyleri işlemek için Kesir'i alt sınıflara ayırırsanız veya en sık kullanılan nesneler için bir havuz kullanmaya karar verirseniz daha sonra yararlı olabilir.

Durum böyle olmayabilir, sadece belirtmek istedim. :)

Etkili Java birinci maddesine bakın .


0

Karşılık vermek, geri kalanı almak ve bütün olmak gibi basit şeyler eklemek faydalı olabilir.


bu cevap yorum olarak uygundur.
Jasonw

Geç cevap için çok üzgünüm, ancak sahip olmadığım bir cevaba yorum yapmak için minimum miktarda tekrar (50?) Gerektiğine inanıyorum ...
Darth Joshua

0

ComparTo () yöntemlerine sahip olsanız bile, Collections.sort () gibi yardımcı programlardan yararlanmak istiyorsanız, o zaman Comparable'ı da uygulamalısınız.

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

Ayrıca, güzel bir görüntü için toString () 'i geçersiz kılmanızı öneririm

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

Ve son olarak, sınıfı farklı paketlerden kullanabilmeniz için herkese açık hale getirdim.


0

Bu işlev, ökledik algoritmayı kullanmayı kolaylaştırır, kesirleri tanımlarken oldukça yararlıdır

 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

Endüstri düzeyinde Kesir / Rasyonel uygulama için, onu NaN, pozitif sonsuz, negatif sonsuz ve isteğe bağlı olarak negatif sıfırı, kayan nokta aritmetiği için IEEE 754 standart durumları ile tamamen aynı olan operasyonel anlamlarla temsil edebilmesi için uygularım (aynı zamanda kayan nokta değerlerine / değerlerinden dönüştürme). Artı, sıfır, bir ve yukarıdaki özel değerlerle karşılaştırma sadece basit, ancak pay ve paydanın 0 ve 1 ile karşılaştırılmasını gerektirdiğinden - kullanım kolaylığı için birkaç isXXX ve CompareToXXX yöntemi eklerdim (örn. Eq0 () istemcinin sıfır değerli bir örnekle karşılaştırma yapmasına izin vermek yerine perde arkasında pay == 0 && payda! = 0 kullanın). Statik olarak önceden tanımlanmış bazı değerler (SIFIR, BİR, İKİ, TEN, BİR_TENTH, NAN, vb.) Da yararlıdır, çünkü birkaç yerde sabit değerler olarak görünürler. IMHO'nun en iyi yolu budur.


0

Sınıf Kesir:

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

Ana program:

    static void main(String[] args){
    Scanner input = new Scanner(System.in);
    System.out.println("Enter numerator and denominator of first fraction");
    int num1 =input.nextInt();
    int denom1 =input.nextInt();
    Fraction x = new Fraction(num1, denom1);
    System.out.println("Enter numerator and denominator of second fraction");
    int num2 =input.nextInt();
    int denom2 =input.nextInt();
    Fraction y = new Fraction(num2, denom2);
    Fraction result = new Fraction();
    System.out.println("Enter required operation: A (Add), M (Multiply)");
    char op = input.next().charAt(0);
    if(op == 'A') {
        result = x.add(y);
        System.out.println(x + " + " + y + " = " + result);
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.