1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.gwtwidgets.client.util;
18
19 /***
20 * <dl>
21 * <dt><b>Title: </b><dd>Decimal Format</dd>
22 * <p>
23 * <dt><b>Description: </b><dd>This is a simple number formatting/ parsing class. Besides the simple number formatting
24 * it also interprets shortcuts for thousand (k) million (m) and billion (b).<p/>
25 * This Number Format class was adapted from the public domain javascript class found at
26 * http://www.mredkj.com/javascript/nfdocs.html </dd>
27 * <p>
28 * </dl>
29 * @author <a href="mailto:jasone@greenrivercomputing.com">Jason Essington</a>
30 * @version $Revision: 0.0 $
31 */
32
33 public class NumberFormat
34 {
35 public static final String COMMA = ",";
36 public static final String PERIOD = ".";
37 public static final char DASH = '-';
38 public static final char LEFT_PAREN = '(';
39 public static final char RIGHT_PAREN = ')';
40
41
42 public static final String THOUSAND = "k";
43 public static final String MILLION = "m";
44 public static final String BILLION = "b";
45
46
47 public static final int CUR_POS_LEFT_OUTSIDE = 0;
48 public static final int CUR_POS_LEFT_INSIDE = 1;
49 public static final int CUR_POS_RIGHT_INSIDE = 2;
50 public static final int CUR_POS_RIGHT_OUTSIDE = 3;
51
52
53 public static final int NEG_LEFT_DASH = 0;
54 public static final int NEG_RIGHT_DASH = 1;
55 public static final int NEG_PARENTHESIS = 2;
56
57
58 public static final int ARBITRARY_PRECISION = -1;
59
60 private String inputDecimalSeparator = PERIOD;
61
62 private boolean showGrouping = true;
63 private String groupingSeparator = COMMA;
64 private String decimalSeparator = PERIOD;
65
66 private boolean showCurrencySymbol = false;
67 private String currencySymbol = "$";
68 private int currencySymbolPosition = CUR_POS_LEFT_OUTSIDE;
69
70 private int negativeFormat = NEG_LEFT_DASH;
71 private boolean isNegativeRed = false;
72
73 private int decimalPrecision = 0;
74 private boolean useFixedPrecision = false;
75 private boolean truncate = false;
76
77 private boolean isPercentage = false;
78
79 private NumberFormat()
80 {
81 }
82
83 /***
84 * returns the default instance of NumberFormat
85 * @return
86 */
87 public static NumberFormat getInstance ()
88 {
89 NumberFormat nf = new NumberFormat();
90 return nf;
91 }
92
93 /***
94 * Returns a currency instance of number format
95 * @return
96 */
97 public static NumberFormat getCurrencyInstance ()
98 {
99 return getCurrencyInstance("$", true);
100 }
101
102 /***
103 * Returns a currency instance of number format that uses curSymbol as the currency symbol
104 * @param curSymbol
105 * @return
106 */
107 public static NumberFormat getCurrencyInstance (String curSymbol)
108 {
109 return getCurrencyInstance(curSymbol, true);
110 }
111
112 /***
113 * Returns a currency instance of number format that uses curSymbol as the currency symbol
114 * and either commas or periods as the thousands separator.
115 * @param curSymbol Currency Symbol
116 * @param useCommas true, uses commas as the thousands separator, false uses periods
117 * @return
118 */
119 public static NumberFormat getCurrencyInstance (String curSymbol, boolean useCommas)
120 {
121 NumberFormat nf = new NumberFormat();
122 nf.isCurrency(true);
123 nf.setCurrencySymbol(curSymbol);
124 if (!useCommas) {
125 nf.setDecimalSeparator(COMMA);
126 nf.setGroupingSeparator(PERIOD);
127 }
128 nf.setFixedPrecision(2);
129 return nf;
130 }
131
132 /***
133 * Returns an instance that formats numbers as integers.
134 * @return
135 */
136 public static NumberFormat getIntegerInstance ()
137 {
138 NumberFormat nf = new NumberFormat();
139 nf.setShowGrouping(false);
140 nf.setFixedPrecision(0);
141 return nf;
142 }
143
144 public static NumberFormat getPercentInstance ()
145 {
146 NumberFormat nf = new NumberFormat();
147 nf.isPercentage(true);
148 nf.setFixedPrecision(2);
149 nf.setShowGrouping(false);
150 return nf;
151 }
152
153 public String format (String num)
154 {
155 return toFormatted(parse(num));
156 }
157
158 public double parse (String num)
159 {
160 return asNumber(num, inputDecimalSeparator);
161 }
162
163 /***
164 * Static routine that attempts to create a double out of the
165 * supplied text. This routine is a bit smarter than Double.parseDouble()
166 * @param num
167 * @return
168 */
169 public static double parseDouble (String num, String decimalChar)
170 {
171 return asNumber(num, decimalChar);
172 }
173
174 public static double parseDouble (String num)
175 {
176 return parseDouble(num, PERIOD);
177 }
178
179 public void setInputDecimalSeparator (String val)
180 {
181 inputDecimalSeparator = val == null ? PERIOD : val;
182 }
183
184 public void setNegativeFormat (int format)
185 {
186 negativeFormat = format;
187 }
188
189 public void setNegativeRed (boolean isRed)
190 {
191 isNegativeRed = isRed;
192 }
193
194 public void setShowGrouping (boolean show)
195 {
196 showGrouping = show;
197 }
198
199 public void setDecimalSeparator (String separator)
200 {
201 decimalSeparator = separator;
202 }
203
204 public void setGroupingSeparator (String separator)
205 {
206 groupingSeparator = separator;
207 }
208
209 public void isCurrency (boolean isC)
210 {
211 showCurrencySymbol = isC;
212 }
213
214 public void setCurrencySymbol (String symbol)
215 {
216 currencySymbol = symbol;
217 }
218
219 public void setCurrencyPosition (int cp)
220 {
221 currencySymbolPosition = cp;
222 }
223
224 public void isPercentage (boolean pct)
225 {
226 isPercentage = pct;
227 }
228
229 /***
230 * Sets the number of fixed precision decimal places should be displayed.
231 * To use arbitrary precision, setFixedPrecision(NumberFormat.ARBITRARY_PRECISION)
232 * @param places
233 */
234 public void setFixedPrecision (int places)
235 {
236 useFixedPrecision = places != ARBITRARY_PRECISION;
237 this.decimalPrecision = places < 0 ? 0 : places;
238 }
239
240 /***
241 * Causes the number to be truncated rather than rounded to its fixed precision.
242 * @param trunc
243 */
244 public void setTruncate (boolean trunc)
245 {
246 truncate = trunc;
247 }
248
249 /***
250 *
251 * @param preSep raw number as text
252 * @param PERIOD incoming decimal point
253 * @param decimalSeparator outgoing decimal point
254 * @param groupingSeparator thousands separator
255 * @return
256 */
257 private String addSeparators (String preSep)
258 {
259 String nStr = preSep;
260 int dpos = nStr.indexOf(PERIOD);
261 String nStrEnd = "";
262 if (dpos != -1) {
263 nStrEnd = decimalSeparator + nStr.substring(dpos + 1, nStr.length());
264 nStr = nStr.substring(0, dpos);
265 }
266 int l = nStr.length();
267 for (int i = l; i > 0; i--) {
268 nStrEnd = nStr.charAt(i - 1) + nStrEnd;
269 if (i != 1 && ((l - i + 1) % 3) == 0) nStrEnd = groupingSeparator + nStrEnd;
270 }
271 return nStrEnd;
272 }
273
274 protected String toFormatted(double num)
275 {
276 String nStr;
277
278 if (isPercentage) num = num * 100;
279
280 nStr = useFixedPrecision ? toFixed(Math.abs(getRounded(num)), decimalPrecision) : Double.toString(num);
281
282 nStr = showGrouping ? addSeparators(nStr) : nStr.replaceAll("//" + PERIOD, decimalSeparator);
283
284 String c0 = "";
285 String n0 = "";
286 String c1 = "";
287 String n1 = "";
288 String n2 = "";
289 String c2 = "";
290 String n3 = "";
291 String c3 = "";
292
293 String negSignL = "" + ((negativeFormat == NEG_PARENTHESIS) ? LEFT_PAREN : DASH);
294 String negSignR = "" + ((negativeFormat == NEG_PARENTHESIS) ? RIGHT_PAREN : DASH);
295
296 if (currencySymbolPosition == CUR_POS_LEFT_OUTSIDE) {
297 if (num < 0) {
298 if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
299 n1 = negSignL;
300 if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
301 n2 = negSignR;
302 }
303 if (showCurrencySymbol) c0 = currencySymbol;
304 }
305 else if (currencySymbolPosition == CUR_POS_LEFT_INSIDE) {
306 if (num < 0) {
307 if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
308 n0 = negSignL;
309 if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
310 n3 = negSignR;
311 }
312 if (showCurrencySymbol) c1 = currencySymbol;
313 }
314 else if (currencySymbolPosition == CUR_POS_RIGHT_INSIDE) {
315 if (num < 0) {
316 if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
317 n0 = negSignL;
318 if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
319 n3 = negSignR;
320 }
321 if (showCurrencySymbol) c2 = currencySymbol;
322 }
323 else if (currencySymbolPosition == CUR_POS_RIGHT_OUTSIDE) {
324 if (num < 0) {
325 if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
326 n1 = negSignL;
327 if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
328 n2 = negSignR;
329 }
330 if (showCurrencySymbol) c3 = currencySymbol;
331 }
332 nStr = c0 + n0 + c1 + n1 + nStr + n2 + c2 + n3 + c3 + (isPercentage ? "%" : "");
333
334 if (isNegativeRed && num < 0) {
335 nStr = "<font color='red'>" + nStr + "</font>";
336 }
337
338 return nStr;
339 }
340
341 /***
342 * javascript only rounds to whole numbers, so we need to shift our decimal right,
343 * then round, then shift the decimal back left.
344 * @param val
345 * @return
346 */
347 private double getRounded (double val)
348 {
349 double exp = Math.pow(10, decimalPrecision);
350 double rounded = val * exp;
351 if (truncate)
352 rounded = rounded >= 0 ? Math.floor(rounded) : Math.ceil(rounded);
353 else
354 rounded = Math.round(rounded);
355 return rounded / exp;
356 }
357
358 private static native String toFixed(double val, int places)
359
360 ;
361
362 private static double asNumber(String val, String inputDecimalValue)
363 {
364 String newVal = val;
365 boolean isPercentage = false;
366
367 if (newVal.indexOf('%') != -1) {
368 newVal = newVal.replaceAll("//%", "");
369 isPercentage = true;
370 }
371
372
373 newVal = newVal.toLowerCase().replaceAll(BILLION, "000000000");
374 newVal = newVal.replaceAll(MILLION, "000000");
375 newVal = newVal.replaceAll(THOUSAND, "000");
376
377
378 String re = "[^//" + inputDecimalValue + "//d//-//+//(//)eE]";
379 newVal = newVal.replaceAll(re, "");
380
381
382 int index = newVal.indexOf(inputDecimalValue);
383 if (index != -1) {
384 newVal = newVal.substring(0, index)
385 + PERIOD
386 + (newVal.substring(index + inputDecimalValue.length()).replaceAll("//"
387 + inputDecimalValue, ""));
388 }
389
390
391 if (newVal.charAt(newVal.length() - 1) == DASH) {
392 newVal = newVal.substring(0, newVal.length() - 1);
393 newVal = DASH + newVal;
394 }
395 else if (newVal.charAt(0) == LEFT_PAREN
396 && newVal.charAt(newVal.length() - 1) == RIGHT_PAREN) {
397 newVal = newVal.substring(1, newVal.length() - 1);
398 newVal = DASH + newVal;
399 }
400
401 Double parsed;
402 try {
403 parsed = new Double(newVal);
404 if (parsed.isInfinite() || parsed.isNaN()) parsed = new Double(0);
405 }
406 catch (NumberFormatException e) {
407 parsed = new Double(0);
408 }
409
410 return isPercentage ? parsed.doubleValue() / 100 : parsed.doubleValue();
411 }
412 }
413