ProperBigFractionFormat.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.fraction;

  22. import java.math.BigInteger;
  23. import java.text.FieldPosition;
  24. import java.text.NumberFormat;
  25. import java.text.ParsePosition;

  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.util.MathUtils;

  28. /**
  29.  * Formats a BigFraction number in proper format. The number format
  30.  * for each of the whole number, numerator and, denominator can be configured.
  31.  * <p>
  32.  * Minus signs are only allowed in the whole number part - i.e.,
  33.  * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
  34.  * will result in a <code>ParseException</code>.
  35.  */
  36. public class ProperBigFractionFormat extends BigFractionFormat {

  37.     /** Serializable version identifier */
  38.     private static final long serialVersionUID = 20160323L;

  39.     /** The format used for the whole number. */
  40.     private final NumberFormat wholeFormat;

  41.     /**
  42.      * Create a proper formatting instance with the default number format for
  43.      * the whole, numerator, and denominator.
  44.      */
  45.     public ProperBigFractionFormat() {
  46.         this(getDefaultNumberFormat());
  47.     }

  48.     /**
  49.      * Create a proper formatting instance with a custom number format for the
  50.      * whole, numerator, and denominator.
  51.      * @param format the custom format for the whole, numerator, and denominator.
  52.      * @throws org.hipparchus.exception.NullArgumentException if the provided format is null.
  53.      */
  54.     public ProperBigFractionFormat(final NumberFormat format) {
  55.         this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone());
  56.     }

  57.     /**
  58.      * Create a proper formatting instance with a custom number format for each
  59.      * of the whole, numerator, and denominator.
  60.      * @param wholeFormat the custom format for the whole.
  61.      * @param numeratorFormat the custom format for the numerator.
  62.      * @param denominatorFormat the custom format for the denominator.
  63.      * @throws org.hipparchus.exception.NullArgumentException if either provided format is null.
  64.      */
  65.     public ProperBigFractionFormat(final NumberFormat wholeFormat,
  66.                                    final NumberFormat numeratorFormat,
  67.                                    final NumberFormat denominatorFormat) {
  68.         super(numeratorFormat, denominatorFormat);

  69.         MathUtils.checkNotNull(wholeFormat, LocalizedCoreFormats.WHOLE_FORMAT);
  70.         this.wholeFormat = wholeFormat;
  71.     }

  72.     /**
  73.      * Formats a {@link BigFraction} object to produce a string.  The BigFraction
  74.      * is output in proper format.
  75.      *
  76.      * @param fraction the object to format.
  77.      * @param toAppendTo where the text is to be appended
  78.      * @param pos On input: an alignment field, if desired. On output: the
  79.      * offsets of the alignment field
  80.      * @return the value passed in as toAppendTo.
  81.      */
  82.     @Override
  83.     public StringBuffer format(final BigFraction fraction,
  84.                                final StringBuffer toAppendTo,
  85.                                final FieldPosition pos) {

  86.         pos.setBeginIndex(0);
  87.         pos.setEndIndex(0);

  88.         BigInteger num = fraction.getNumerator();
  89.         BigInteger den = fraction.getDenominator();
  90.         BigInteger whole = num.divide(den);
  91.         num = num.remainder(den);

  92.         if (!BigInteger.ZERO.equals(whole)) {
  93.             getWholeFormat().format(whole, toAppendTo, pos);
  94.             toAppendTo.append(' ');
  95.             if (num.compareTo(BigInteger.ZERO) < 0) {
  96.                 num = num.negate();
  97.             }
  98.         }
  99.         getNumeratorFormat().format(num, toAppendTo, pos);
  100.         toAppendTo.append(" / ");
  101.         getDenominatorFormat().format(den, toAppendTo, pos);

  102.         return toAppendTo;
  103.     }

  104.     /**
  105.      * Access the whole format.
  106.      * @return the whole format.
  107.      */
  108.     public NumberFormat getWholeFormat() {
  109.         return wholeFormat;
  110.     }

  111.     /**
  112.      * Parses a string to produce a {@link BigFraction} object.  This method
  113.      * expects the string to be formatted as a proper BigFraction.
  114.      * <p>
  115.      * Minus signs are only allowed in the whole number part - i.e.,
  116.      * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
  117.      * will result in a <code>ParseException</code>.</p>
  118.      *
  119.      * @param source the string to parse
  120.      * @param pos input/ouput parsing parameter.
  121.      * @return the parsed {@link BigFraction} object.
  122.      */
  123.     @Override
  124.     public BigFraction parse(final String source, final ParsePosition pos) {
  125.         // try to parse improper BigFraction
  126.         BigFraction ret = super.parse(source, pos);
  127.         if (ret != null) {
  128.             return ret;
  129.         }

  130.         final int initialIndex = pos.getIndex();

  131.         // parse whitespace
  132.         parseAndIgnoreWhitespace(source, pos);

  133.         // parse whole
  134.         BigInteger whole = parseNextBigInteger(source, pos);
  135.         if (whole == null) {
  136.             // invalid integer number
  137.             // set index back to initial, error index should already be set
  138.             // character examined.
  139.             pos.setIndex(initialIndex);
  140.             return null;
  141.         }

  142.         // parse whitespace
  143.         parseAndIgnoreWhitespace(source, pos);

  144.         // parse numerator
  145.         BigInteger num = parseNextBigInteger(source, pos);
  146.         if (num == null) {
  147.             // invalid integer number
  148.             // set index back to initial, error index should already be set
  149.             // character examined.
  150.             pos.setIndex(initialIndex);
  151.             return null;
  152.         }

  153.         if (num.compareTo(BigInteger.ZERO) < 0) {
  154.             // minus signs should be leading, invalid expression
  155.             pos.setIndex(initialIndex);
  156.             return null;
  157.         }

  158.         // parse '/'
  159.         final int startIndex = pos.getIndex();
  160.         final char c = parseNextCharacter(source, pos);
  161.         switch (c) {
  162.         case 0 :
  163.             // no '/'
  164.             // return num as a BigFraction
  165.             return new BigFraction(num);
  166.         case '/' :
  167.             // found '/', continue parsing denominator
  168.             break;
  169.         default :
  170.             // invalid '/'
  171.             // set index back to initial, error index should be the last
  172.             // character examined.
  173.             pos.setIndex(initialIndex);
  174.             pos.setErrorIndex(startIndex);
  175.             return null;
  176.         }

  177.         // parse whitespace
  178.         parseAndIgnoreWhitespace(source, pos);

  179.         // parse denominator
  180.         final BigInteger den = parseNextBigInteger(source, pos);
  181.         if (den == null) {
  182.             // invalid integer number
  183.             // set index back to initial, error index should already be set
  184.             // character examined.
  185.             pos.setIndex(initialIndex);
  186.             return null;
  187.         }

  188.         if (den.compareTo(BigInteger.ZERO) < 0) {
  189.             // minus signs must be leading, invalid
  190.             pos.setIndex(initialIndex);
  191.             return null;
  192.         }

  193.         boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0;
  194.         if (wholeIsNeg) {
  195.             whole = whole.negate();
  196.         }
  197.         num = whole.multiply(den).add(num);
  198.         if (wholeIsNeg) {
  199.             num = num.negate();
  200.         }

  201.         return new BigFraction(num, den);

  202.     }
  203. }