Source code: com/hartmath/lib/Formatter.java
1 package com.hartmath.lib;
2
3
4 /**
5 *
6 * This class provides C-like formatting functions that allow
7 * programmers to convert an integer or floating point number
8 * into a string with a specified, width, precision and format.
9 * For instance this might be used to format monetary data to
10 * two decimal places.<P>
11 *
12 * Because Java does not have variable length argument lists like C,
13 * a different strategy must be employed. Each number is passed
14 * to a format() method along with formatting instructions. The format()
15 * method returns a formatted string which may then be passed to
16 * System.out.println() or other methods. In short this is more
17 * similar to C's sprintf() than to printf(). <P>
18 *
19 * There are a number of possible things one can do when a number
20 * will not fit inside the specified format. You can throw an exception,
21 * truncate the number, return an error string, or expand the width.
22 * Here I've chosen to expand the width. <P>
23 *
24 * The rounding of these precisions still needs work. Currently
25 * excess digits are merely truncated.<P>
26 *
27 * @Version: 0.1 of August 11, 1997
28 * @Author: Elliotte Rusty Harold (elharo@sunsite.unc.edu)
29 */
30
31 public class Formatter {
32
33 /* Since this class only contains static utility methods there's
34 no reason to allow instances of it to be created */
35
36 private Formatter() {
37 }
38
39 // longs and ints have different default widths,
40 // otherwise they're treated the same
41 // I used the maximum necessary widths for the type. An
42 // alternative soultion would be to use only as many characters
43 // as necessary
44
45 /**
46 *
47 * This method formats an int in 10 characters
48 * with no leading zeroes,
49 * right aligned, without +
50 * signs on positive numbers, filled out to the specified width
51 * with spaces
52 *
53 * @param d The number to be formatted
54 * @return A String containing a formatted representation of the number
55 */
56 public static String format(int i) {
57 return format(i, 11, -1, false, false, ' ', 10);
58 }
59
60 /**
61 *
62 * This method formats a long in 20 characters
63 * with no leading zeroes,
64 * right aligned, without +
65 * signs on positive numbers, filled out to the specified width
66 * with spaces
67 *
68 * @param d The number to be formatted
69 * @return A String containing a formatted representation of the number
70 */
71 public static String format(long l) {
72 return format(l, 20, -1, false, false, ' ', 10);
73 }
74
75 // Rather than providing all possible permutations of arguments
76 // (64 in this case) it's customary to provide the most common
77 // options in the most common order
78
79 /**
80 *
81 * This method formats an integer to a specified width,
82 * no leading zeroes,
83 * right aligned, without +
84 * signs on positive numbers, filled out to the specified width
85 * with spaces
86 *
87 * @param d The number to be formatted
88 * @param width The minimum number of characters in the returned string
89 * @return A String containing a formatted representation of the number
90 */
91 public static String format(long l, int width) {
92 return format(l, width, -1, false, false, ' ', 10);
93 }
94
95 /**
96 *
97 * This method formats an integer to a specified width,
98 * number of leading zeroes,
99 * right aligned, without +
100 * signs on positive numbers, filled out to the specified width
101 * with spaces
102 *
103 * @param d The number to be formatted
104 * @param width The minimum number of characters in the returned string
105 * @param precision The number of leading zeroes
106 * @return A String containing a formatted representation of the number
107 */
108 public static String format(long l, int width, int precision) {
109 return format(l, width, precision, false, false, ' ', 10);
110 }
111
112 /**
113 *
114 * This method formats an integer to a specified width,
115 * number of leading zeroes,
116 * aligned left or right, without +
117 * signs on positive numbers, filled out to the specified width
118 * with spaces
119 *
120 * @param d The number to be formatted
121 * @param width The minimum number of characters in the returned string
122 * @param precision The number of leading zeroes
123 * @param alignLeft True for left alignment in the width,
124 * false for right alignment
125 * @return A String containing a formatted representation of the number
126 */
127 public static String format(long l, int width, int precision,
128 boolean alignLeft) {
129 return format(l, width, precision, alignLeft, false, ' ', 10);
130 }
131
132 /**
133 *
134 * This method formats an integer to a specified width,
135 * number of leading zeroes,
136 * aligned left or right, with or without +
137 * signs on positive numbers, filled out to the specified width
138 * with spaces
139 *
140 * @param d The number to be formatted
141 * @param width The minimum number of characters in the returned string
142 * @param precision The number of leading zeroes
143 * @param alignLeft True for left alignment in the width,
144 * false for right alignment
145 * @param usePlus True if positive numbers are prefixed with + signs
146 * @return A String containing a formatted representation of the number
147 */
148 public static String format(long l, int width, int precision,
149 boolean alignLeft, boolean usePlus) {
150 return format(l, width, precision, alignLeft, usePlus, ' ', 10);
151 }
152
153 /**
154 *
155 * This method formats an integer to a specified width,
156 * number of leading zeroes,
157 * aligned left or right, with or without +
158 * signs on positive numbers, with a user-specified padding
159 * character used to fill out the number to the specified width.
160 *
161 * @param d The number to be formatted
162 * @param width The minimum number of characters in the returned string
163 * @param precision The number of leading zeroes
164 * @param alignLeft True for left alignment in the width,
165 * false for right alignment
166 * @param usePlus True if positive numbers are prefixed with + signs
167 * @param pad The character used to fill out the number
168 * to the specified width
169 * @return A String containing a formatted representation of the number
170 */
171 public static String format(long l, int width,
172 int precision, boolean alignLeft,
173 boolean usePlus, char pad) {
174 return format(l, width, precision, alignLeft, usePlus, pad, 10);
175 }
176
177 /**
178 *
179 * This method formats an integer to a specified width,
180 * number of leading zeroes,
181 * aligned left or right, with or without +
182 * signs on positive numbers, with a user-specified padding
183 * character used to fill out the number to the specified width,
184 * in a specified base
185 *
186 * @param d The number to be formatted
187 * @param width The minimum number of characters in the returned string
188 * @param precision The number of leading zeroes
189 * @param alignLeft True for left alignment in the width,
190 * false for right alignment
191 * @param usePlus True if positive numbers are prefixed with + signs
192 * @param pad The character used to fill out the number
193 * to the specified width
194 * @param radix The base in which numbers are formatted; e.g. 8, 10, 16
195 * @return A String containing a formatted representation of the number
196 */
197 public static String format(long l, int width,
198 int precision, boolean alignLeft,
199 boolean usePlus, char pad, int radix) {
200
201 // Do the initial conversion
202 String s = Long.toString(Math.abs(l), radix);
203
204 // add leading zeroes if necessary
205 if (precision > s.length()) {
206 int padLength = precision - s.length();
207 String zeroes = "";
208 for (int i = 0; i < padLength; i++) zeroes += '0';
209 s = zeroes + s;
210 }
211
212 // add the sign
213 if (usePlus && l > 0) s = '+' + s;
214 else if (l < 0) s = '-' + s;
215 else s = ' ' + s;
216
217 if (s.length() < width) {
218 int padLength = width - s.length();
219 String padding = "";
220 for (int i = 0; i < padLength; i++) padding += pad;
221 if (alignLeft) s += padding;
222 else s = padding + s;
223 }
224
225 return s;
226
227 }
228
229 /**
230 *
231 * This method formats a floating point number to a minimum of 12 characters,
232 * six decimal places, in decimal format,
233 * right aligned, without +
234 * signs on positive numbers. Spaces
235 * fill out the number to the specified width.
236 *
237 * @param d The number to be formatted
238 * @return A String containing a formatted representation of the number.
239 */
240 public static String format(double d) {
241 return format(d, 12, 6, false, false, false, ' ');
242 }
243
244 /**
245 *
246 * This method formats a floating point number to specified width,
247 * six decimal places, in decimal format,
248 * right aligned, without +
249 * signs on positive numbers. Spaces
250 * fill out the number to the specified width.
251 *
252 * @param d The number to be formatted
253 * @param width The minimum number of characters in the returned string
254 * @return A String containing a formatted representation of the number.
255 */
256 public static String format(double d, int width) {
257 return format(d, width, 6, false, false, false, ' ');
258 }
259
260 /**
261 *
262 * This method formats a floating point number to specified width,
263 * number of decimal places, in decimal format,
264 * right aligned, without +
265 * signs on positive numbers. Spaces
266 * fill out the number to the specified width.
267 *
268 * @param d The number to be formatted
269 * @param width The minimum number of characters in the returned string
270 * @param precision The number of digits after the decimal point
271 * @return A String containing a formatted representation of the number.
272 */
273 public static String format(double d, int width, int precision) {
274 return format(d, width, precision, false, false, false, ' ');
275 }
276
277 /**
278 *
279 * This method formats a floating point number to specified width,
280 * number of decimal places, in exponential or decimal format,
281 * right aligned, without +
282 * signs on positive numbers. Spaces
283 * fill out the number to the specified width.
284 *
285 * @param d The number to be formatted
286 * @param width The minimum number of characters in the returned string
287 * @param precision The number of digits after the decimal point
288 * @param exponential true if the number is to be displayed in
289 * exponential notation, e.g. 3.50E+09, false otherwise
290 * @return A String containing a formatted representation of the number.
291 */
292 public static String format(double d, int width,
293 int precision, boolean exponential) {
294 return format(d, width, precision, exponential, false, false, ' ');
295 }
296
297 /**
298 *
299 * This method formats a floating point number to a specified width,
300 * number of decimal places, in exponential or decimal format,
301 * aligned left or right, without +
302 * signs on positive numbers. Spaces
303 * fill out the number to the specified width.
304 *
305 * @param d The number to be formatted
306 * @param width The minimum number of characters in the returned string
307 * @param precision The number of digits after the decimal point
308 * @param exponential true if the number is to be displayed in
309 * exponential notation, e.g. 3.50E+09, false otherwise
310 * @param alignLeft True for left alignment in the width,
311 * false for right alignment
312 * @return A String containing a formatted representation of the number.
313 */
314 public static String format(double d, int width,
315 int precision, boolean exponential, boolean alignLeft) {
316 return format(d, width, precision, exponential, alignLeft, false, ' ');
317 }
318
319 /**
320 *
321 * This method formats a floating point number to a specified width,
322 * number of decimal places, in exponential or decimal format,
323 * aligned left or right, with or without +
324 * signs on positive numbers. Spaces
325 * fill out the number to the specified width.
326 *
327 * @param d The number to be formatted
328 * @param width The minimum number of characters in the returned string
329 * @param precision The number of digits after the decimal point
330 * @param exponential true if the number is to be displayed in
331 * exponential notation, e.g. 3.50E+09, false otherwise
332 * @param alignLeft True for left alignment in the width,
333 * false for right alignment
334 * @param usePlus True if positive numbers are prefixed with + signs
335 * @return A String containing a formatted representation of the number.
336 */
337 public static String format(double d, int width,
338 int precision, boolean exponential, boolean alignLeft,
339 boolean usePlus) {
340 return format(d, width, precision, exponential, alignLeft, usePlus, ' ');
341 }
342
343 // need to round better when truncating precision
344 /**
345 *
346 * This method formats a floating point number to a specified width,
347 * number of decimal places, in exponential or decimal format,
348 * aligned left or right, with or without +
349 * signs on positive numbers, and with a user-specified padding
350 * character used to fill out the number to the specified width.
351 *
352 * @param d The number to be formatted
353 * @param width The minimum number of characters in the returned string
354 * @param precision The number of digits after the decimal point
355 * @param exponential true if the number is to be displayed in
356 * exponential notation, e.g. 3.50E+09, false otherwise
357 * @param alignLeft True for left alignment in the width,
358 * false for right alignment
359 * @param usePlus True if positive numbers are prefixed with + signs
360 * @param pad The character used to fill out the number
361 * to the specified width
362 * @return A String containing a formatted representation of the number.
363 */
364 public static String format(double d, int width,
365 int precision, boolean exponential, boolean alignLeft,
366 boolean usePlus, char pad) {
367
368 String s = "";
369
370 // find mantissa and exponent
371 double y = Math.abs(d);
372 String mantissa;
373 int exponent;
374
375 if (Double.isInfinite(y)) {
376 s = "Inf";
377 }
378 else if (Double.isNaN(y)) {
379 s = "NaN";
380 }
381 else { // find mantissa and exponent
382 s = Double.toString(y);
383 int e = s.toUpperCase().indexOf('E');
384 if (e != -1) { // in exponential form
385 mantissa = s.substring(0, e);
386 // remove the decimal point
387 // which is always to the right of the first digit
388 mantissa = mantissa.charAt(0) + mantissa.substring(2);
389 exponent = Integer.parseInt(s.substring(e+1));
390 } // end exponential form
391 else { // in decimal form to start with
392 int decimalpoint = s.indexOf('.');
393 mantissa = s.substring(0, decimalpoint) + s.substring(decimalpoint+1);
394 // find first non-zero digit
395 int r = -1;
396 for (int i = 0; i < s.length(); i++) {
397 char c = s.charAt(i);
398 if (c != '0' && Character.isDigit(c)) {
399 r = i;
400 break;
401 }
402 }
403 if (r == -1) { // this has to zero
404 exponent = 0;
405 }
406 else {
407 if (r < decimalpoint) exponent = decimalpoint - r - 1;
408 else exponent = decimalpoint - r;
409 }
410 } // end decimal form
411
412 if (exponential) {
413 while (mantissa.length() < precision + 1) mantissa += '0';
414 s = mantissa.charAt(0) + "." + mantissa.substring(1,precision+1);
415 String exponentString = "E";
416 if (exponent >= 0) exponentString += "+";
417 else exponentString += "-";
418 if (Math.abs(exponent) < 10) exponentString += "0";
419 exponentString += Math.abs(exponent);
420 s += exponentString;
421 }
422 else { // use decimal notation
423 // convert the exponent to a decimal point
424 // with extra zeroes if necessary
425 if (exponent < 0) {
426 String prefix = "0.";
427 for (int i = exponent; i < -1; i++) prefix += '0';
428 s = prefix + mantissa;
429 if (precision >= 0) s = s.substring(0, precision+3);
430 }
431 else if (exponent < mantissa.length()-1 ) {
432 s = mantissa.substring(0, exponent+1) + '.'
433 + mantissa.substring(exponent+1);
434 if (precision >= 0) {
435 int end = exponent + 1 + precision;
436 if (end < s.length()) s = s.substring(0, end+1);
437 else {
438 for (int i = 0; i < end - s.length() + 1; i++) s += '0';
439 }
440 }
441 }
442 else { // exponent >= mantissa.length()
443 s = mantissa;
444 for (int i = 0; i < exponent - mantissa.length() + 1; i++) s += '0';
445 s += ".";
446 for (int i = 0; i < precision; i++) s += '0';
447 }
448 }
449 }
450
451
452 // add the sign
453 if (d < 0) s = '-' + s;
454 else if (d == 0.0) s = ' ' + s;
455 // > 0 is not superfluous due to NaN
456 else if (usePlus && d > 0) s = '+' + s;
457
458 // expand the width
459 if (s.length() < width) {
460 int padLength = width - s.length();
461 String padding = "";
462 for (int i = 0; i < padLength; i++) padding += pad;
463 if (alignLeft) s += padding;
464 else s = padding + s;
465 }
466
467 return s;
468
469 }
470
471
472 // still need to handle %g
473 /**
474 *
475 * This method formats a floating point number to a specified width,
476 * number of leading zeroes,
477 * aligned left or right, with or without +
478 * signs on positive numbers, with a user-specified padding
479 * character used to fill out the number to the specified width,
480 * in a specified base using a C-like formatting string such as
481 * %5f, %15.4F, %-15.3E, %.10f, and so on.<P>
482 * Currently only
483 * f, F, e, and E formats are supported. g and G formats are not supported
484 *
485 * @param d The number to be formatted
486 * @param s A String containing formatting instructions
487 * @return A String containing a formatted representation of the number
488 */
489 public static String format(double d, String s) {
490
491 int width = -1;
492 int precision = 6;
493 boolean exponential = false;
494 boolean alignLeft = false;
495 boolean usePlus = false;
496
497 if (s.charAt(0) == '%') s = s.substring(1);
498 if (s.charAt(0) == '-') {
499 alignLeft = true;
500 s = s.substring(1);
501 }
502 if (s.toUpperCase().endsWith("E")) exponential = true;
503 int period = s.indexOf(".");
504 if (period != -1) {
505 try {
506 String prec = s.substring(period+1, s.length()-1);
507 precision = Integer.parseInt(prec);
508 }
509 catch (Exception e) {
510 }
511 try {
512 String w = s.substring(0, period);
513 width = Integer.parseInt(w);
514 }
515 catch (Exception e) {
516 }
517 } // end if
518 else {
519 try {
520 String w = s.substring(0, s.length()-1);
521 width = Integer.parseInt(w);
522 }
523 catch (Exception e) {
524 }
525 } // end else
526
527 return format(d, width, precision, exponential, alignLeft, usePlus);
528
529 }
530
531 /**
532 *
533 * This method formats an integer to a specified width,
534 * number of leading zeroes,
535 * aligned left or right, with or without +
536 * signs on positive numbers,
537 * in octal, decimal, or hexadecimal
538 * using a C-like formatting string such as
539 * %5d, %15.4d, %-15.3x, %.10X, and so on.<P>
540 *
541 * @param d The number to be formatted
542 * @param s A String containing formatting instructions
543 * @return A String containing a formatted representation of the number
544 */
545 public static String format(long l, String s) {
546
547 int width = -1;
548 int precision = 0;
549 int radix = 10;
550 boolean alignLeft = false;
551 boolean usePlus = false;
552
553 if (s.charAt(0) == '%') s = s.substring(1);
554 if (s.charAt(0) == '-') {
555 alignLeft = true;
556 s = s.substring(1);
557 }
558 if (s.toUpperCase().endsWith("X")) radix = 16;
559 else if (s.toUpperCase().endsWith("O")) radix = 8;
560
561 int period = s.indexOf(".");
562 if (period != -1) {
563 try {
564 String prec = s.substring(period+1, s.length()-1);
565 precision = Integer.parseInt(prec);
566 }
567 catch (Exception e) {
568 }
569 try {
570 String w = s.substring(0, period);
571 width = Integer.parseInt(w);
572 }
573 catch (Exception e) {
574 }
575 } // end if
576 else {
577 try {
578 String w = s.substring(0, s.length()-1);
579 width = Integer.parseInt(w);
580 }
581 catch (Exception e) {
582 }
583 } // end else
584
585 return format(l, width, precision, alignLeft, usePlus, ' ', radix);
586
587 }
588
589 public static void main(String[] args) {
590
591 if (args == null || args.length == 0) test();
592 else {
593 for (int i = 0; i < args.length; i++) {
594 long n = 0;
595 double x = 0;
596 try {
597 n = Long.valueOf(args[i]).longValue();
598 testInteger(n);
599 }
600 catch (NumberFormatException e1) {
601 try {
602 x = Double.valueOf(args[i]).doubleValue();
603 testFloatingPoint(x);
604 }
605 catch (NumberFormatException e2) {
606 System.err.println(args[i] + "is not a number.");
607 }
608 }
609 } // end for
610
611 } // end else
612
613 } // end main()
614
615
616 private static void test() {
617
618 for (int i = -10; i <= 10; i+= 5) testInteger(i);
619 for (int i = 1; i < 10; i++) {
620 testInteger((long) Math.floor(Math.random() * 1000000));
621 }
622 testFloatingPoint(7.5);
623 testFloatingPoint(Math.PI);
624 testFloatingPoint(Math.E);
625 testFloatingPoint(897.6);
626 testFloatingPoint(765);
627 testFloatingPoint(-98.765);
628 testFloatingPoint(65.4E23);
629 testFloatingPoint(65.4E-23);
630 testFloatingPoint(-65.4E23);
631 testFloatingPoint(-65.4E-23);
632 testFloatingPoint(0.0);
633 testFloatingPoint(1);
634 testFloatingPoint(Math.sqrt(2));
635 testFloatingPoint(Double.POSITIVE_INFINITY);
636 testFloatingPoint(Double.NEGATIVE_INFINITY);
637 testFloatingPoint(Double.NaN);
638 }
639
640 private static void testInteger(long n) {
641
642 String s;
643 s = format(n, 16, 4, false, false, ' ');
644 System.out.print(s);
645 s = format(n, 10, 4, false, false, ' ');
646 System.out.print(s);
647 s = format(n, 4, -1, true, true, '_');
648 System.out.print(s);
649 s = format(n, -1, -1, true, true, ' ');
650 System.out.print(s);
651 System.out.println();
652
653 }
654
655 // should also print non-formatted value for comparison
656
657 private static void testFloatingPoint(double x) {
658
659 System.out.println("Unformatted: " + x);
660 String s = format(x);
661 System.out.println(s);
662 s = format(x, 10, 4, true);
663 System.out.println(s);
664
665 }
666
667
668 }
669