All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.Ostermiller.util.SignificantFigures Maven / Gradle / Ivy

Go to download

Open source (GPL) Java utilities maintained by Stephen Ostermiller with help from many contributors.

The newest version!
/*
 * 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.
 */

package com.Ostermiller.util;

/**
 * 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 ostermiller.org.
 * 

* When parsing a number to determine the number of significant figures, * these rules are used: *

    *
  • Non-zero digits are always significant.
  • *
  • All zeros between other significant digits are significant.
  • *
  • All zeros left of the decimal point between a significant digit and the decimal point are significant.
  • *
  • All trailing zeros to the right of the decimal point are significant.
  • *
  • If the number is contains no digits other than zero, every zero is significant.
  • *
*

* When rounding a number the following rules are used: *

    *
  • If the greatest insignificant digit is less than five, round down.
  • *
  • If the greatest insignificant digit is greater than five, round up.
  • *
  • If the greatest insignificant digit is five and followed by some non-zero digit, round up.
  • *
  • If the greatest insignificant digit is five and followed only by zeros, and the least significant * digit is odd, round up.
  • *
  • If the greatest insignificant digit is five and followed only by zeros, and the least significant * digit is even, round down.
  • *
* *

* Example of using this class to multiply numbers and display the result * with the proper number of significant figures:
*

 String[] arguments = {"1.0", "2.0", ...}
 * SignificantFigures number;
 * int sigFigs = Integer.MAX_VALUE;
 * double result = 1D;
 * for (int i=0; i<arguments.length; i++){
 *     number = new SignificantFigures(arguments[i]);
 *     sigFigs = Math.min(sigFigs, number.getNumberSignificantFigures());
 *     result *= number.doubleValue();
 * }
 * number = new SignificantFigures(result);
 * number.setNumberSignificantFigures(sigFigs);
 * System.out.println(number);
*

* Example of using this class to add numbers and display the result * with the proper number of significant figures:
*

 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<arguments.length; i++){
 *     number = new SignificantFigures(arguments[i]);
 *     leastSD = Math.max(leastSD, number.getLSD());
 *     mostSD = Math.max(mostSD, number.getMSD());
 *     result += number.doubleValue();
 * }
 * number = new SignificantFigures(result);
 * number.setLMSD(leastSD, mostSD);
 * System.out.println(number);
* * @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. *

* 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. *

* 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. *

* 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. *

* 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. *

* 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 * 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. *

* 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. *

* 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. *

* 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 0){ // if nothing but zeros all zeros are significant. for (int j=0; j * 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){ // 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=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(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy