Najlepszy sposób na przedstawienie ułamka w Javie?


100

Próbuję pracować z ułamkami w Javie.

Chcę zaimplementować funkcje arytmetyczne. W tym celu najpierw potrzebuję sposobu na znormalizowanie funkcji. Wiem, że nie mogę dodać 1/6 i 1/2, dopóki nie mam wspólnego mianownika. Będę musiał dodać 1/6 i 3/6. Naiwne podejście wymagałoby dodania 2/12 i 6/12, a następnie zmniejszenia. Jak osiągnąć wspólny mianownik przy jak najmniejszym spadku wydajności? Jaki algorytm jest do tego najlepszy?


Wersja 8 (dzięki hstoerr ):

Ulepszenia obejmują:

  • metoda equals () jest teraz spójna z metodą 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;
    }
}

Usunąłem wszystkie poprzednie wersje. Moje podziękowania dla:


33
Wyrzuć kod, użyj Apache Commons :) commons.apache.org/math/userguide/fraction.html
Patrick

3
Komentarz Patryka zasługiwałby na +1, gdyby został opublikowany jako odpowiedź. W większości przypadków jest to właściwa odpowiedź; „poznaj i korzystaj z bibliotek”, jak mówi Effective Java. Oryginalne pytanie jest również jasne i przydatne.
Jonik,

Zauważyłem, że zaakceptowałeś moją odpowiedź ... jeśli faktycznie używasz tego kodu i znajdujesz z nim problemy lub czegokolwiek, czego w nim brakuje, daj mi znać! napisz do mnie z mojej strony internetowej: vacant-nebula.com/contact/kip
Kip

Proponuję edytować metodę „compareTo” i rzutować „this.getNumerator ()” na długo przed pomnożeniem. W przeciwnym razie kod jest nadal podatny na przepełnienie. Myślę też, że byłoby miło zaimplementować Comparable <Fraction>, ponieważ zaimplementowałeś już metodę compareTo.
Hosam Aly

A ponieważ zaszedłeś już tak daleko, może być przydatne zaimplementowanie equals i hashCode.
Hosam Aly

Odpowiedzi:


65

Tak się składa, że ​​niedawno napisałem klasę BigFraction, dotyczącą problemów z Projektem Euler . Zachowuje licznik i mianownik BigInteger, więc nigdy się nie przepełni. Ale będzie to trochę powolne w przypadku wielu operacji, o których wiesz, że nigdy się nie przepełnią… w każdym razie użyj go, jeśli chcesz. Nie mogłem się jakoś pochwalić, żeby to pokazać. :)

Edycja : najnowsza i najlepsza wersja tego kodu, w tym testy jednostkowe, jest teraz hostowana na GitHub, a także dostępna za pośrednictwem Maven Central . Zostawiam tutaj mój oryginalny kod, aby ta odpowiedź nie była tylko linkiem ...


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

}

Jeśli argument ma wartość null, zgłoś wyjątek NullPointerException. W rzeczywistości kod i tak to zrobi, więc Twój czek (i zastąpienie go przez IllegalArgumentException (to niepotrzebny nadmiar kodu.
cletus

24
Nie zgadzam się; Gdyby inny użytkownik używał tej klasy bez patrzenia na moje źródło i uzyskał wyjątek NullPointerException, pomyślałby, że w moim kodzie jest błąd . Jednak wyjątek IllegalArgumentException wskazuje, że złamał on umowę sugerowaną przez javadoc (mimo że nie powiedziałem tego wyraźnie).
Kip


1
tylko pytanie, co jest nie tak z Fraction i BigFraction w Commons Math?
Mortimer

@Mortimer: nie jestem pewien, nigdy na to nie patrzyłem
Kip

61
  • Uczyń to niezmiennym ;
  • Ustaw je jako kanoniczne , co oznacza, że ​​6/4 stanie się 3/2 ( największy wspólny dzielnik przydaje się do tego algorytm );
  • Nazwij to racjonalnym, ponieważ to, co reprezentujesz, jest liczbą wymierną ;
  • Możesz użyć BigIntegerdo przechowywania dowolnie precyzyjnych wartości. Jeśli nie to, tolong , to ma łatwiejszą implementację;
  • Niech mianownik będzie zawsze pozytywny. Znak powinien być noszony przez licznik;
  • Poszerzać Number ;
  • Wprowadzić w życie Comparable<T> ;
  • Wdrożenie equals()ihashCode() ;
  • Dodaj metodę fabryki dla liczby reprezentowanej przez String ;
  • Dodaj kilka wygodnych metod fabrycznych;
  • Dodać toString() ; i
  • Zrób to Serializable.

W rzeczywistości spróbuj tego dla rozmiaru. Działa, ale może mieć pewne problemy:

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

Wynik to:

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

Próbuję pracować z odpowiednimi ułamkami w Javie.

Apache Commons Math ma klasę Fraction od dłuższego czasu. W większości przypadków odpowiedź brzmi: „Chłopcze, chciałbym, żeby Java miała coś takiego jak X w podstawowej bibliotece!” można znaleźć pod parasolem biblioteki Apache Commons .


2
Powiem ci, dlaczego jest tak niska, że ​​biblioteka Apache Commons nie jest przyjazna dla początkujących. Po pierwsze nie ma bezpośredniego linku do pobrania na tej stronie (jest ukryty w menu paska bocznego), po drugie nie ma instrukcji, jak go używać (dodawanie jar do ścieżki kompilacji), po trzecie otrzymałem błąd classDefNotFound po dodaniu tego wszystkiego w tak czy inaczej . Więc nie dostajesz głosów od nas, ludzi, którzy wiedzą tylko, jak kopiować i wklejać.
Noumenon,

@Noumenon, co powiesz na użycie dowolnego menedżera kompilacji (np. Maven) i dodanie zależności w POM?
eugene.polschikov

1
Chciałbym zobaczyć krótką notkę „Jak to wykorzystać w swoim projekcie” dla noobów. Ta sugestia może się tam znaleźć. To powiedziawszy, wymyśliłem, jak to zrobić i użyłem tego w mojej fabrycznej aplikacji, która wymagała wyświetlania ułamków cali, i nigdy nie wróciłem, aby dać ci twoją opinię. Więc dzięki, tutaj jest późno.
Noumenon

To uczciwa opinia. Oto moje spóźnione podziękowania! :)
yawmark

Ten jest dość łatwy w użyciu.
Eric Wang

24

Proszę, uczyń go niezmiennym typem! Wartość ułamka się nie zmienia - na przykład połowa nie staje się trzecią. Zamiast setDenominator, możesz mieć withDenominator, który zwraca nowy ułamek, który ma ten sam licznik, ale określony mianownik.

Życie jest dużo łatwiejsze z niezmiennymi typami.

Zastąpienie równości i hashcode również byłoby rozsądne, więc można go używać w mapach i zestawach. Uwagi programisty dotyczące operatorów arytmetycznych i formatowania łańcuchów są również dobre.

Jako ogólny przewodnik, spójrz na BigInteger i BigDecimal. Nie robią tego samego, ale są na tyle podobne, że dają dobre pomysły.


5
„Proszę, uczyń to typem niezmiennym! Wartość ułamka się nie zmienia - na przykład połowa nie staje się trzecią”. Również lista / krotka / wektor (1, 2, 3, 4) nie staje się wartością (4, 3, 2, 1), ale nie przeszkadza to większości osób, które wymieniają stan. Nie, żebym nie zgadzał się z niezmiennością ułamków, ale zasługuje na lepszy argument. Wydaje się, że jest to wartość większa niż pakiet państwa. Czy oczekiwania programisty to właściwy powód, aby się nim kierować? Nie jestem pewien w 100%, ale brzmi to na dobry pomysł.
Jonas Kölker

2
Cóż, w prawdziwym życiu listy się zmieniają: jak pisze się listę zakupów? Zaczynasz od czystej kartki papieru i piszesz na niej. W połowie nadal nazwałbyś to „listą zakupów”. Powiedziawszy to, programowanie funkcjonalne stara się, aby nawet listy były niezmienne ...
Jon Skeet

7

Cóż, po pierwsze, pozbyłbym się seterów i uczyniłbym frakcje niezmiennymi.

Prawdopodobnie będziesz także potrzebować metod dodawania, odejmowania itp., A może jakiegoś sposobu na uzyskanie reprezentacji w różnych formatach String.

EDYCJA: Prawdopodobnie oznaczyłbym pola jako `` ostateczne '', aby zasygnalizować mój zamiar, ale myślę, że to nic wielkiego ...


2
Zastanawiam się, jak wiele „make it niezmienne” odpowiedzi będziemy skończyć się :)
Jon Skeet

5
  • To trochę bezcelowe bez metod arytmetycznych, takich jak add () i multiply (), itp.
  • Zdecydowanie powinieneś nadpisać equals () i hashCode ().
  • Powinieneś albo dodać metodę normalizacji ułamka, albo zrobić to automatycznie. Zastanów się, czy chcesz, aby 1/2 i 2/4 były traktowane tak samo, czy nie - ma to konsekwencje dla metod equals (), hashCode () i compareTo ().

5

Będę musiał zamówić je od najmniejszego do największego, więc ostatecznie będę musiał przedstawić je również jako podwójne

Nie jest to bezwzględnie konieczne. (W rzeczywistości, jeśli chcesz poprawnie obsługiwać równość, nie polegaj na double, aby działać poprawnie.) Jeśli b * d jest dodatnie, a / b <c / d if ad <bc. Jeśli w grę wchodzą ujemne liczby całkowite, można to odpowiednio obsłużyć ...

Mógłbym przepisać jako:

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

Zastosowanie longtutaj ma na celu zapewnienie, że nie ma przepełnienia, jeśli pomnożymy dwa dużeint s. uchwyt Jeśli możesz zagwarantować, że mianownik jest zawsze nieujemny (jeśli jest ujemny, po prostu zaneguj zarówno licznik, jak i mianownik), możesz pozbyć się konieczności sprawdzania, czy b * d jest dodatnie, i zapisać kilka kroków. Nie jestem pewien, jakiego zachowania szukasz przy zerowym mianowniku.

Nie wiem, jak wypada wydajność w porównaniu z używaniem podwójnych do porównania. (to znaczy, jeśli tak bardzo zależy ci na wydajności) Oto metoda testowa, którą sprawdzałem. (Wydaje się, że działa poprawnie.)

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 możesz rozważyć restrukturyzację do wdrożenia Comparablelub Comparatordla swojej klasy).


Nie jest to prawdą, jeśli na przykład a = 1, b = 3, c = -2, d = -3. Jeśli b i d są dodatnie, to prawdą jest, że a / b <c / d wtedy i tylko wtedy, gdy ad <bc.
Luke Woodward

Argh, źle zrozumiałem kwalifikacje. (dzięki!) Warunek powinien być taki, jeśli bd> 0.
Jason S

Prawdziwe. Dokładniej, a / b <c / d <=> ac <bd jest prawdziwe, pod warunkiem, że bd> 0. Jeśli bd <0, odwrotność jest prawdą. (Jeśli bd = 0, to masz ułamek bum. :-))
Paul Brinkley

Blisko. masz na myśli a / b <c / d <=> ad <bc for bd> 0. (Udało mi się to za pierwszym razem w komentarzach do kodu!)
Jason S

4

Jednym z bardzo drobnych ulepszeń może być potencjalnie zapisanie podwójnej wartości, którą obliczasz, tak aby była obliczana tylko przy pierwszym dostępie. Nie będzie to duża wygrana, chyba że często uzyskujesz dostęp do tej liczby, ale nie jest to też zbyt trudne.

Dodatkową kwestią może być błąd sprawdzania, który wykonujesz w mianowniku ... automatycznie zmieniasz 0 na 1. Nie jestem pewien, czy to jest poprawne dla twojej konkretnej aplikacji, ale ogólnie jeśli ktoś próbuje podzielić przez 0, coś jest bardzo nie tak . Pozwoliłbym temu zgłosić wyjątek (wyspecjalizowany wyjątek, jeśli uważasz, że jest potrzebny), zamiast zmieniać wartość w pozornie arbitralny sposób, który nie jest znany użytkownikowi.

W przeciwieństwie do kilku innych komentarzy, o dodawaniu metod dodawania odejmowania itp. ... ponieważ nie wspomniałeś o ich potrzebie, zakładam, że nie. I jeśli nie budujesz biblioteki, która naprawdę będzie używana w wielu miejscach lub przez innych ludzi, idź z YAGNI (nie będziesz jej potrzebować, więc nie powinno jej tam być).


Fakt, że ma getNumerator () i getDenominator (), prowadzi mnie do przekonania, że ​​tworzył nowe ułamki POZA tą klasą. Ta logika prawdopodobnie należy tutaj, jeśli istnieje.
Outlaw Programmer

+1 Cicha zmiana z 0 na 1 w mianowniku to przepis na katastrofę.
maaartinus

4

Istnieje kilka sposobów na ulepszenie tego lub dowolnego typu wartości:

  • Uczyń swoją klasę niezmienną , włączając w to ostateczne oznaczenie licznika i mianownika
  • Automatycznie konwertuj ułamki na postać kanoniczną , np. 2/4 -> 1/2
  • Zaimplementuj toString ()
  • Zaimplementuj „public static Fraction valueOf (String s)”, aby konwertować ciągi na ułamki. Zaimplementuj podobne metody fabryczne do konwersji z int, double itp.
  • Wdrażaj dodawanie, mnożenie itp
  • Dodaj konstruktora z liczb całkowitych
  • Zastąp równa się / hashCode
  • Rozważ utworzenie interfejsu Fraction z implementacją, która w razie potrzeby przełączy się na BigInteger
  • Rozważ podklasyfikację Number
  • Rozważ uwzględnienie nazwanych stałych dla typowych wartości, takich jak 0 i 1
  • Rozważ możliwość serializacji
  • Sprawdź dzielenie przez zero
  • Udokumentuj swój interfejs API

Zasadniczo spójrz na API dla innych klas wartości, takich jak Double , Integer i rób to, co robią :)


3

Jeśli pomnożymy licznik i mianownik jednej frakcji przez mianownik drugiej i na odwrót, otrzymamy dwa ułamki (które nadal są tymi samymi wartościami) o tym samym mianowniku i można bezpośrednio porównać liczniki. Dlatego nie musisz obliczać podwójnej wartości:

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

To kończy się niepowodzeniem, jeśli frac.getDenominator () i this.denominator mają przeciwne znaki. (patrz mój post.) Trzeba też uważać na to, że mnożenie może się przepełnić.
Jason S

Ach tak, to prawda. Ale w takim przypadku wolę implementację Kipa, co przynajmniej mogę zrozumieć. ;)
Francisco Canedo

Zwracam uwagę, że w mojej implementacji tylko licznik może być ujemny. Używam też BigIntegers, więc nigdy nie będzie przepełnienia (oczywiście kosztem wydajności).
Kip

2

jak ulepszyłbym ten kod:

  1. konstruktor oparty na String Fraction (String s) // oczekuj "liczba / liczba"
  2. konstruktor kopiujący Fraction (Fraction copy)
  3. zastąpić metodę clone
  4. implementuje metody equals, toString i hashcode
  5. implementuje interfejs java.io.Serializable, Comparable
  6. metoda „double getDoubleValue ()”
  7. metoda add / divide / etc ...
  8. Uczyniłbym tę klasę niezmienną (bez ustawiaczy)

Całkiem niezła lista. Prawdopodobnie nie ma potrzeby klonowania / serializacji, ale wszystko inne jest rozsądne.
Outlaw Programmer

@OutlawProgrammer: Tak, 8 lub 3. Cloneable immutable nie ma sensu.
maaartinus

2

Masz już funkcję compareTo ... Zaimplementowałbym interfejs Comparable.

Może nie mieć jednak znaczenia, niezależnie od tego, co z nim zrobisz.



2

W szczególności : czy istnieje lepszy sposób radzenia sobie z przekazywaniem zerowego mianownika? Ustawienie mianownika na 1 wydaje się bardzo arbitralne. Jak mogę to zrobić dobrze?

Powiedziałbym rzucić wyjątek ArithmeticException do dzielenia przez zero, ponieważ tak naprawdę się dzieje:

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

Zamiast „Podziel przez zero”, możesz chcieć, aby komunikat brzmiał „Podziel przez zero: mianownikiem ułamka jest zero”.


1

Po utworzeniu obiektu ułamkowego, dlaczego miałbyś chcieć pozwolić innym obiektom ustawić licznik lub mianownik? Myślę, że powinny one być tylko do odczytu. Czyni obiekt niezmiennym ...

Poza tym ... ustawienie mianownika na zero powinno spowodować zgłoszenie wyjątku nieprawidłowego argumentu (nie wiem, co to jest w Javie)


Lub wrzuć nowy wyjątek ArithmeticException („Podziel przez zero.”)
Kip

1

Timothy Budd ma świetną implementację klasy Rational w swoich „Strukturach danych w C ++”. Oczywiście inny język, ale bardzo ładnie przenosi się na Javę.

Poleciłbym więcej konstruktorów. Domyślny konstruktor miałby licznik 0, mianownik 1. Konstruktor z pojedynczym argumentem zakładałby mianownik równy 1. Pomyśl, jak użytkownicy mogliby używać tej klasy.

Brak czeku na zerowy mianownik? Programowanie na podstawie umowy wymagałoby dodania go.


1

Podam trzecią, piątą lub jakąkolwiek rekomendację, aby uczynić twoją część niezmienną. Poleciłbym również, abyś rozszerzył klasę Number . Prawdopodobnie spojrzałbym na Double klasę , ponieważ prawdopodobnie będziesz chciał zaimplementować wiele takich samych metod.

Prawdopodobnie powinieneś również zaimplementować Comparable i Serializable, ponieważ takie zachowanie będzie prawdopodobnie oczekiwane. Dlatego będziesz musiał zaimplementować funkcję compareTo (). Będziesz także musiał zastąpić equals () i nie mogę wystarczająco mocno podkreślić, że możesz również zastąpić hashCode (). Może to być jednak jeden z nielicznych przypadków, w których nie chcesz, aby metody compareTo () i equals () były spójne, ponieważ ułamki, które można redukować, niekoniecznie są równe.


1

Praktyką sprzątania, którą lubię, jest tylko jeden powrót.

 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

Oczyściłem odpowiedź Cletusa :

  • Dodano Javadoc dla wszystkich metod.
  • Dodano sprawdzanie warunków wstępnych metod.
  • Zastąpiony w zwyczaju analizowania valueOf(String)z BigInteger(String)którego jest jednocześnie bardziej elastyczne i szybciej.
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

Uwaga wstępna:

Nigdy tego nie pisz:

if ( condition ) statement;

To jest dużo lepsze

if ( condition ) { statement };

Po prostu stwórz, aby stworzyć dobry nawyk.

Uczyniając klasę niezmienną zgodnie z sugestią, możesz również skorzystać z double do wykonywania operacji equals, hashCode i compareTo

Oto moja szybka, brudna wersja:

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

}

Jeśli chodzi o metodę fabryki statycznej, może być przydatna później, jeśli podklasujesz ułamek w celu obsługi bardziej złożonych rzeczy lub jeśli zdecydujesz się użyć puli dla najczęściej używanych obiektów.

Może tak nie jest, po prostu chciałem to podkreślić. :)

Zobacz Efektywna pierwsza pozycja w Javie .


0

Może przydać się dodanie prostych rzeczy, takich jak odwzajemnienie, uzyskanie reszty i uzyskanie całości.


ta odpowiedź nadaje się jako komentarz.
Jasonw

Strasznie przepraszam za spóźnioną odpowiedź, ale uważam, że jest minimalna liczba przedstawicieli (50?) Potrzebna do skomentowania odpowiedzi, której nie mam ...
Darth Joshua

0

Nawet jeśli masz metody compareTo (), jeśli chcesz korzystać z narzędzi takich jak Collections.sort (), powinieneś również zaimplementować Comparable.

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

Ponadto, aby uzyskać ładny wyświetlacz, zalecam zastąpienie toString ()

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

Na koniec upubliczniłbym klasę, abyś mógł jej używać z różnych pakietów.


0

Ta funkcja upraszcza korzystanie z algorytmu eukledowskiego, jest bardzo przydatna podczas definiowania ułamków

 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

W przypadku implementacji frakcji / racjonalnej klasy przemysłowej zaimplementowałbym ją tak, aby mogła reprezentować NaN, dodatnią nieskończoność, ujemną nieskończoność i opcjonalnie ujemne zero z semantyką operacyjną dokładnie taką samą, jak standardowe stany IEEE 754 dla arytmetyki zmiennoprzecinkowej (ułatwia to również konwersja na / z wartości zmiennoprzecinkowych). Ponadto, ponieważ porównanie do zera, jedynki i powyższych wartości specjalnych wymaga tylko prostego, ale połączonego porównania licznika i mianownika z 0 i 1 - dodałbym kilka metod isXXX i porównajToXXX dla łatwości użycia (np. Eq0 () użyj licznika == 0 && denominator! = 0 za kulisami, zamiast pozwalać klientowi na porównanie z instancją o wartości zerowej). Przydatne są również niektóre predefiniowane wartości statyczne (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN itp.), ponieważ pojawiają się w kilku miejscach jako wartości stałe. To najlepszy sposób IMHO.


0

Frakcja klasowa:

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

Program główny:

    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);
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.