diff --git a/src/java/org/apache/commons/math/MessagesResources_fr.java b/src/java/org/apache/commons/math/MessagesResources_fr.java index 41ef83d63..640c81e21 100644 --- a/src/java/org/apache/commons/math/MessagesResources_fr.java +++ b/src/java/org/apache/commons/math/MessagesResources_fr.java @@ -101,6 +101,28 @@ public class MessagesResources_fr { "cannot convert infinite value", "les valeurs infinies ne peuvent \u00eatre converties" }, + // org.apache.commons.math.fraction.AbstractFormat + { "denominator format can not be null", + "le format du d\u00e9nominateur ne doit pas \u00eatre nul" }, + { "numerator format can not be null", + "le format du num\u00e9rateur ne doit pas \u00eatre nul" }, + + // org.apache.commons.math.fraction.FractionFormat + { "cannot convert given object to a fraction number: {0}", + "impossible de convertir l''objet sous forme d''un nombre rationnel : {0}" }, + + // org.apache.commons.math.fraction.FractionFormat + // org.apache.commons.math.fraction.BigFractionFormat + { "unparseable fraction number: \"{0}\"", + "\u00e9chec d''analyse du nombre rationnel \"{0}\"" }, + { "cannot format given object as a fraction number", + "impossible de formater l''objet sous forme d''un nombre rationnel" }, + + // org.apache.commons.math.fraction.ProperFractionFormat + // org.apache.commons.math.fraction.ProperBigFractionFormat + { "whole format can not be null", + "le format complet ne doit pas \u00eatre nul" }, + // org.apache.commons.math.analysis.solvers.UnivariateRealSolverUtils { "Number of iterations={0}, maximum iterations={1}, initial={2}, lower bound={3}, upper bound={4}," + " final a value={5}, final b value={6}, f(a)={7}, f(b)={8}", @@ -315,10 +337,6 @@ public class MessagesResources_fr { "unparseable complex number: \"{0}\"", "\u00e9chec d''analyse du nombre complexe \"{0}\"" }, - // org.apache.commons.math.fraction.FractionFormat - { "unparseable fraction number: \"{0}\"", - "\u00e9chec d''analyse du nombre rationnel \"{0}\"" }, - // org.apache.commons.math.geometry.Vector3DFormat { "unparseable 3D vector: \"{0}\"", "\u00e9chec d''analyse du vecteur de dimension 3 \"{0}\"" }, diff --git a/src/java/org/apache/commons/math/fraction/AbstractFormat.java b/src/java/org/apache/commons/math/fraction/AbstractFormat.java new file mode 100644 index 000000000..aa3c4104d --- /dev/null +++ b/src/java/org/apache/commons/math/fraction/AbstractFormat.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math.fraction; + +import java.io.Serializable; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +import org.apache.commons.math.MathRuntimeException; + +public abstract class AbstractFormat extends NumberFormat implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -6981118387974191891L; + + /** The format used for the denominator. */ + protected NumberFormat denominatorFormat; + + /** The format used for the numerator. */ + protected NumberFormat numeratorFormat; + + /** + * Create an improper formatting instance with the default number format + * for the numerator and denominator. + */ + protected AbstractFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create an improper formatting instance with a custom number format for + * both the numerator and denominator. + * @param format the custom format for both the numerator and denominator. + */ + protected AbstractFormat(final NumberFormat format) { + this(format, (NumberFormat) format.clone()); + } + + /** + * Create an improper formatting instance with a custom number format for + * the numerator and a custom number format for the denominator. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + protected AbstractFormat(final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + this.numeratorFormat = numeratorFormat; + this.denominatorFormat = denominatorFormat; + } + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only + * customizing is the maximum number of BigFraction digits, which is set to 0. + * @return the default number format. + */ + protected static NumberFormat getDefaultNumberFormat() { + return getDefaultNumberFormat(Locale.getDefault()); + } + + /** + * Create a default number format. The default number format is based on + * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only + * customizing is the maximum number of BigFraction digits, which is set to 0. + * @param locale the specific locale used by the format. + * @return the default number format specific to the given locale. + */ + protected static NumberFormat getDefaultNumberFormat(final Locale locale) { + final NumberFormat nf = NumberFormat.getNumberInstance(locale); + nf.setMaximumFractionDigits(0); + nf.setParseIntegerOnly(true); + return nf; + } + + /** + * Access the denominator format. + * @return the denominator format. + */ + public NumberFormat getDenominatorFormat() { + return denominatorFormat; + } + + /** + * Access the numerator format. + * @return the numerator format. + */ + public NumberFormat getNumeratorFormat() { + return numeratorFormat; + } + + /** + * Modify the denominator format. + * @param format the new denominator format value. + * @throws IllegalArgumentException if format is + * null. + */ + public void setDenominatorFormat(final NumberFormat format) { + if (format == null) { + throw MathRuntimeException.createIllegalArgumentException( + "denominator format can not be null"); + } + this.denominatorFormat = format; + } + + /** + * Modify the numerator format. + * @param format the new numerator format value. + * @throws IllegalArgumentException if format is + * null. + */ + public void setNumeratorFormat(final NumberFormat format) { + if (format == null) { + throw MathRuntimeException.createIllegalArgumentException( + "numerator format can not be null"); + } + this.numeratorFormat = format; + } + + /** + * Parses source until a non-whitespace character is found. + * @param source the string to parse + * @param pos input/ouput parsing parameter. On output, pos + * holds the index of the next non-whitespace character. + */ + protected static void parseAndIgnoreWhitespace(final String source, + final ParsePosition pos) { + parseNextCharacter(source, pos); + pos.setIndex(pos.getIndex() - 1); + } + + /** + * Parses source until a non-whitespace character is found. + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the first non-whitespace character. + */ + protected static char parseNextCharacter(final String source, + final ParsePosition pos) { + int index = pos.getIndex(); + final int n = source.length(); + char ret = 0; + + if (index < n) { + char c; + do { + c = source.charAt(index++); + } while (Character.isWhitespace(c) && index < n); + pos.setIndex(index); + + if (index < n) { + ret = c; + } + } + + return ret; + } + + /** + * Formats a double value as a fraction and appends the result to a StringBuffer. + * + * @param value the double value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return a reference to the appended buffer + * @see {@link #format(Object, StringBuffer, FieldPosition)} + */ + public StringBuffer format(final double value, + final StringBuffer buffer, final FieldPosition position) { + return format(Double.valueOf(value), buffer, position); + } + + + /** + * Formats a long value as a fraction and appends the result to a StringBuffer. + * + * @param value the long value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return a reference to the appended buffer + * @see {@link #format(Object, StringBuffer, FieldPosition)} + */ + public StringBuffer format(final long value, + final StringBuffer buffer, final FieldPosition position) { + return format(Long.valueOf(value), buffer, position); + } + +} \ No newline at end of file diff --git a/src/java/org/apache/commons/math/fraction/BigFractionFormat.java b/src/java/org/apache/commons/math/fraction/BigFractionFormat.java new file mode 100644 index 000000000..3aa586981 --- /dev/null +++ b/src/java/org/apache/commons/math/fraction/BigFractionFormat.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math.fraction; + +import java.io.Serializable; +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import org.apache.commons.math.MathRuntimeException; + +/** + * Formats a BigFraction number in proper format or improper format. + *

+ * The number format for each of the whole number, numerator and, + * denominator can be configured. + *

+ * + * @since 2.0 + * @version $Revision$ $Date$ + */ +public class BigFractionFormat extends AbstractFormat implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -2932167925527338976L; + + /** + * Create an improper formatting instance with the default number format + * for the numerator and denominator. + */ + public BigFractionFormat() { + } + + /** + * Create an improper formatting instance with a custom number format for + * both the numerator and denominator. + * @param format the custom format for both the numerator and denominator. + */ + public BigFractionFormat(final NumberFormat format) { + super(format); + } + + /** + * Create an improper formatting instance with a custom number format for + * the numerator and a custom number format for the denominator. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public BigFractionFormat(final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + } + + /** + * Get the set of locales for which complex formats are available. This + * is the same set as the {@link NumberFormat} set. + * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * This static method calls formatBigFraction() on a default instance of + * BigFractionFormat. + * + * @param f BigFraction object to format + * @return A formatted BigFraction in proper form. + */ + public static String formatBigFraction(final BigFraction f) { + return getImproperInstance().format(f); + } + + /** + * Returns the default complex format for the current locale. + * @return the default complex format. + */ + public static BigFractionFormat getImproperInstance() { + return getImproperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getImproperInstance(final Locale locale) { + return new BigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Returns the default complex format for the current locale. + * @return the default complex format. + */ + public static BigFractionFormat getProperInstance() { + return getProperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getProperInstance(final Locale locale) { + return new ProperBigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction is + * output in improper format. + * + * @param BigFraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(final BigFraction BigFraction, + final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + getNumeratorFormat().format(BigFraction.getNumerator(), toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(BigFraction.getDenominator(), toAppendTo, pos); + + return toAppendTo; + } + + /** + * Formats an object and appends the result to a StringBuffer. + * obj must be either a {@link BigFraction} object or a + * {@link BigInteger} object or a {@link Number} object. Any other type of + * object will result in an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) + * @throws IllegalArgumentException is obj is not a valid type. + */ + public StringBuffer format(final Object obj, + final StringBuffer toAppendTo, final FieldPosition pos) { + + final StringBuffer ret; + if (obj instanceof BigFraction) { + ret = format((BigFraction) obj, toAppendTo, pos); + } else if (obj instanceof BigInteger) { + ret = format(new BigFraction((BigInteger) obj), toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new BigFraction(((Number) obj).doubleValue()), + toAppendTo, pos); + } else { + throw MathRuntimeException.createIllegalArgumentException( + "cannot format given object as a fraction number"); + } + + return ret; + } + + /** + * Parses a string to produce a {@link BigFraction} object. + * @param source the string to parse + * @return the parsed {@link BigFraction} object. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public BigFraction parse(final String source) throws ParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final BigFraction result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw MathRuntimeException.createParseException( + parsePosition.getErrorIndex(), + "unparseable fraction number: \"{0}\"", source); + } + return result; + } + + /** + * Parses a string to produce a {@link BigFraction} object. + * This method expects the string to be formatted as an improper BigFraction. + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link BigFraction} object. + */ + public BigFraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + final BigInteger num = parseNextBigInteger(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0 : + // no '/' + // return num as a BigFraction + return new BigFraction(num); + case '/' : + // found '/', continue parsing denominator + break; + default : + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final BigInteger den = parseNextBigInteger(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + return new BigFraction(num, den); + } + + /** + * Parses a string to produce a BigInteger. + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return a parsed BigInteger or null if string does not + * contain a BigInteger at the specified position + */ + protected BigInteger parseNextBigInteger(final String source, + final ParsePosition pos) { + + final int start = pos.getIndex(); + int end = (source.charAt(start) == '-') ? (start + 1) : start; + while((end < source.length()) && + Character.isDigit(source.charAt(end))) { + ++end; + } + + try { + BigInteger n = new BigInteger(source.substring(start, end)); + pos.setIndex(end); + return n; + } catch (NumberFormatException nfe) { + pos.setErrorIndex(start); + return null; + } + + } + +} diff --git a/src/java/org/apache/commons/math/fraction/FractionFormat.java b/src/java/org/apache/commons/math/fraction/FractionFormat.java index cf25b03d4..78d93053d 100644 --- a/src/java/org/apache/commons/math/fraction/FractionFormat.java +++ b/src/java/org/apache/commons/math/fraction/FractionFormat.java @@ -17,7 +17,6 @@ package org.apache.commons.math.fraction; -import java.io.Serializable; import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParseException; @@ -35,23 +34,16 @@ import org.apache.commons.math.MathRuntimeException; * @since 1.1 * @version $Revision$ $Date$ */ -public class FractionFormat extends NumberFormat implements Serializable { +public class FractionFormat extends AbstractFormat { /** Serializable version identifier */ - private static final long serialVersionUID = -6337346779577272306L; + private static final long serialVersionUID = 3008655719530972611L; - /** The format used for the denominator. */ - private NumberFormat denominatorFormat; - - /** The format used for the numerator. */ - private NumberFormat numeratorFormat; - /** * Create an improper formatting instance with the default number format * for the numerator and denominator. */ public FractionFormat() { - this(getDefaultNumberFormat()); } /** @@ -59,8 +51,8 @@ public class FractionFormat extends NumberFormat implements Serializable { * both the numerator and denominator. * @param format the custom format for both the numerator and denominator. */ - public FractionFormat(NumberFormat format) { - this(format, (NumberFormat)format.clone()); + public FractionFormat(final NumberFormat format) { + super(format); } /** @@ -69,12 +61,18 @@ public class FractionFormat extends NumberFormat implements Serializable { * @param numeratorFormat the custom format for the numerator. * @param denominatorFormat the custom format for the denominator. */ - public FractionFormat(NumberFormat numeratorFormat, - NumberFormat denominatorFormat) - { - super(); - this.numeratorFormat = numeratorFormat; - this.denominatorFormat = denominatorFormat; + public FractionFormat(final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + } + + /** + * Get the set of locales for which complex formats are available. This + * is the same set as the {@link NumberFormat} set. + * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); } /** @@ -88,15 +86,6 @@ public class FractionFormat extends NumberFormat implements Serializable { return getImproperInstance().format(f); } - /** - * Get the set of locales for which complex formats are available. This - * is the same set as the {@link NumberFormat} set. - * @return available complex format locales. - */ - public static Locale[] getAvailableLocales() { - return NumberFormat.getAvailableLocales(); - } - /** * Returns the default complex format for the current locale. * @return the default complex format. @@ -110,9 +99,8 @@ public class FractionFormat extends NumberFormat implements Serializable { * @param locale the specific locale used by the format. * @return the complex format specific to the given locale. */ - public static FractionFormat getImproperInstance(Locale locale) { - NumberFormat f = getDefaultNumberFormat(locale); - return new FractionFormat(f); + public static FractionFormat getImproperInstance(final Locale locale) { + return new FractionFormat(getDefaultNumberFormat(locale)); } /** @@ -128,9 +116,8 @@ public class FractionFormat extends NumberFormat implements Serializable { * @param locale the specific locale used by the format. * @return the complex format specific to the given locale. */ - public static FractionFormat getProperInstance(Locale locale) { - NumberFormat f = getDefaultNumberFormat(locale); - return new ProperFractionFormat(f); + public static FractionFormat getProperInstance(final Locale locale) { + return new ProperFractionFormat(getDefaultNumberFormat(locale)); } /** @@ -143,20 +130,6 @@ public class FractionFormat extends NumberFormat implements Serializable { return getDefaultNumberFormat(Locale.getDefault()); } - /** - * Create a default number format. The default number format is based on - * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only - * customizing is the maximum number of fraction digits, which is set to 0. - * @param locale the specific locale used by the format. - * @return the default number format specific to the given locale. - */ - private static NumberFormat getDefaultNumberFormat(Locale locale) { - NumberFormat nf = NumberFormat.getNumberInstance(locale); - nf.setMaximumFractionDigits(0); - nf.setParseIntegerOnly(true); - return nf; - } - /** * Formats a {@link Fraction} object to produce a string. The fraction is * output in improper format. @@ -167,8 +140,8 @@ public class FractionFormat extends NumberFormat implements Serializable { * offsets of the alignment field * @return the value passed in as toAppendTo. */ - public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, - FieldPosition pos) { + public StringBuffer format(final Fraction fraction, + final StringBuffer toAppendTo, final FieldPosition pos) { pos.setBeginIndex(0); pos.setEndIndex(0); @@ -194,45 +167,29 @@ public class FractionFormat extends NumberFormat implements Serializable { * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) * @throws IllegalArgumentException is obj is not a valid type. */ - public StringBuffer format(Object obj, StringBuffer toAppendTo, - FieldPosition pos) - { + public StringBuffer format(final Object obj, + final StringBuffer toAppendTo, final FieldPosition pos) { StringBuffer ret = null; if (obj instanceof Fraction) { - ret = format( (Fraction)obj, toAppendTo, pos); + ret = format((Fraction) obj, toAppendTo, pos); } else if (obj instanceof Number) { try { - ret = format( new Fraction(((Number)obj).doubleValue()), - toAppendTo, pos); + ret = format(new Fraction(((Number) obj).doubleValue()), + toAppendTo, pos); } catch (ConvergenceException ex) { - throw new IllegalArgumentException( - "Cannot convert given object to a fraction."); + throw MathRuntimeException.createIllegalArgumentException( + "cannot convert given object to a fraction number: {0}", + ex.getLocalizedMessage()); } } else { - throw new IllegalArgumentException( - "Cannot format given object as a fraction"); + throw MathRuntimeException.createIllegalArgumentException( + "cannot format given object as a fraction number"); } return ret; } - /** - * Access the denominator format. - * @return the denominator format. - */ - public NumberFormat getDenominatorFormat() { - return denominatorFormat; - } - - /** - * Access the numerator format. - * @return the numerator format. - */ - public NumberFormat getNumeratorFormat() { - return numeratorFormat; - } - /** * Parses a string to produce a {@link Fraction} object. * @param source the string to parse @@ -240,9 +197,9 @@ public class FractionFormat extends NumberFormat implements Serializable { * @exception ParseException if the beginning of the specified string * cannot be parsed. */ - public Fraction parse(String source) throws ParseException { - ParsePosition parsePosition = new ParsePosition(0); - Fraction result = parse(source, parsePosition); + public Fraction parse(final String source) throws ParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final Fraction result = parse(source, parsePosition); if (parsePosition.getIndex() == 0) { throw MathRuntimeException.createParseException( parsePosition.getErrorIndex(), @@ -258,14 +215,14 @@ public class FractionFormat extends NumberFormat implements Serializable { * @param pos input/ouput parsing parameter. * @return the parsed {@link Fraction} object. */ - public Fraction parse(String source, ParsePosition pos) { - int initialIndex = pos.getIndex(); + public Fraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); // parse whitespace parseAndIgnoreWhitespace(source, pos); // parse numerator - Number num = getNumeratorFormat().parse(source, pos); + final Number num = getNumeratorFormat().parse(source, pos); if (num == null) { // invalid integer number // set index back to initial, error index should already be set @@ -275,8 +232,8 @@ public class FractionFormat extends NumberFormat implements Serializable { } // parse '/' - int startIndex = pos.getIndex(); - char c = parseNextCharacter(source, pos); + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); switch (c) { case 0 : // no '/' @@ -298,7 +255,7 @@ public class FractionFormat extends NumberFormat implements Serializable { parseAndIgnoreWhitespace(source, pos); // parse denominator - Number den = getDenominatorFormat().parse(source, pos); + final Number den = getDenominatorFormat().parse(source, pos); if (den == null) { // invalid integer number // set index back to initial, error index should already be set @@ -310,100 +267,4 @@ public class FractionFormat extends NumberFormat implements Serializable { return new Fraction(num.intValue(), den.intValue()); } - /** - * Modify the denominator format. - * @param format the new denominator format value. - * @throws IllegalArgumentException if format is - * null. - */ - public void setDenominatorFormat(NumberFormat format) { - if (format == null) { - throw new IllegalArgumentException( - "denominator format can not be null."); - } - this.denominatorFormat = format; - } - - /** - * Modify the numerator format. - * @param format the new numerator format value. - * @throws IllegalArgumentException if format is - * null. - */ - public void setNumeratorFormat(NumberFormat format) { - if (format == null) { - throw new IllegalArgumentException( - "numerator format can not be null."); - } - this.numeratorFormat = format; - } - - /** - * Parses source until a non-whitespace character is found. - * @param source the string to parse - * @param pos input/ouput parsing parameter. On output, pos - * holds the index of the next non-whitespace character. - */ - protected static void parseAndIgnoreWhitespace( - String source, ParsePosition pos) - { - parseNextCharacter(source, pos); - pos.setIndex(pos.getIndex() - 1); - } - - /** - * Parses source until a non-whitespace character is found. - * @param source the string to parse - * @param pos input/ouput parsing parameter. - * @return the first non-whitespace character. - */ - protected static char parseNextCharacter(String source, ParsePosition pos) { - int index = pos.getIndex(); - int n = source.length(); - char ret = 0; - - if (index < n) { - char c; - do { - c = source.charAt(index++); - } while (Character.isWhitespace(c) && index < n); - pos.setIndex(index); - - if (index < n) { - ret = c; - } - } - - return ret; - } - - /** - * Formats a double value as a fraction and appends the result to a StringBuffer. - * - * @param value the double value to format - * @param buffer StringBuffer to append to - * @param position On input: an alignment field, if desired. On output: the - * offsets of the alignment field - * @return a reference to the appended buffer - * @see {@link #format(Object, StringBuffer, FieldPosition)} - */ - public StringBuffer format(double value, StringBuffer buffer, - FieldPosition position) { - return format(Double.valueOf(value), buffer, position); - } - - - /** - * Formats a long value as a fraction and appends the result to a StringBuffer. - * - * @param value the long value to format - * @param buffer StringBuffer to append to - * @param position On input: an alignment field, if desired. On output: the - * offsets of the alignment field - * @return a reference to the appended buffer - * @see {@link #format(Object, StringBuffer, FieldPosition)} - */ - public StringBuffer format(long value, StringBuffer buffer, FieldPosition position) { - return format(Long.valueOf(value), buffer, position); - } } diff --git a/src/java/org/apache/commons/math/fraction/ProperBigFractionFormat.java b/src/java/org/apache/commons/math/fraction/ProperBigFractionFormat.java new file mode 100644 index 000000000..ab2916e57 --- /dev/null +++ b/src/java/org/apache/commons/math/fraction/ProperBigFractionFormat.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math.fraction; + +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; + +import org.apache.commons.math.MathRuntimeException; + +/** + * Formats a BigFraction number in proper format. The number format for each of + * the whole number, numerator and, denominator can be configured. + *

+ * Minus signs are only allowed in the whole number part - i.e., + * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and + * will result in a ParseException.

+ * + * @since 1.1 + * @version $Revision$ $Date$ + */ +public class ProperBigFractionFormat extends BigFractionFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = -6337346779577272307L; + + /** The format used for the whole number. */ + private NumberFormat wholeFormat; + + /** + * Create a proper formatting instance with the default number format for + * the whole, numerator, and denominator. + */ + public ProperBigFractionFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create a proper formatting instance with a custom number format for the + * whole, numerator, and denominator. + * @param format the custom format for the whole, numerator, and + * denominator. + */ + public ProperBigFractionFormat(final NumberFormat format) { + this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone()); + } + + /** + * Create a proper formatting instance with a custom number format for each + * of the whole, numerator, and denominator. + * @param wholeFormat the custom format for the whole. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public ProperBigFractionFormat(final NumberFormat wholeFormat, + final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + setWholeFormat(wholeFormat); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction + * is output in proper format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the + * offsets of the alignment field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format(final BigFraction fraction, + final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + BigInteger num = fraction.getNumerator(); + BigInteger den = fraction.getDenominator(); + BigInteger whole = num.divide(den); + num = num.remainder(den); + + if (!BigInteger.ZERO.equals(whole)) { + getWholeFormat().format(whole, toAppendTo, pos); + toAppendTo.append(' '); + if (num.compareTo(BigInteger.ZERO) < 0) { + num = num.negate(); + } + } + getNumeratorFormat().format(num, toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(den, toAppendTo, pos); + + return toAppendTo; + } + + /** + * Access the whole format. + * @return the whole format. + */ + public NumberFormat getWholeFormat() { + return wholeFormat; + } + + /** + * Parses a string to produce a {@link BigFraction} object. This method + * expects the string to be formatted as a proper BigFraction. + *

+ * Minus signs are only allowed in the whole number part - i.e., + * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and + * will result in a ParseException.

+ * + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link BigFraction} object. + */ + public BigFraction parse(final String source, final ParsePosition pos) { + // try to parse improper BigFraction + BigFraction ret = super.parse(source, pos); + if (ret != null) { + return ret; + } + + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse whole + BigInteger whole = parseNextBigInteger(source, pos); + if (whole == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + BigInteger num = parseNextBigInteger(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (num.compareTo(BigInteger.ZERO) < 0) { + // minus signs should be leading, invalid expression + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0 : + // no '/' + // return num as a BigFraction + return new BigFraction(num); + case '/' : + // found '/', continue parsing denominator + break; + default : + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final BigInteger den = parseNextBigInteger(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (den.compareTo(BigInteger.ZERO) < 0) { + // minus signs must be leading, invalid + pos.setIndex(initialIndex); + return null; + } + + boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0; + if (wholeIsNeg) { + whole = whole.negate(); + } + num = whole.multiply(den).add(num); + if (wholeIsNeg) { + num = num.negate(); + } + + return new BigFraction(num, den); + + } + + /** + * Modify the whole format. + * @param format The new whole format value. + * @throws IllegalArgumentException if format is + * null. + */ + public void setWholeFormat(final NumberFormat format) { + if (format == null) { + throw MathRuntimeException.createIllegalArgumentException( + "whole format can not be null"); + } + this.wholeFormat = format; + } + +} diff --git a/src/java/org/apache/commons/math/fraction/ProperFractionFormat.java b/src/java/org/apache/commons/math/fraction/ProperFractionFormat.java index 01d20ea0f..715060b6f 100644 --- a/src/java/org/apache/commons/math/fraction/ProperFractionFormat.java +++ b/src/java/org/apache/commons/math/fraction/ProperFractionFormat.java @@ -20,6 +20,7 @@ import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParsePosition; +import org.apache.commons.math.MathRuntimeException; import org.apache.commons.math.util.MathUtils; /** @@ -36,8 +37,8 @@ import org.apache.commons.math.util.MathUtils; public class ProperFractionFormat extends FractionFormat { /** Serializable version identifier */ - private static final long serialVersionUID = -6337346779577272307L; - + private static final long serialVersionUID = 760934726031766749L; + /** The format used for the whole number. */ private NumberFormat wholeFormat; @@ -222,8 +223,8 @@ public class ProperFractionFormat extends FractionFormat { */ public void setWholeFormat(NumberFormat format) { if (format == null) { - throw new IllegalArgumentException( - "whole format can not be null."); + throw MathRuntimeException.createIllegalArgumentException( + "whole format can not be null"); } this.wholeFormat = format; } diff --git a/src/test/org/apache/commons/math/fraction/BigFractionFormatTest.java b/src/test/org/apache/commons/math/fraction/BigFractionFormatTest.java new file mode 100644 index 000000000..c7e6edf4f --- /dev/null +++ b/src/test/org/apache/commons/math/fraction/BigFractionFormatTest.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math.fraction; + +import java.math.BigInteger; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; + +import junit.framework.TestCase; + +public class BigFractionFormatTest extends TestCase { + + BigFractionFormat properFormat = null; + BigFractionFormat improperFormat = null; + + protected Locale getLocale() { + return Locale.getDefault(); + } + + protected void setUp() throws Exception { + properFormat = BigFractionFormat.getProperInstance(getLocale()); + improperFormat = BigFractionFormat.getImproperInstance(getLocale()); + } + + public void testFormat() { + BigFraction c = new BigFraction(1, 2); + String expected = "1 / 2"; + + String actual = properFormat.format(c); + assertEquals(expected, actual); + + actual = improperFormat.format(c); + assertEquals(expected, actual); + } + + public void testFormatNegative() { + BigFraction c = new BigFraction(-1, 2); + String expected = "-1 / 2"; + + String actual = properFormat.format(c); + assertEquals(expected, actual); + + actual = improperFormat.format(c); + assertEquals(expected, actual); + } + + public void testFormatZero() { + BigFraction c = new BigFraction(0, 1); + String expected = "0 / 1"; + + String actual = properFormat.format(c); + assertEquals(expected, actual); + + actual = improperFormat.format(c); + assertEquals(expected, actual); + } + + public void testFormatImproper() { + BigFraction c = new BigFraction(5, 3); + + String actual = properFormat.format(c); + assertEquals("1 2 / 3", actual); + + actual = improperFormat.format(c); + assertEquals("5 / 3", actual); + } + + public void testFormatImproperNegative() { + BigFraction c = new BigFraction(-5, 3); + + String actual = properFormat.format(c); + assertEquals("-1 2 / 3", actual); + + actual = improperFormat.format(c); + assertEquals("-5 / 3", actual); + } + + public void testParse() { + String source = "1 / 2"; + + try { + BigFraction c = properFormat.parse(source); + assertNotNull(c); + assertEquals(BigInteger.ONE, c.getNumerator()); + assertEquals(BigInteger.valueOf(2l), c.getDenominator()); + + c = improperFormat.parse(source); + assertNotNull(c); + assertEquals(BigInteger.ONE, c.getNumerator()); + assertEquals(BigInteger.valueOf(2l), c.getDenominator()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + } + + public void testParseInteger() { + String source = "10"; + try { + BigFraction c = properFormat.parse(source); + assertNotNull(c); + assertEquals(BigInteger.TEN, c.getNumerator()); + assertEquals(BigInteger.ONE, c.getDenominator()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + try { + BigFraction c = improperFormat.parse(source); + assertNotNull(c); + assertEquals(BigInteger.TEN, c.getNumerator()); + assertEquals(BigInteger.ONE, c.getDenominator()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + } + + public void testParseInvalid() { + String source = "a"; + String msg = "should not be able to parse '10 / a'."; + try { + properFormat.parse(source); + fail(msg); + } catch (ParseException ex) { + // success + } + try { + improperFormat.parse(source); + fail(msg); + } catch (ParseException ex) { + // success + } + } + + public void testParseInvalidDenominator() { + String source = "10 / a"; + String msg = "should not be able to parse '10 / a'."; + try { + properFormat.parse(source); + fail(msg); + } catch (ParseException ex) { + // success + } + try { + improperFormat.parse(source); + fail(msg); + } catch (ParseException ex) { + // success + } + } + + public void testParseNegative() { + + try { + String source = "-1 / 2"; + BigFraction c = properFormat.parse(source); + assertNotNull(c); + assertEquals(-1, c.getNumeratorAsInt()); + assertEquals(2, c.getDenominatorAsInt()); + + c = improperFormat.parse(source); + assertNotNull(c); + assertEquals(-1, c.getNumeratorAsInt()); + assertEquals(2, c.getDenominatorAsInt()); + + source = "1 / -2"; + c = properFormat.parse(source); + assertNotNull(c); + assertEquals(-1, c.getNumeratorAsInt()); + assertEquals(2, c.getDenominatorAsInt()); + + c = improperFormat.parse(source); + assertNotNull(c); + assertEquals(-1, c.getNumeratorAsInt()); + assertEquals(2, c.getDenominatorAsInt()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + } + + public void testParseProper() { + String source = "1 2 / 3"; + + try { + BigFraction c = properFormat.parse(source); + assertNotNull(c); + assertEquals(5, c.getNumeratorAsInt()); + assertEquals(3, c.getDenominatorAsInt()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + + try { + improperFormat.parse(source); + fail("invalid improper fraction."); + } catch (ParseException ex) { + // success + } + } + + public void testParseProperNegative() { + String source = "-1 2 / 3"; + try { + BigFraction c = properFormat.parse(source); + assertNotNull(c); + assertEquals(-5, c.getNumeratorAsInt()); + assertEquals(3, c.getDenominatorAsInt()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + + try { + improperFormat.parse(source); + fail("invalid improper fraction."); + } catch (ParseException ex) { + // success + } + } + + public void testParseProperInvalidMinus() { + String source = "2 -2 / 3"; + try { + properFormat.parse(source); + fail("invalid minus in improper fraction."); + } catch (ParseException ex) { + // expected + } + source = "2 2 / -3"; + try { + properFormat.parse(source); + fail("invalid minus in improper fraction."); + } catch (ParseException ex) { + // expected + } + } + + public void testNumeratorFormat() { + NumberFormat old = properFormat.getNumeratorFormat(); + NumberFormat nf = NumberFormat.getInstance(); + nf.setParseIntegerOnly(true); + properFormat.setNumeratorFormat(nf); + assertEquals(nf, properFormat.getNumeratorFormat()); + properFormat.setNumeratorFormat(old); + + old = improperFormat.getNumeratorFormat(); + nf = NumberFormat.getInstance(); + nf.setParseIntegerOnly(true); + improperFormat.setNumeratorFormat(nf); + assertEquals(nf, improperFormat.getNumeratorFormat()); + improperFormat.setNumeratorFormat(old); + } + + public void testDenominatorFormat() { + NumberFormat old = properFormat.getDenominatorFormat(); + NumberFormat nf = NumberFormat.getInstance(); + nf.setParseIntegerOnly(true); + properFormat.setDenominatorFormat(nf); + assertEquals(nf, properFormat.getDenominatorFormat()); + properFormat.setDenominatorFormat(old); + + old = improperFormat.getDenominatorFormat(); + nf = NumberFormat.getInstance(); + nf.setParseIntegerOnly(true); + improperFormat.setDenominatorFormat(nf); + assertEquals(nf, improperFormat.getDenominatorFormat()); + improperFormat.setDenominatorFormat(old); + } + + public void testWholeFormat() { + ProperBigFractionFormat format = (ProperBigFractionFormat)properFormat; + + NumberFormat old = format.getWholeFormat(); + NumberFormat nf = NumberFormat.getInstance(); + nf.setParseIntegerOnly(true); + format.setWholeFormat(nf); + assertEquals(nf, format.getWholeFormat()); + format.setWholeFormat(old); + } + + public void testLongFormat() { + assertEquals("10 / 1", improperFormat.format(10l)); + } + + public void testDoubleFormat() { + assertEquals("1 / 16", improperFormat.format(0.0625)); + } +}