A simple number formatting/ parsing class
/* * Copyright 2006 Robert Hanson <iamroberthanson AT gmail.com> * * Licensed 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.gwtwidgets.client.util; /** * <dl> * <dt><b>Title: </b><dd>Decimal Format</dd> * <p> * <dt><b>Description: </b><dd>This is a simple number formatting/ parsing class. Besides the simple number formatting * it also interprets shortcuts for thousand (k) million (m) and billion (b).<p/> * This Number Format class was adapted from the public domain javascript class found at * http://www.mredkj.com/javascript/nfdocs.html </dd> * <p> * </dl> * @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a> * @version $Revision: 0.0 $ */ public class NumberFormat { public static final String COMMA = ","; public static final String PERIOD = "."; public static final char DASH = '-'; public static final char LEFT_PAREN = '('; public static final char RIGHT_PAREN = ')'; // k/m/b Shortcuts public static final String THOUSAND = "k"; public static final String MILLION = "m"; public static final String BILLION = "b"; // currency position constants public static final int CUR_POS_LEFT_OUTSIDE = 0; public static final int CUR_POS_LEFT_INSIDE = 1; public static final int CUR_POS_RIGHT_INSIDE = 2; public static final int CUR_POS_RIGHT_OUTSIDE = 3; // negative format constants public static final int NEG_LEFT_DASH = 0; public static final int NEG_RIGHT_DASH = 1; public static final int NEG_PARENTHESIS = 2; // constant to signal that fixed precision is not to be used public static final int ARBITRARY_PRECISION = -1; private String inputDecimalSeparator = PERIOD; // decimal character used on the original string private boolean showGrouping = true; private String groupingSeparator = COMMA; // thousands grouping character private String decimalSeparator = PERIOD; // decimal point character private boolean showCurrencySymbol = false; private String currencySymbol = "$"; private int currencySymbolPosition = CUR_POS_LEFT_OUTSIDE; private int negativeFormat = NEG_LEFT_DASH; private boolean isNegativeRed = false; // wrap the output in html that will display red? private int decimalPrecision = 0; private boolean useFixedPrecision = false; private boolean truncate = false; // truncate to decimalPrecision rather than rounding? private boolean isPercentage = false; // should the result be displayed as a percentage? private NumberFormat() { } /** * returns the default instance of NumberFormat * @return */ public static NumberFormat getInstance () { NumberFormat nf = new NumberFormat(); return nf; } /** * Returns a currency instance of number format * @return */ public static NumberFormat getCurrencyInstance () { return getCurrencyInstance("$", true); } /** * Returns a currency instance of number format that uses curSymbol as the currency symbol * @param curSymbol * @return */ public static NumberFormat getCurrencyInstance (String curSymbol) { return getCurrencyInstance(curSymbol, true); } /** * Returns a currency instance of number format that uses curSymbol as the currency symbol * and either commas or periods as the thousands separator. * @param curSymbol Currency Symbol * @param useCommas true, uses commas as the thousands separator, false uses periods * @return */ public static NumberFormat getCurrencyInstance (String curSymbol, boolean useCommas) { NumberFormat nf = new NumberFormat(); nf.isCurrency(true); nf.setCurrencySymbol(curSymbol); if (!useCommas) { nf.setDecimalSeparator(COMMA); nf.setGroupingSeparator(PERIOD); } nf.setFixedPrecision(2); return nf; } /** * Returns an instance that formats numbers as integers. * @return */ public static NumberFormat getIntegerInstance () { NumberFormat nf = new NumberFormat(); nf.setShowGrouping(false); nf.setFixedPrecision(0); return nf; } public static NumberFormat getPercentInstance () { NumberFormat nf = new NumberFormat(); nf.isPercentage(true); nf.setFixedPrecision(2); nf.setShowGrouping(false); return nf; } public String format (String num) { return toFormatted(parse(num)); } public double parse (String num) { return asNumber(num, inputDecimalSeparator); } /** * Static routine that attempts to create a double out of the * supplied text. This routine is a bit smarter than Double.parseDouble() * @param num * @return */ public static double parseDouble (String num, String decimalChar) { return asNumber(num, decimalChar); } public static double parseDouble (String num) { return parseDouble(num, PERIOD); } public void setInputDecimalSeparator (String val) { inputDecimalSeparator = val == null ? PERIOD : val; } public void setNegativeFormat (int format) { negativeFormat = format; } public void setNegativeRed (boolean isRed) { isNegativeRed = isRed; } public void setShowGrouping (boolean show) { showGrouping = show; } public void setDecimalSeparator (String separator) { decimalSeparator = separator; } public void setGroupingSeparator (String separator) { groupingSeparator = separator; } public void isCurrency (boolean isC) { showCurrencySymbol = isC; } public void setCurrencySymbol (String symbol) { currencySymbol = symbol; } public void setCurrencyPosition (int cp) { currencySymbolPosition = cp; } public void isPercentage (boolean pct) { isPercentage = pct; } /** * Sets the number of fixed precision decimal places should be displayed. * To use arbitrary precision, setFixedPrecision(NumberFormat.ARBITRARY_PRECISION) * @param places */ public void setFixedPrecision (int places) { useFixedPrecision = places != ARBITRARY_PRECISION; this.decimalPrecision = places < 0 ? 0 : places; } /** * Causes the number to be truncated rather than rounded to its fixed precision. * @param trunc */ public void setTruncate (boolean trunc) { truncate = trunc; } /** * * @param preSep raw number as text * @param PERIOD incoming decimal point * @param decimalSeparator outgoing decimal point * @param groupingSeparator thousands separator * @return */ private String addSeparators (String preSep) { String nStr = preSep; int dpos = nStr.indexOf(PERIOD); String nStrEnd = ""; if (dpos != -1) { nStrEnd = decimalSeparator + nStr.substring(dpos + 1, nStr.length()); nStr = nStr.substring(0, dpos); } int l = nStr.length(); for (int i = l; i > 0; i--) { nStrEnd = nStr.charAt(i - 1) + nStrEnd; if (i != 1 && ((l - i + 1) % 3) == 0) nStrEnd = groupingSeparator + nStrEnd; } return nStrEnd; } protected String toFormatted(double num) { String nStr; if (isPercentage) num = num * 100; nStr = useFixedPrecision ? toFixed(Math.abs(getRounded(num)), decimalPrecision) : Double.toString(num); nStr = showGrouping ? addSeparators(nStr) : nStr.replaceAll("\\" + PERIOD, decimalSeparator); String c0 = ""; String n0 = ""; String c1 = ""; String n1 = ""; String n2 = ""; String c2 = ""; String n3 = ""; String c3 = ""; String negSignL = "" + ((negativeFormat == NEG_PARENTHESIS) ? LEFT_PAREN : DASH); String negSignR = "" + ((negativeFormat == NEG_PARENTHESIS) ? RIGHT_PAREN : DASH); if (currencySymbolPosition == CUR_POS_LEFT_OUTSIDE) { if (num < 0) { if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS) n1 = negSignL; if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS) n2 = negSignR; } if (showCurrencySymbol) c0 = currencySymbol; } else if (currencySymbolPosition == CUR_POS_LEFT_INSIDE) { if (num < 0) { if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS) n0 = negSignL; if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS) n3 = negSignR; } if (showCurrencySymbol) c1 = currencySymbol; } else if (currencySymbolPosition == CUR_POS_RIGHT_INSIDE) { if (num < 0) { if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS) n0 = negSignL; if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS) n3 = negSignR; } if (showCurrencySymbol) c2 = currencySymbol; } else if (currencySymbolPosition == CUR_POS_RIGHT_OUTSIDE) { if (num < 0) { if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS) n1 = negSignL; if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS) n2 = negSignR; } if (showCurrencySymbol) c3 = currencySymbol; } nStr = c0 + n0 + c1 + n1 + nStr + n2 + c2 + n3 + c3 + (isPercentage ? "%" : ""); if (isNegativeRed && num < 0) { nStr = "<font color='red'>" + nStr + "</font>"; } return nStr; } /** * javascript only rounds to whole numbers, so we need to shift our decimal right, * then round, then shift the decimal back left. * @param val * @return */ private double getRounded (double val) { double exp = Math.pow(10, decimalPrecision); double rounded = val * exp; if (truncate) rounded = rounded >= 0 ? Math.floor(rounded) : Math.ceil(rounded); else rounded = Math.round(rounded); return rounded / exp; } private static native String toFixed(double val, int places) /*-{ return val.toFixed(places); }-*/; private static double asNumber(String val, String inputDecimalValue) { String newVal = val; boolean isPercentage = false; // remove % if there is one if (newVal.indexOf('%') != -1) { newVal = newVal.replaceAll("\\%", ""); isPercentage = true; } // convert abbreviations for thousand, million and billion newVal = newVal.toLowerCase().replaceAll(BILLION, "000000000"); newVal = newVal.replaceAll(MILLION, "000000"); newVal = newVal.replaceAll(THOUSAND, "000"); // remove any characters that are not digit, decimal separator, +, -, (, ), e, or E String re = "[^\\" + inputDecimalValue + "\\d\\-\\+\\(\\)eE]"; newVal = newVal.replaceAll(re, ""); // ensure that the first decimal separator is a . and remove the rest. int index = newVal.indexOf(inputDecimalValue); if (index != -1) { newVal = newVal.substring(0, index) + PERIOD + (newVal.substring(index + inputDecimalValue.length()).replaceAll("\\" + inputDecimalValue, "")); } // convert right dash and paren negatives to left dash negative if (newVal.charAt(newVal.length() - 1) == DASH) { newVal = newVal.substring(0, newVal.length() - 1); newVal = DASH + newVal; } else if (newVal.charAt(0) == LEFT_PAREN && newVal.charAt(newVal.length() - 1) == RIGHT_PAREN) { newVal = newVal.substring(1, newVal.length() - 1); newVal = DASH + newVal; } Double parsed; try { parsed = new Double(newVal); if (parsed.isInfinite() || parsed.isNaN()) parsed = new Double(0); } catch (NumberFormatException e) { parsed = new Double(0); } return isPercentage ? parsed.doubleValue() / 100 : parsed.doubleValue(); } }