package cafeaulait.io; /** * * This class provides C-like formatting functions that allow * programmers to convert an integer or floating point number * into a string with a specified, width, precision and format. * For instance this might be used to format monetary data to * two decimal places.
* * Because Java does not have variable length argument lists like C, * a different strategy must be employed. Each number is passed * to a format() method along with formatting instructions. The format() * method returns a formatted string which may then be passed to * System.out.println() or other methods. In short this is more * similar to C's sprintf() than to printf().
* * There are a number of possible things one can do when a number * will not fit inside the specified format. You can throw an exception, * truncate the number, return an error string, or expand the width. * Here I've chosen to expand the width.
* * The rounding of these precisions still needs work. Currently * excess digits are merely truncated.
* * @Version: 0.1 of August 11, 1997 * @Author: Elliotte Rusty Harold (elharo@sunsite.unc.edu) */ public class Formatter { /* Since this class only contains static utility methods there's no reason to allow instances of it to be created */ private Formatter() { } // longs and ints have different default widths, // otherwise they're treated the same // I used the maximum necessary widths for the type. An // alternative soultion would be to use only as many characters // as necessary /** * * This method formats an int in 10 characters * with no leading zeroes, * right aligned, without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @return A String containing a formatted representation of the number */ public static String format(int i) { return format(i, 11, -1, false, false, ' ', 10); } /** * * This method formats a long in 20 characters * with no leading zeroes, * right aligned, without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @return A String containing a formatted representation of the number */ public static String format(long l) { return format(l, 20, -1, false, false, ' ', 10); } // Rather than providing all possible permutations of arguments // (64 in this case) it's customary to provide the most common // options in the most common order /** * * This method formats an integer to a specified width, * no leading zeroes, * right aligned, without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @return A String containing a formatted representation of the number */ public static String format(long l, int width) { return format(l, width, -1, false, false, ' ', 10); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * right aligned, without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of leading zeroes * @return A String containing a formatted representation of the number */ public static String format(long l, int width, int precision) { return format(l, width, precision, false, false, ' ', 10); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * aligned left or right, without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of leading zeroes * @param alignLeft True for left alignment in the width, * false for right alignment * @return A String containing a formatted representation of the number */ public static String format(long l, int width, int precision, boolean alignLeft) { return format(l, width, precision, alignLeft, false, ' ', 10); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * aligned left or right, with or without + * signs on positive numbers, filled out to the specified width * with spaces * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of leading zeroes * @param alignLeft True for left alignment in the width, * false for right alignment * @param usePlus True if positive numbers are prefixed with + signs * @return A String containing a formatted representation of the number */ public static String format(long l, int width, int precision, boolean alignLeft, boolean usePlus) { return format(l, width, precision, alignLeft, usePlus, ' ', 10); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * aligned left or right, with or without + * signs on positive numbers, with a user-specified padding * character used to fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of leading zeroes * @param alignLeft True for left alignment in the width, * false for right alignment * @param usePlus True if positive numbers are prefixed with + signs * @param pad The character used to fill out the number * to the specified width * @return A String containing a formatted representation of the number */ public static String format(long l, int width, int precision, boolean alignLeft, boolean usePlus, char pad) { return format(l, width, precision, alignLeft, usePlus, pad, 10); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * aligned left or right, with or without + * signs on positive numbers, with a user-specified padding * character used to fill out the number to the specified width, * in a specified base * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of leading zeroes * @param alignLeft True for left alignment in the width, * false for right alignment * @param usePlus True if positive numbers are prefixed with + signs * @param pad The character used to fill out the number * to the specified width * @param radix The base in which numbers are formatted; e.g. 8, 10, 16 * @return A String containing a formatted representation of the number */ public static String format(long l, int width, int precision, boolean alignLeft, boolean usePlus, char pad, int radix) { // Do the initial conversion String s = Long.toString(Math.abs(l), radix); // add leading zeroes if necessary if (precision > s.length()) { int padLength = precision - s.length(); String zeroes = ""; for (int i = 0; i < padLength; i++) zeroes += '0'; s = zeroes + s; } // add the sign if (usePlus && l > 0) s = '+' + s; else if (l < 0) s = '-' + s; else s = ' ' + s; if (s.length() < width) { int padLength = width - s.length(); String padding = ""; for (int i = 0; i < padLength; i++) padding += pad; if (alignLeft) s += padding; else s = padding + s; } return s; } /** * * This method formats a floating point number to a minimum of 12 characters, * six decimal places, in decimal format, * right aligned, without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @return A String containing a formatted representation of the number. */ public static String format(double d) { return format(d, 12, 6, false, false, false, ' '); } /** * * This method formats a floating point number to specified width, * six decimal places, in decimal format, * right aligned, without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @return A String containing a formatted representation of the number. */ public static String format(double d, int width) { return format(d, width, 6, false, false, false, ' '); } /** * * This method formats a floating point number to specified width, * number of decimal places, in decimal format, * right aligned, without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of digits after the decimal point * @return A String containing a formatted representation of the number. */ public static String format(double d, int width, int precision) { return format(d, width, precision, false, false, false, ' '); } /** * * This method formats a floating point number to specified width, * number of decimal places, in exponential or decimal format, * right aligned, without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of digits after the decimal point * @param exponential true if the number is to be displayed in * exponential notation, e.g. 3.50E+09, false otherwise * @return A String containing a formatted representation of the number. */ public static String format(double d, int width, int precision, boolean exponential) { return format(d, width, precision, exponential, false, false, ' '); } /** * * This method formats a floating point number to a specified width, * number of decimal places, in exponential or decimal format, * aligned left or right, without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of digits after the decimal point * @param exponential true if the number is to be displayed in * exponential notation, e.g. 3.50E+09, false otherwise * @param alignLeft True for left alignment in the width, * false for right alignment * @return A String containing a formatted representation of the number. */ public static String format(double d, int width, int precision, boolean exponential, boolean alignLeft) { return format(d, width, precision, exponential, alignLeft, false, ' '); } /** * * This method formats a floating point number to a specified width, * number of decimal places, in exponential or decimal format, * aligned left or right, with or without + * signs on positive numbers. Spaces * fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of digits after the decimal point * @param exponential true if the number is to be displayed in * exponential notation, e.g. 3.50E+09, false otherwise * @param alignLeft True for left alignment in the width, * false for right alignment * @param usePlus True if positive numbers are prefixed with + signs * @return A String containing a formatted representation of the number. */ public static String format(double d, int width, int precision, boolean exponential, boolean alignLeft, boolean usePlus) { return format(d, width, precision, exponential, alignLeft, usePlus, ' '); } // need to round better when truncating precision /** * * This method formats a floating point number to a specified width, * number of decimal places, in exponential or decimal format, * aligned left or right, with or without + * signs on positive numbers, and with a user-specified padding * character used to fill out the number to the specified width. * * @param d The number to be formatted * @param width The minimum number of characters in the returned string * @param precision The number of digits after the decimal point * @param exponential true if the number is to be displayed in * exponential notation, e.g. 3.50E+09, false otherwise * @param alignLeft True for left alignment in the width, * false for right alignment * @param usePlus True if positive numbers are prefixed with + signs * @param pad The character used to fill out the number * to the specified width * @return A String containing a formatted representation of the number. */ public static String format(double d, int width, int precision, boolean exponential, boolean alignLeft, boolean usePlus, char pad) { String s = ""; // find mantissa and exponent double y = Math.abs(d); String mantissa; int exponent; if (Double.isInfinite(y)) { s = "Inf"; } else if (Double.isNaN(y)) { s = "NaN"; } else { // find mantissa and exponent s = Double.toString(y); int e = s.toUpperCase().indexOf('E'); if (e != -1) { // in exponential form mantissa = s.substring(0, e); // remove the decimal point // which is always to the right of the first digit mantissa = mantissa.charAt(0) + mantissa.substring(2); exponent = Integer.parseInt(s.substring(e+1)); } // end exponential form else { // in decimal form to start with int decimalpoint = s.indexOf('.'); mantissa = s.substring(0, decimalpoint) + s.substring(decimalpoint+1); // find first non-zero digit int r = -1; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c != '0' && Character.isDigit(c)) { r = i; break; } } if (r == -1) { // this has to zero exponent = 0; } else { if (r < decimalpoint) exponent = decimalpoint - r - 1; else exponent = decimalpoint - r; } } // end decimal form if (exponential) { while (mantissa.length() < precision + 1) mantissa += '0'; s = mantissa.charAt(0) + "." + mantissa.substring(1,precision+1); String exponentString = "E"; if (exponent >= 0) exponentString += "+"; else exponentString += "-"; if (Math.abs(exponent) < 10) exponentString += "0"; exponentString += Math.abs(exponent); s += exponentString; } else { // use decimal notation // convert the exponent to a decimal point // with extra zeroes if necessary if (exponent < 0) { String prefix = "0."; for (int i = exponent; i < -1; i++) prefix += '0'; s = prefix + mantissa; if (precision >= 0) s = s.substring(0, precision+3); } else if (exponent < mantissa.length()-1 ) { s = mantissa.substring(0, exponent+1) + '.' + mantissa.substring(exponent+1); if (precision >= 0) { int end = exponent + 1 + precision; if (end < s.length()) s = s.substring(0, end+1); else { for (int i = 0; i < end - s.length() + 1; i++) s += '0'; } } } else { // exponent >= mantissa.length() s = mantissa; for (int i = 0; i < exponent - mantissa.length() + 1; i++) s += '0'; s += "."; for (int i = 0; i < precision; i++) s += '0'; } } } // add the sign if (d < 0) s = '-' + s; else if (d == 0.0) s = ' ' + s; // > 0 is not superfluous due to NaN else if (usePlus && d > 0) s = '+' + s; // expand the width if (s.length() < width) { int padLength = width - s.length(); String padding = ""; for (int i = 0; i < padLength; i++) padding += pad; if (alignLeft) s += padding; else s = padding + s; } return s; } // still need to handle %g /** * * This method formats a floating point number to a specified width, * number of leading zeroes, * aligned left or right, with or without + * signs on positive numbers, with a user-specified padding * character used to fill out the number to the specified width, * in a specified base using a C-like formatting string such as * %5f, %15.4F, %-15.3E, %.10f, and so on.
* Currently only * f, F, e, and E formats are supported. g and G formats are not supported * * @param d The number to be formatted * @param s A String containing formatting instructions * @return A String containing a formatted representation of the number */ public static String format(double d, String s) { int width = -1; int precision = 6; boolean exponential = false; boolean alignLeft = false; boolean usePlus = false; if (s.charAt(0) == '%') s = s.substring(1); if (s.charAt(0) == '-') { alignLeft = true; s = s.substring(1); } if (s.toUpperCase().endsWith("E")) exponential = true; int period = s.indexOf("."); if (period != -1) { try { String prec = s.substring(period+1, s.length()-1); precision = Integer.parseInt(prec); } catch (Exception e) { } try { String w = s.substring(0, period); width = Integer.parseInt(w); } catch (Exception e) { } } // end if else { try { String w = s.substring(0, s.length()-1); width = Integer.parseInt(w); } catch (Exception e) { } } // end else return format(d, width, precision, exponential, alignLeft, usePlus); } /** * * This method formats an integer to a specified width, * number of leading zeroes, * aligned left or right, with or without + * signs on positive numbers, * in octal, decimal, or hexadecimal * using a C-like formatting string such as * %5d, %15.4d, %-15.3x, %.10X, and so on.
* * @param d The number to be formatted * @param s A String containing formatting instructions * @return A String containing a formatted representation of the number */ public static String format(long l, String s) { int width = -1; int precision = 0; int radix = 10; boolean alignLeft = false; boolean usePlus = false; if (s.charAt(0) == '%') s = s.substring(1); if (s.charAt(0) == '-') { alignLeft = true; s = s.substring(1); } if (s.toUpperCase().endsWith("X")) radix = 16; else if (s.toUpperCase().endsWith("O")) radix = 8; int period = s.indexOf("."); if (period != -1) { try { String prec = s.substring(period+1, s.length()-1); precision = Integer.parseInt(prec); } catch (Exception e) { } try { String w = s.substring(0, period); width = Integer.parseInt(w); } catch (Exception e) { } } // end if else { try { String w = s.substring(0, s.length()-1); width = Integer.parseInt(w); } catch (Exception e) { } } // end else return format(l, width, precision, alignLeft, usePlus, ' ', radix); } public static void main(String[] args) { if (args == null || args.length == 0) test(); else { for (int i = 0; i < args.length; i++) { long n = 0; double x = 0; try { n = Long.valueOf(args[i]).longValue(); testInteger(n); } catch (NumberFormatException e1) { try { x = Double.valueOf(args[i]).doubleValue(); testFloatingPoint(x); } catch (NumberFormatException e2) { System.err.println(args[i] + "is not a number."); } } } // end for } // end else } // end main() private static void test() { for (int i = -10; i <= 10; i+= 5) testInteger(i); for (int i = 1; i < 10; i++) { testInteger((long) Math.floor(Math.random() * 1000000)); } testFloatingPoint(7.5); testFloatingPoint(Math.PI); testFloatingPoint(Math.E); testFloatingPoint(897.6); testFloatingPoint(765); testFloatingPoint(-98.765); testFloatingPoint(65.4E23); testFloatingPoint(65.4E-23); testFloatingPoint(-65.4E23); testFloatingPoint(-65.4E-23); testFloatingPoint(0.0); testFloatingPoint(1); testFloatingPoint(Math.sqrt(2)); testFloatingPoint(Double.POSITIVE_INFINITY); testFloatingPoint(Double.NEGATIVE_INFINITY); testFloatingPoint(Double.NaN); } private static void testInteger(long n) { String s; s = format(n, 16, 4, false, false, ' '); System.out.print(s); s = format(n, 10, 4, false, false, ' '); System.out.print(s); s = format(n, 4, -1, true, true, '_'); System.out.print(s); s = format(n, -1, -1, true, true, ' '); System.out.print(s); System.out.println(); } // should also print non-formatted value for comparison private static void testFloatingPoint(double x) { System.out.println("Unformatted: " + x); String s = format(x); System.out.println(s); s = format(x, 10, 4, true); System.out.println(s); } }