Java/Data Type/Rational

Материал из Java эксперт
Версия от 06:32, 1 июня 2010; Admin (обсуждение | вклад) (1 версия)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Immutable class for holding a rational number without loss of precision

 
/*
 * Rational.java
 *
 * This class is public domain software - that is, you can do whatever you want
 * with it, and include it software that is licensed under the GNU or the
 * BSD license, or whatever other licence you choose, including proprietary
 * closed source licenses.  Similarly, I release this Java version under the
 * same license, though I do ask that you leave this header in tact.
 *
 * If you make modifications to this code that you think would benefit the
 * wider community, please send me a copy and I"ll post it on my site.
 *
 * If you make use of this code, I"d appreciate hearing about it.
 *   drew.noakes@drewnoakes.ru
 * Latest version of this software kept at
 *   http://drewnoakes.ru/
 *
 * Created on 6 May 2002, 18:06
 * Updated 26 Aug 2002 by Drew
 * - Added toSimpleString() method, which returns a simplified and hopefully more
 *   readable version of the Rational.  i.e. 2/10 -> 1/5, and 10/2 -> 5
 * Modified 29 Oct 2002 (v1.2)
 * - Improved toSimpleString() to factor more complex rational numbers into
 *   a simpler form
 *     i.e.
 *       10/15 -> 2/3
 * - toSimpleString() now accepts a boolean flag, "allowDecimals" which will
 *   display the rational number in decimal form if it fits within 5 digits
 *     i.e.
 *       3/4 -> 0.75 when allowDecimal == true
 */
import java.io.Serializable;
/**
 * Immutable class for holding a rational number without loss of precision.  Provides
 * a familiar representation via toString() in form <code>numerator/denominator</code>.
 * <p>
 * @author  Drew Noakes http://drewnoakes.ru
 */
public class Rational extends java.lang.Number implements Serializable
{
    /**
     * Holds the numerator.
     */
    private final int numerator;
    /**
     * Holds the denominator.
     */
    private final int denominator;
    private int maxSimplificationCalculations = 1000;
    /**
     * Creates a new instance of Rational.  Rational objects are immutable, so
     * once you"ve set your numerator and denominator values here, you"re stuck
     * with them!
     */
    public Rational(int numerator, int denominator)
    {
        this.numerator = numerator;
        this.denominator = denominator;
    }
    /**
     * Returns the value of the specified number as a <code>double</code>.
     * This may involve rounding.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>double</code>.
     */
    public double doubleValue()
    {
        return (double)numerator / (double)denominator;
    }
    /**
     * Returns the value of the specified number as a <code>float</code>.
     * This may involve rounding.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>float</code>.
     */
    public float floatValue()
    {
        return (float)numerator / (float)denominator;
    }
    /**
     * Returns the value of the specified number as a <code>byte</code>.
     * This may involve rounding or truncation.  This implementation simply
     * casts the result of <code>doubleValue()</code> to <code>byte</code>.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>byte</code>.
     */
    public final byte byteValue()
    {
        return (byte)doubleValue();
    }
    /**
     * Returns the value of the specified number as an <code>int</code>.
     * This may involve rounding or truncation.  This implementation simply
     * casts the result of <code>doubleValue()</code> to <code>int</code>.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>int</code>.
     */
    public final int intValue()
    {
        return (int)doubleValue();
    }
    /**
     * Returns the value of the specified number as a <code>long</code>.
     * This may involve rounding or truncation.  This implementation simply
     * casts the result of <code>doubleValue()</code> to <code>long</code>.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>long</code>.
     */
    public final long longValue()
    {
        return (long)doubleValue();
    }
    /**
     * Returns the value of the specified number as a <code>short</code>.
     * This may involve rounding or truncation.  This implementation simply
     * casts the result of <code>doubleValue()</code> to <code>short</code>.
     *
     * @return  the numeric value represented by this object after conversion
     *          to type <code>short</code>.
     */
    public final short shortValue()
    {
        return (short)doubleValue();
    }

    /**
     * Returns the denominator.
     */
    public final int getDenominator()
    {
        return this.denominator;
    }
    /**
     * Returns the numerator.
     */
    public final int getNumerator()
    {
        return this.numerator;
    }
    /**
     * Returns the reciprocal value of this obejct as a new Rational.
     * @return the reciprocal in a new object
     */
    public Rational getReciprocal()
    {
        return new Rational(this.denominator, this.numerator);
    }
    /**
     * Checks if this rational number is an Integer, either positive or negative.
     */
    public boolean isInteger()
    {
        if (denominator == 1 ||
                (denominator != 0 && (numerator % denominator == 0)) ||
                (denominator == 0 && numerator == 0)
        ) {
            return true;
        } else {
            return false;
        }
    }
    /**
     * Returns a string representation of the object of form <code>numerator/denominator</code>.
     * @return  a string representation of the object.
     */
    public String toString()
    {
        return numerator + "/" + denominator;
    }
    /**
     * Returns the simplest represenation of this Rational"s value possible.
     */
    public String toSimpleString(boolean allowDecimal)
    {
        if (denominator == 0 && numerator != 0) {
            return toString();
        } else if (isInteger()) {
            return Integer.toString(intValue());
        } else if (numerator != 1 && denominator % numerator == 0) {
            // common factor between denominator and numerator
            int newDenominator = denominator / numerator;
            return new Rational(1, newDenominator).toSimpleString(allowDecimal);
        } else {
            Rational simplifiedInstance = getSimplifiedInstance();
            if (allowDecimal) {
                String doubleString = Double.toString(simplifiedInstance.doubleValue());
                if (doubleString.length() < 5) {
                    return doubleString;
                }
            }
            return simplifiedInstance.toString();
        }
    }
    /**
     * Decides whether a brute-force simplification calculation should be avoided
     * by comparing the maximum number of possible calculations with some threshold.
     * @return true if the simplification should be performed, otherwise false
     */
    private boolean tooComplexForSimplification()
    {
        double maxPossibleCalculations = (((double)(Math.min(denominator, numerator) - 1) / 5d) + 2);
        return maxPossibleCalculations > maxSimplificationCalculations;
    }
    /**
     * Compares two <code>Rational</code> instances, returning true if they are mathematically
     * equivalent.
     * @param obj the Rational to compare this instance to.
     * @return true if instances are mathematically equivalent, otherwise false.  Will also
     *         return false if <code>obj</code> is not an instance of <code>Rational</code>.
     */
    public boolean equals(Object obj)
    {
        if (!(obj instanceof Rational)) {
            return false;
        }
        Rational that = (Rational)obj;
        return this.doubleValue() == that.doubleValue();
    }
    /**
     * <p>
     * Simplifies the Rational number.</p>
     * <p>
     * Prime number series: 1, 2, 3, 5, 7, 9, 11, 13, 17</p>
     * <p>
     * To reduce a rational, need to see if both numerator and denominator are divisible
     * by a common factor.  Using the prime number series in ascending order guarantees
     * the minimun number of checks required.</p>
     * <p>
     * However, generating the prime number series seems to be a hefty task.  Perhaps
     * it"s simpler to check if both d & n are divisible by all numbers from 2 ->
     * (Math.min(denominator, numerator) / 2).  In doing this, one can check for 2
     * and 5 once, then ignore all even numbers, and all numbers ending in 0 or 5.
     * This leaves four numbers from every ten to check.</p>
     * <p>
     * Therefore, the max number of pairs of modulus divisions required will be:</p>
     * <code><pre>
     *    4   Math.min(denominator, numerator) - 1
     *   -- * ------------------------------------ + 2
     *   10                    2
     *
     *   Math.min(denominator, numerator) - 1
     * = ------------------------------------ + 2
     *                  5
     * </pre></code>
     * @return a simplified instance, or if the Rational could not be simpliffied,
     *         returns itself (unchanged)
     */
    public Rational getSimplifiedInstance()
    {
        if (tooComplexForSimplification()) {
            return this;
        }
        for (int factor = 2; factor <= Math.min(denominator, numerator); factor++) {
            if ((factor % 2 == 0 && factor > 2) || (factor % 5 == 0 && factor > 5)) {
                continue;
            }
            if (denominator % factor == 0 && numerator % factor == 0) {
                // found a common factor
                return new Rational(numerator / factor, denominator / factor);
            }
        }
        return this;
    }
}





Significant Figures

 
/*
 * Copyright (C) 2002-2007 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Java+Utilities
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * See COPYING.TXT for details.
 */

/**
 * A number with an associated number of significant figures.
 * This class handles parsing numbers, determining the number
 * of significant figures, adjusting the number of significant
 * figures (including scientific rounding), and displaying the number.
 * More information about this class is available from .
 * <p>
 * When parsing a number to determine the number of significant figures,
 * these rules are used:
 * <ul>
 * <li>Non-zero digits are always significant.</li>
 * <li>All zeros between other significant digits are significant.</li>
 * <li>All zeros left of the decimal point between a significant digit and the decimal point are significant.</li>
 * <li>All trailing zeros to the right of the decimal point are significant.</li>
 * <li>If the number is contains no digits other than zero, every zero is significant.</li>
 * </ul>
 * <p>
 * When rounding a number the following rules are used:
 * <ul>
 * <li>If the greatest insignificant digit is less than five, round down.</li>
 * <li>If the greatest insignificant digit is greater than five, round up.</li>
 * <li>If the greatest insignificant digit is five and followed by some non-zero digit, round up.</li>
 * <li>If the greatest insignificant digit is five and followed only by zeros, and the least significant
 * digit is odd, round up.</li>
 * <li>If the greatest insignificant digit is five and followed only by zeros, and the least significant
 * digit is even, round down.</li>
 * </ul>
 *
 * <p>
 * Example of using this class to multiply numbers and display the result
 * with the proper number of significant figures:<br>
 * <pre> String[] arguments = {"1.0", "2.0", ...}
 * SignificantFigures number;
 * int sigFigs = Integer.MAX_VALUE;
 * double result = 1D;
 * for (int i=0; i&lt;arguments.length; i++){
 * &nbsp;   number = new SignificantFigures(arguments[i]);
 * &nbsp;   sigFigs = Math.min(sigFigs, number.getNumberSignificantFigures());
 * &nbsp;   result *= number.doubleValue();
 * }
 * number = new SignificantFigures(result);
 * number.setNumberSignificantFigures(sigFigs);
 * System.out.println(number);</pre>
 * <p>
 * Example of using this class to add numbers and display the result
 * with the proper number of significant figures:<br>
 * <pre> String[] arguments = {"1.0", "2.0", ...}
 * SignificantFigures number;
 * int leastSD = Integer.MIN_VALUE;
 * int mostSD = Integer.MIN_VALUE;
 * double result = 0D;
 * for (int i=0; i&lt;arguments.length; i++){
 * &nbsp;   number = new SignificantFigures(arguments[i]);
 * &nbsp;   leastSD = Math.max(leastSD, number.getLSD());
 * &nbsp;   mostSD = Math.max(mostSD, number.getMSD());
 * &nbsp;   result += number.doubleValue();
 * }
 * number = new SignificantFigures(result);
 * number.setLMSD(leastSD, mostSD);
 * System.out.println(number);</pre>
 *
 * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
 * @since ostermillerutils 1.00.00
 */
public class SignificantFigures extends Number {
  /**
   *
   */
  private static final long serialVersionUID = -1130798283937219608L;
  /**
   * In the case the a number
   * could not be parsed, the original is stored
   * for toString purposes.
   *
   * @since ostermillerutils 1.00.00
   */
  private String original;
  /**
   * Buffer of the significant digits.
   *
   * @since ostermillerutils 1.00.00
   */
  private StringBuffer digits;
  /**
   * The exponent of the digits if a
   * decimal place were inserted after
   * the first digit.
   *
   * @since ostermillerutils 1.00.00
   */
  private int mantissa = -1;
  /**
   * positive if true, negative if false.
   *
   * @since ostermillerutils 1.00.00
   */
  private boolean sign = true;
  /**
   * True if this number has no non-zero digits.
   *
   * @since ostermillerutils 1.00.00
   */
  private boolean isZero = false;
  /**
   * Create a SignificantFigures object from a String representation of a number.
   *
   * @param number String representation of the number.
   * @throws NumberFormatException if the String is not a valid number.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(String number) throws NumberFormatException {
    original = number;
    parse(original);
  }
  /**
   * Create a SignificantFigures object from a byte.
   *
   * @param number an 8 bit integer.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(byte number){
    original = Byte.toString(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from a short.
   *
   * @param number a 16 bit integer.
   *
   * @since ostermillerutils 1.00.00
   */
   public SignificantFigures(short number){
    original = Short.toString(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from an integer.
   *
   * @param number a 32 bit integer.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(int number){
    original = String.valueOf(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from a long.
   *
   * @param number a 64 bit integer.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(long number){
    original = Long.toString(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from a float.
   *
   * @param number a 32 bit floating point.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(float number){
    original = Float.toString(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from a double.
   *
   * @param number a 64 bit floating point.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(double number){
    original = Double.toString(number);
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Create a SignificantFigures object from a java number such as
   * a BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, or
   * Short.
   *
   * @param number a number.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures(Number number){
    original = number.toString();
    try {
      parse(original);
    } catch (NumberFormatException nfe){
      digits = null;
    }
  }
  /**
   * Get the number of significant digits.
   * <p>
   * If this number is not a number or infinity zero
   * will be returned.
   *
   * @return the number of significant digits in this number.
   *
   * @since ostermillerutils 1.00.00
   */
  public int getNumberSignificantFigures() {
    if (digits == null) return 0;
    return digits.length();
  }
  /**
   * Adjust the number of significant figures such that the least
   * significant digit is at the given place.  This method may add
   * significant zeros to the end of this number, or remove significant
   * digits from this number.
   * <p>
   * It is possible to remove all significant digits from this number which
   * will cause the string representation of this number to become "NaN".  This
   * could become a problem if you are adding numbers and the result is close
   * to zero.  All of the significant digits may get removed, even though the
   * result could be zero with some number of significant digits.  Its is safes
   * to use the setLMSD() method which will make a zero with the appropriate
   * number of significant figures in such instances.
   * <p>
   * This method has no effect if this number is not a number or infinity.
   *
   * @param place the desired place of the least significant digit.
   * @return this number.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures setLSD(int place){
    setLMSD(place, Integer.MIN_VALUE);
    return this;
  }
  /**
   * Adjust the number of significant figures such that the least
   * significant digit is at the given place.  This method may add
   * significant zeros to the end of this number, or remove significant
   * digits from this number.
   * <p>
   * If all significant digits are removed from this number by truncating to
   * the least significant place, a zero will be created with significant figures
   * from the least to most significant places.
   * <p>
   * This method has no effect if this number is not a number or infinity.
   *
   * @param leastPlace the desired place of the least significant digit or Integer.MIN_VALUE to ignore.
   * @param mostPlace the desired place of the most significant digit or Integer.MIN_VALUE to ignore.
   * @return this number
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures setLMSD(int leastPlace, int mostPlace){
    if (digits != null && leastPlace != Integer.MIN_VALUE){
      int significantFigures = digits.length();
      int current = mantissa - significantFigures + 1;
      int newLength = significantFigures - leastPlace + current;
      if (newLength <= 0){
        if (mostPlace == Integer.MIN_VALUE){
          original = "NaN";
          digits = null;
        } else {
          newLength = mostPlace - leastPlace + 1;
          digits.setLength(newLength);
          mantissa = leastPlace;
          for (int i=0; i<newLength; i++){
            digits.setCharAt(i, "0");
          }
          isZero = true;
          sign = true;
        }
      } else {
        digits.setLength(newLength);
        for (int i=significantFigures; i<newLength; i++){
          digits.setCharAt(i, "0");
        }
      }
    }
    return this;
  }
  /**
   * Get the decimal place of the least significant digit.
   * <p>
   * If this number is not a number or infinity Integer.MIN_VALUE
   * will be returned.
   *
   * @return the decimal place of the least significant digit.
   *
   * @since ostermillerutils 1.00.00
   */
  public int getLSD(){
    if (digits == null) return Integer.MIN_VALUE;
    return mantissa - digits.length() + 1;
  }
  /**
   * Get the decimal place of the most significant digit.
   * <p>
   * If this number is not a number or infinity Integer.MIN_VALUE
   * will be returned.
   *
   * @return the decimal place of the least significant digit.
   *
   * @since ostermillerutils 1.00.00
   */
  public int getMSD(){
    if (digits == null) return Integer.MIN_VALUE;
    return mantissa + 1;
  }
  /**
   * Formats this number.
   * If the number is less than 10^-3 or greater than or equal to 10^7,
   * or the number might have an ambiguous number of significant figures,
   * scientific notation will be used.
   * <p>
   * A string such as "NaN" or "Infinity" may be returned by this method.
   *
   * @return representation of this number.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public String toString() {
    if (digits == null) return original;
    StringBuffer digits = new StringBuffer(this.digits.toString());
    int length = digits.length();
    if (mantissa <= -4 || mantissa >= 7 ||
        (mantissa >= length &&
        digits.charAt(digits.length()-1) == "0") ||
        (isZero && mantissa != 0)) {
      // use scientific notation.
      if (length > 1){
        digits.insert(1, ".");
      }
      if (mantissa != 0){
        digits.append("E" + mantissa);
      }
    } else if (mantissa <= -1){
      digits.insert(0, "0.");
      for (int i=mantissa; i<-1; i++){
        digits.insert(2, "0");
      }
    } else if (mantissa+1 == length){
      if (length > 1 && digits.charAt(digits.length()-1) == "0"){
        digits.append(".");
      }
    } else if (mantissa < length){
      digits.insert(mantissa+1, ".");
    } else {
      for (int i=length; i<=mantissa; i++){
        digits.append("0");
      }
    }
    if (!sign) {
      digits.insert(0, "-");
    }
    return digits.toString();
  }
  /**
   * Formats this number in scientific notation.
   * <p>
   * A string such as "NaN" or "Infinity" may be returned by this method.
   *
   * @return representation of this number in scientific notation.
   *
   * @since ostermillerutils 1.00.00
   */
  public String toScientificNotation() {
    if (digits == null) return original;
    StringBuffer digits = new StringBuffer(this.digits.toString());
    int length = digits.length();
    if (length > 1){
      digits.insert(1, ".");
    }
    if (mantissa != 0){
      digits.append("E" + mantissa);
    }
    if (!sign) {
      digits.insert(0, "-");
    }
    return digits.toString();
  }
  /**
   * Parsing state:
   * Initial state before anything read.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int INITIAL = 0;
  /**
   * Parsing state:
   * State in which a possible sign and
   * possible leading zeros have been read.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int LEADZEROS = 1;
  /**
   * Parsing state:
   * State in which a possible sign and
   * at least one non-zero digit
   * has been read followed by some number of
   * zeros.  The decimal place has no
   * been encountered yet.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int MIDZEROS = 2;
  /**
   * Parsing state:
   * State in which a possible sign and
   * at least one non-zero digit
   * has been read.  The decimal place has no
   * been encountered yet.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int DIGITS = 3;
  /**
   * Parsing state:
   * State in which only a possible sign,
   * leading zeros, and a decimal point
   * have been encountered.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int LEADZEROSDOT = 4;
  /**
   * Parsing state:
   * State in which a possible sign,
   * at least one nonzero digit and a
   * decimal point have been encountered.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int DIGITSDOT = 5;
  /**
   * Parsing state:
   * State in which the exponent symbol
   * "E" has been encountered.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int MANTISSA = 6;
  /**
   * Parsing state:
   * State in which the exponent symbol
   * "E" has been encountered followed
   * by a possible sign or some number
   * of digits.
   *
   * @since ostermillerutils 1.00.00
   */
  private final static int MANTISSADIGIT = 7;
  /**
   * Parse a number from the given string.
   * A valid number has an optional sign, some digits
   * with an optional decimal point, and an optional
   * scientific notation part consisting of an "E" followed
   * by an optional sign, followed by some digits.
   *
   * @param number String representation of a number.
   * @throws NumberFormatException if the string is not a valid number.
   *
   * @since ostermillerutils 1.00.00
   */
  private void parse(String number) throws NumberFormatException {
    int length = number.length();
    digits = new StringBuffer(length);
    int state = INITIAL;
    int mantissaStart = -1;
    boolean foundMantissaDigit = false;
    // sometimes we don"t know if a zero will be
    // significant or not when it is encountered.
    // keep track of the number of them so that
    // the all can be made significant if we find
    // out that they are.
    int zeroCount = 0;
    int leadZeroCount = 0;
    for (int i=0; i<length; i++){
      char c = number.charAt(i);
      switch (c){
        case ".": {
          switch (state){
            case INITIAL:
            case LEADZEROS: {
              state = LEADZEROSDOT;
            } break;
            case MIDZEROS: {
              // we now know that these zeros
              // are more than just trailing place holders.
              for (int j=0; j<zeroCount; j++){
                digits.append("0");
              }
              zeroCount = 0;
              state = DIGITSDOT;
            } break;
            case DIGITS: {
              state = DIGITSDOT;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        case "+":{
          switch (state){
            case INITIAL: {
              sign = true;
              state = LEADZEROS;
            } break;
            case MANTISSA: {
              state = MANTISSADIGIT;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        case "-": {
          switch (state){
            case INITIAL: {
              sign = false;
              state = LEADZEROS;
            } break;
            case MANTISSA: {
              state = MANTISSADIGIT;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        case "0": {
          switch (state){
            case INITIAL:
            case LEADZEROS: {
              // only significant if number
              // is all zeros.
              zeroCount++;
              leadZeroCount++;
              state = LEADZEROS;
            } break;
            case MIDZEROS:
            case DIGITS: {
              // only significant if followed
              // by a decimal point or nonzero digit.
              mantissa++;
              zeroCount++;
              state = MIDZEROS;
            } break;
            case LEADZEROSDOT:{
              // only significant if number
              // is all zeros.
              mantissa--;
              zeroCount++;
              state = LEADZEROSDOT;
            } break;
            case DIGITSDOT: {
              // non-leading zeros after
              // a decimal point are always
              // significant.
              digits.append(c);
            } break;
            case MANTISSA:
            case MANTISSADIGIT: {
              foundMantissaDigit = true;
              state = MANTISSADIGIT;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        case "1": case "2": case "3":
        case "4": case "5": case "6":
        case "7": case "8": case "9": {
          switch (state){
            case INITIAL:
            case LEADZEROS:
            case DIGITS: {
              zeroCount = 0;
              digits.append(c);
              mantissa++;
              state = DIGITS;
            } break;
            case MIDZEROS: {
              // we now know that these zeros
              // are more than just trailing place holders.
              for (int j=0; j<zeroCount; j++){
                digits.append("0");
              }
              zeroCount = 0;
              digits.append(c);
              mantissa++;
              state = DIGITS;
            } break;
            case LEADZEROSDOT:
            case DIGITSDOT: {
              zeroCount = 0;
              digits.append(c);
              state = DIGITSDOT;
            } break;
            case MANTISSA:
            case MANTISSADIGIT: {
              state = MANTISSADIGIT;
              foundMantissaDigit = true;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        case "E": case "e": {
          switch (state){
            case INITIAL:
            case LEADZEROS:
            case DIGITS:
            case LEADZEROSDOT:
            case DIGITSDOT: {
              // record the starting point of the mantissa
              // so we can do a substring to get it back later
              mantissaStart = i+1;
              state = MANTISSA;
            } break;
            default: {
              throw new NumberFormatException (
                "Unexpected character "" + c + "" at position " + i
              );
            }
          }
        } break;
        default: {
          throw new NumberFormatException (
            "Unexpected character "" + c + "" at position " + i
          );
        }
      }
    }
    if (mantissaStart != -1){
      // if we had found an "E"
      if (!foundMantissaDigit){
        // we didn"t actually find a mantissa to go with.
        throw new NumberFormatException (
          "No digits in mantissa."
        );
      }
      // parse the mantissa.
      mantissa += Integer.parseInt(number.substring(mantissaStart));
    }
    if (digits.length() == 0){
      if (zeroCount > 0){
        // if nothing but zeros all zeros are significant.
        for (int j=0; j<zeroCount; j++){
          digits.append("0");
        }
        mantissa += leadZeroCount;
        isZero = true;
        sign = true;
      } else {
        // a hack to catch some cases that we could catch
        // by adding a ton of extra states.  Things like:
        // "e2" "+e2" "+." "." "+" etc.
        throw new NumberFormatException (
          "No digits in number."
        );
      }
    }
  }
  /**
   * Adjust the number of digits in the number.
   * Pad the tail with zeros if too short, round the
   * number according to scientific rounding if too long, leave alone
   * if just right.
   * <p>
   * This method has no effect if this number is not a number or infinity.
   *
   * @param significantFigures desired number of significant figures.
   * @return This number.
   *
   * @since ostermillerutils 1.00.00
   */
  public SignificantFigures setNumberSignificantFigures(int significantFigures){
    if (significantFigures <= 0) throw new IllegalArgumentException("Desired number of significant figures must be positive.");
    if (digits != null) {
      int length =  digits.length();
      if (length < significantFigures){
        // number is not long enough, pad it with zeros.
        for (int i=length; i<significantFigures; i++){
          digits.append("0");
        }
      } else if (length > significantFigures){
        // number is too long chop some of it off with rounding.
        boolean addOne; // we need to round up if true.
        char firstInSig = digits.charAt(significantFigures);
        if (firstInSig < "5"){
          // first non-significant digit less than five, round down.
          addOne = false;
        } else if (firstInSig == "5"){
          // first non-significant digit equal to five
          addOne = false;
          for (int i=significantFigures+1; !addOne && i<length; i++){
            // if its followed by any non-zero digits, round up.
            if (digits.charAt(i) != "0"){
              addOne = true;
            }
          }
          if (!addOne){
            // if it was not followed by non-zero digits
            // if the last significant digit is odd round up
            // if the last significant digit is even round down
            addOne = (digits.charAt(significantFigures-1) & 1) == 1;
          }
        } else {
          // first non-significant digit greater than five, round up.
          addOne = true;
        }
        // loop to add one (and carry a one if added to a nine)
        // to the last significant digit
        for (int i=significantFigures-1; addOne && i>=0; i--){
          char digit = digits.charAt(i);
          if (digit < "9"){
            digits.setCharAt(i, (char)(digit+1));
            addOne = false;
          } else {
            digits.setCharAt(i, "0");
          }
        }
        if (addOne){
          // if the number was all nines
          digits.insert(0, "1");
          mantissa++;
        }
        // chop it to the correct number of figures.
        digits.setLength(significantFigures);
      }
    }
    return this;
  }
  /**
   * Returns the value of this number as a byte.
   *
   * @return the numeric value represented by this object after conversion to type byte.
   * @throws NumberFormatException if this number cannot be converted to a byte.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public byte byteValue() throws NumberFormatException {
    return Byte.parseByte(original);
  }
  /**
   * Returns the value of this number as a double.
   *
   * @return the numeric value represented by this object after conversion to type double.
   * @throws NumberFormatException if this number cannot be converted to a double.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public double doubleValue() throws NumberFormatException {
    return Double.parseDouble(original);
  }
  /**
   * Returns the value of this number as a float.
   *
   * @return the numeric value represented by this object after conversion to type float.
   * @throws NumberFormatException if this number cannot be converted to a float.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public float floatValue() throws NumberFormatException {
    return Float.parseFloat(original);
  }
  /**
   * Returns the value of this number as a int.
   *
   * @return the numeric value represented by this object after conversion to type int.
   * @throws NumberFormatException if this number cannot be converted to a int.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public int intValue() throws NumberFormatException {
    return Integer.parseInt(original);
  }
  /**
   * Returns the value of this number as a long.
   *
   * @return the numeric value represented by this object after conversion to type long.
   * @throws NumberFormatException if this number cannot be converted to a long.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public long longValue() throws NumberFormatException {
    return Long.parseLong(original);
  }
  /**
   * Returns the value of this number as a short.
   *
   * @return the numeric value represented by this object after conversion to type short.
   * @throws NumberFormatException if this number cannot be converted to a short.
   *
   * @since ostermillerutils 1.00.00
   */
  @Override public short shortValue() throws NumberFormatException {
    return Short.parseShort(original);
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(byte number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(double number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(float number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(int number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(long number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(Number number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(short number, int significantFigures){
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
  /**
   * Convenience method to display a number with the correct
   * significant digits.
   *
   * @param number the number to display
   * @param significantFigures the number of significant figures to display.
   * @return the number formatted with the correct significant figures
   * @throws NumberFormatException if the String is not a valid number.
   *
   * @since ostermillerutils 1.02.07
   */
  public static String format(String number, int significantFigures) throws NumberFormatException {
    SignificantFigures sf = new SignificantFigures(number);
    sf.setNumberSignificantFigures(significantFigures);
    return sf.toString();
  }
}