package org.xmind.org.freehep.util; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; import java.text.Format; import java.text.ParsePosition; import java.util.Locale; /** * This code formats numbers in Scientific Notation. The input Number object is * returned as a ScientificFormated string. There are two output styles: Pure * and Standard scientific notation. Pure formatted numbers have precisely the * number of digits specified by the significant digits (sigDig) parameter and * always specify a Base 10 Exponential(E). Standard formated numbers have the * number of digits specified by the significant digits (sigDig) parameter but * will not have a Base 10 Exponential(E) if the number of digits in the * mantissa <= maxWidth. * * @author Paul Spence * @author Mark Donszelmann * @author Jason Wong */ public class ScientificFormat extends Format { /** * The number of significant digits the number is formatted to is recorded * by sigDigit. The maximum width allowed for the returned String is * recorded by MaxWidth */ private int sigDigit = 5; private int maxWidth = 8; private boolean sciNote = false; // set to true for pure Scientific Notation private DecimalFormat decimalFormat; private static final long serialVersionUID = -1182686857248711235L; public ScientificFormat() { } /** * Sets the significant digits, maximum allowable width and number * formatting style (SciNote == true for Pure formatting). */ public ScientificFormat(int sigDigit, int maxWidth, boolean SciNote) { setSigDigits(sigDigit); setMaxWidth(maxWidth); setScientificNotationStyle(SciNote); } /** * Implementation of inherited abstract method. Checks to see if object to * be formatted is of type Number. If so casts the Number object to double * and calls the format method. Returns the result. */ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { if (obj instanceof Number) { String result = format(((Number) obj).doubleValue()); return toAppendTo.append(result); } else if (obj instanceof DoubleWithError) { DoubleWithError dwe = (DoubleWithError) obj; toAppendTo.append(format(dwe.getValue())); if (dwe.hasAsymmetricError()) { toAppendTo.append(DoubleWithError.plus); int errorSigDigit = resolveErrorSigDigit(dwe.getValue(), dwe.getPlusError()); toAppendTo.append(format(dwe.getPlusError(), errorSigDigit)); toAppendTo.append(DoubleWithError.minus); errorSigDigit = resolveErrorSigDigit(dwe.getValue(), dwe.getMinError()); toAppendTo.append(format(dwe.getMinError(), errorSigDigit)); } else { toAppendTo.append(DoubleWithError.plusorminus); int errorSigDigit = resolveErrorSigDigit(dwe.getValue(), dwe.getError()); toAppendTo.append(format(dwe.getError(), errorSigDigit)); } return toAppendTo; } else throw new IllegalArgumentException( "Cannot format given Object as a Number"); //$NON-NLS-1$ } /** * Dummy implementation of inherited abstract method. */ public Object parseObject(String source, ParsePosition pos) { return null; } /** * Returns the number of significant digits */ public int getSigDigits() { return sigDigit; } /** * Returns the maximum allowable width of formatted number excluding any * exponentials */ public int getMaxWidth() { return maxWidth; } /** * Returns the formatting style: True means Pure scientific formatting, * False means standard. */ public boolean getScientificNotationStyle() { return sciNote; } /** * Sets the number of significant digits for the formatted number */ public void setSigDigits(int SigDigit) { if (SigDigit < 1) throw new IllegalArgumentException("sigDigit"); //$NON-NLS-1$ sigDigit = SigDigit; decimalFormat = null; } /** * Sets the maximum allowable length of the formattted number mantissa * before exponential notation is used. */ public void setMaxWidth(int mWidth) { if (mWidth < 3) throw new IllegalArgumentException("maxWidth"); //$NON-NLS-1$ maxWidth = mWidth; } /** * Sets the format style used. There are two output styles: Pure and * Standard scientific notation. Pure formatted numbers have precisely the * number of digits specified by the significant digits (sigDig) parameter * and always specify a Base 10 Exponential(E). Standard formated numbers * have the number of digits specified by the significant digits (sigDig) * parameter but will not have a Base 10 Exponential(E) if the number of * digits in the mantissa <= maxWidth. */ public void setScientificNotationStyle(boolean sciNote) { this.sciNote = sciNote; } // simplify method for taking log base 10 of x private final static double k = 1 / Math.log(10); private double Log10(double x) { if (x == 0) return 0; else return Math.log(x) * k; } private int resolveErrorSigDigit(double x, double dx) { // dx should never be negative dx = Math.abs(dx); // make x +ve cause negative doesn't effect sigdigits x = Math.abs(x); // these circumstances errorsigdit does equal sigdigit, excluding // infinity and Nan which are handled by format if (dx == 0 || Double.isInfinite(dx) || Double.isNaN(dx) || dx >= x) return sigDigit; // fail cases for log, method fails to handle if (x == 0 || Double.isInfinite(x) || Double.isNaN(x)) return sigDigit; // otherwise solve for cases when dx<x int log = (int) Math.round(Log10(dx / x));// always will return negative // number int errorsigdigit = sigDigit + log; if (errorsigdigit < 1) return 1; return errorsigdigit; } private DecimalFormat getDecimalFormat(int sigDig) { StringBuffer buffer = new StringBuffer("0."); //$NON-NLS-1$ for (int i = 1; i < sigDig; i++) buffer.append('0'); buffer.append("E0"); //$NON-NLS-1$ return new DecimalFormat(buffer.toString(), new DecimalFormatSymbols( Locale.US)); } /** * Format the number using scientific notation */ public String format(double d) { return format(d, sigDigit); } private String format(double d, int sigDig) { // Deal with a few special values first if (Double.isInfinite(d)) return maxWidth < 8 ? "INF" : "Infinite"; //$NON-NLS-1$ //$NON-NLS-2$ if (Double.isNaN(d)) return "NaN"; //$NON-NLS-1$ // Delegate the hard part to decimalFormat if (decimalFormat == null) decimalFormat = getDecimalFormat(sigDigit); DecimalFormat format = (sigDig == sigDigit) ? decimalFormat : getDecimalFormat(sigDig); String preliminaryResult = format.format(d); if (sciNote) return preliminaryResult; int ePos = preliminaryResult.indexOf('E'); int exponent = Integer.parseInt(preliminaryResult.substring(ePos + 1)) + 1; if (exponent > maxWidth) return preliminaryResult; if (exponent < -maxWidth + sigDig + 1) return preliminaryResult; // We need to fix up the result int sign = preliminaryResult.charAt(0) == '-' ? 1 : 0; StringBuffer result = new StringBuffer(preliminaryResult.substring( sign, sign + 1) + preliminaryResult.substring(sign + 2, ePos)); if (exponent >= sigDig) { for (int i = sigDig; i < exponent; i++) result.append('0'); } else if (exponent < 0) { result.insert(0, "."); //$NON-NLS-1$ for (int i = exponent; i < 0; i++) result.insert(1, '0'); } else { result.insert(exponent, '.'); } if (sign > 0) result.insert(0, '-'); return result.toString(); } // /** // * Format a number plus error using scientific notation // */ // public String formatError(double d,double dx) // { // return format(dx, resolveErrorSigDigit(d, dx)); // } }