package 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 * @version $Id: ScientificFormat.java 8584 2006-08-10 23:06:37Z duns $ */ 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"); } /**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"); 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"); 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."); for (int i=1; i<sigDig; i++) buffer.append('0'); buffer.append("E0"); 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"; if (Double.isNaN(d)) return "NaN"; // 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,"."); 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)); // } }