ProperFractionFormat.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.text.FieldPosition;
  23. import java.text.NumberFormat;
  24. import java.text.ParsePosition;

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

  28. /**
  29.  * Formats a Fraction number in proper format.  The number format for each of
  30.  * 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 ProperFractionFormat extends FractionFormat {

  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 ProperFractionFormat() {
  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 ProperFractionFormat(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 ProperFractionFormat(NumberFormat wholeFormat,
  66.                                 NumberFormat numeratorFormat,
  67.                                 NumberFormat denominatorFormat) {
  68.         super(numeratorFormat, denominatorFormat);

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

  72.     /**
  73.      * Formats a {@link Fraction} object to produce a string.  The fraction
  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(Fraction fraction,
  84.                                StringBuffer toAppendTo,
  85.                                FieldPosition pos) {

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

  88.         int num = fraction.getNumerator();
  89.         int den = fraction.getDenominator();
  90.         int whole = num / den;
  91.         num %= den;

  92.         if (whole != 0) {
  93.             getWholeFormat().format(whole, toAppendTo, pos);
  94.             toAppendTo.append(' ');
  95.             num = FastMath.abs(num);
  96.         }
  97.         getNumeratorFormat().format(num, toAppendTo, pos);
  98.         toAppendTo.append(" / ");
  99.         getDenominatorFormat().format(den, toAppendTo, pos);

  100.         return toAppendTo;
  101.     }

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

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

  128.         int initialIndex = pos.getIndex();

  129.         // parse whitespace
  130.         parseAndIgnoreWhitespace(source, pos);

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

  140.         // parse whitespace
  141.         parseAndIgnoreWhitespace(source, pos);

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

  151.         if (num.intValue() < 0) {
  152.             // minus signs should be leading, invalid expression
  153.             pos.setIndex(initialIndex);
  154.             return null;
  155.         }

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

  175.         // parse whitespace
  176.         parseAndIgnoreWhitespace(source, pos);

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

  186.         if (den.intValue() < 0) {
  187.             // minus signs must be leading, invalid
  188.             pos.setIndex(initialIndex);
  189.             return null;
  190.         }

  191.         int w = whole.intValue();
  192.         int n = num.intValue();
  193.         int d = den.intValue();
  194.         return new Fraction(((FastMath.abs(w) * d) + n) * MathUtils.copySign(1, w), d);
  195.     }

  196. }