// Fmt - some simple single-arg sprintf-like routines // // Copyright (C) 1996 by Jef Poskanzer . All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // // Visit the ACME Labs Java page for up-to-date versions of this and other // fine Java utilities: http://www.acme.com/java/ package Acme; /// Some simple single-arg sprintf-like routines. //

// It is apparently impossible to declare a Java method that accepts // variable numbers of any type of argument. You can declare it to take // Objects, but numeric variables and constants are not in fact Objects. //

// However, using the built-in string concatenation, it's almost as // convenient to make a series of single-argument formatting routines. //

// Fmt can format the following types: //

// byte short int long float double char String Object //
// For each type there is a set of overloaded methods, each returning // a formatted String. There's the plain formatting version: //
// Fmt.fmt( x )
// 
// There's a version specifying a minimum field width: //
// Fmt.fmt( x, minWidth )
// 
// And there's a version that takes flags: //
// Fmt.fmt( x, minWidth, flags )
// 
// Currently available flags are: //
// Fmt.ZF - zero-fill
// Fmt.LJ - left justify
// Fmt.HX - hexadecimal
// Fmt.OC - octal
// 
// The HX and OC flags imply unsigned output. //

// For doubles and floats, there's a significant-figures parameter before // the flags: //

// Fmt.fmt( d )
// Fmt.fmt( d, minWidth )
// Fmt.fmt( d, minWidth, sigFigs )
// Fmt.fmt( d, minWidth, sigFigs, flags )
// 
//

// Fetch the software.
// Fetch the entire Acme package. //


// Similar classes: // public class Fmt { // Flags. /// Zero-fill. public static final int ZF = 1; /// Left justify. public static final int LJ = 2; /// Hexadecimal. public static final int HX = 4; /// Octal. public static final int OC = 8; // Was a number - internal use. private static final int WN = 16; // byte public static String fmt( byte b ) { return fmt( b, 0, 0 ); } public static String fmt( byte b, int minWidth ) { return fmt( b, minWidth, 0 ); } public static String fmt( byte b, int minWidth, int flags ) { boolean hexadecimal = ( ( flags & HX ) != 0 ); boolean octal = ( ( flags & OC ) != 0 ); if ( hexadecimal ) return fmt( Integer.toString( b & 0xff, 16 ), minWidth, flags|WN ); else if ( octal ) return fmt( Integer.toString( b & 0xff, 8 ), minWidth, flags|WN ); else return fmt( Integer.toString( b & 0xff ), minWidth, flags|WN ); } // short public static String fmt( short s ) { return fmt( s, 0, 0 ); } public static String fmt( short s, int minWidth ) { return fmt( s, minWidth, 0 ); } public static String fmt( short s, int minWidth, int flags ) { boolean hexadecimal = ( ( flags & HX ) != 0 ); boolean octal = ( ( flags & OC ) != 0 ); if ( hexadecimal ) return fmt( Integer.toString( s & 0xffff, 16 ), minWidth, flags|WN ); else if ( octal ) return fmt( Integer.toString( s & 0xffff, 8 ), minWidth, flags|WN ); else return fmt( Integer.toString( s ), minWidth, flags|WN ); } // int public static String fmt( int i ) { return fmt( i, 0, 0 ); } public static String fmt( int i, int minWidth ) { return fmt( i, minWidth, 0 ); } public static String fmt( int i, int minWidth, int flags ) { boolean hexadecimal = ( ( flags & HX ) != 0 ); boolean octal = ( ( flags & OC ) != 0 ); if ( hexadecimal ) return fmt( Long.toString( i & 0xffffffffL, 16 ), minWidth, flags|WN ); else if ( octal ) return fmt( Long.toString( i & 0xffffffffL, 8 ), minWidth, flags|WN ); else return fmt( Integer.toString( i ), minWidth, flags|WN ); } // long public static String fmt( long l ) { return fmt( l, 0, 0 ); } public static String fmt( long l, int minWidth ) { return fmt( l, minWidth, 0 ); } public static String fmt( long l, int minWidth, int flags ) { boolean hexadecimal = ( ( flags & HX ) != 0 ); boolean octal = ( ( flags & OC ) != 0 ); if ( hexadecimal ) { if ( ( l & 0xf000000000000000L ) != 0 ) return fmt( Long.toString( l >>> 60, 16 ) + fmt( l & 0x0fffffffffffffffL, 15, HX|ZF ), minWidth, flags|WN ); else return fmt( Long.toString( l, 16 ), minWidth, flags|WN ); } else if ( octal ) { if ( ( l & 0x8000000000000000L ) != 0 ) return fmt( Long.toString( l >>> 63, 8 ) + fmt( l & 0x7fffffffffffffffL, 21, OC|ZF ), minWidth, flags|WN ); else return fmt( Long.toString( l, 8 ), minWidth, flags|WN ); } else return fmt( Long.toString( l ), minWidth, flags|WN ); } // float public static String fmt( float f ) { return fmt( f, 0, 0, 0 ); } public static String fmt( float f, int minWidth ) { return fmt( f, minWidth, 0, 0 ); } public static String fmt( float f, int minWidth, int sigFigs ) { return fmt( f, minWidth, sigFigs, 0 ); } public static String fmt( float f, int minWidth, int sigFigs, int flags ) { if ( sigFigs != 0 ) return fmt( sigFigFix( Float.toString( f ), sigFigs ), minWidth, flags|WN ); else return fmt( Float.toString( f ), minWidth, flags|WN ); } // double public static String fmt( double d ) { return fmt( d, 0, 0, 0 ); } public static String fmt( double d, int minWidth ) { return fmt( d, minWidth, 0, 0 ); } public static String fmt( double d, int minWidth, int sigFigs ) { return fmt( d, minWidth, sigFigs, 0 ); } public static String fmt( double d, int minWidth, int sigFigs, int flags ) { if ( sigFigs != 0 ) return fmt( sigFigFix( doubleToString( d ), sigFigs ), minWidth, flags|WN ); else return fmt( doubleToString( d ), minWidth, flags|WN ); } // char public static String fmt( char c ) { return fmt( c, 0, 0 ); } public static String fmt( char c, int minWidth ) { return fmt( c, minWidth, 0 ); } public static String fmt( char c, int minWidth, int flags ) { // return fmt( Character.toString( c ), minWidth, flags ); // Character currently lacks a static toString method. Workaround // is to make a temporary instance and use the instance toString. return fmt( new Character( c ).toString(), minWidth, flags ); } // Object public static String fmt( Object o ) { return fmt( o, 0, 0 ); } public static String fmt( Object o, int minWidth ) { return fmt( o, minWidth, 0 ); } public static String fmt( Object o, int minWidth, int flags ) { return fmt( o.toString(), minWidth, flags ); } // String public static String fmt( String s ) { return fmt( s, 0, 0 ); } public static String fmt( String s, int minWidth ) { return fmt( s, minWidth, 0 ); } public static String fmt( String s, int minWidth, int flags ) { int len = s.length(); boolean zeroFill = ( ( flags & ZF ) != 0 ); boolean leftJustify = ( ( flags & LJ ) != 0 ); boolean hexadecimal = ( ( flags & HX ) != 0 ); boolean octal = ( ( flags & OC ) != 0 ); boolean wasNumber = ( ( flags & WN ) != 0 ); if ( ( hexadecimal || octal || zeroFill ) && ! wasNumber ) throw new InternalError( "Acme.Fmt: number flag on a non-number" ); if ( zeroFill && leftJustify ) throw new InternalError( "Acme.Fmt: zero-fill left-justify is silly" ); if ( hexadecimal && octal ) throw new InternalError( "Acme.Fmt: can't do both hex and octal" ); if ( len >= minWidth ) return s; int fillWidth = minWidth - len; StringBuffer fill = new StringBuffer( fillWidth ); for ( int i = 0; i < fillWidth; ++i ) if ( zeroFill ) fill.append( '0' ); else fill.append( ' ' ); if ( leftJustify ) return s + fill; else if ( zeroFill && s.startsWith( "-" ) ) return "-" + fill + s.substring( 1 ); else return fill + s; } // Internal routines. private static String sigFigFix( String s, int sigFigs ) { // First dissect the floating-point number string into sign, // integer part, fraction part, and exponent. String sign; String unsigned; if ( s.startsWith( "-" ) || s.startsWith( "+" ) ) { sign = s.substring( 0, 1 ); unsigned = s.substring( 1 ); } else { sign = ""; unsigned = s; } String mantissa; String exponent; int eInd = unsigned.indexOf( 'e' ); if ( eInd == -1 ) // it may be 'e' or 'E' eInd = unsigned.indexOf( 'E' ); if ( eInd == -1 ) { mantissa = unsigned; exponent = ""; } else { mantissa = unsigned.substring( 0, eInd ); exponent = unsigned.substring( eInd ); } StringBuffer number, fraction; int dotInd = mantissa.indexOf( '.' ); if ( dotInd == -1 ) { number = new StringBuffer( mantissa ); fraction = new StringBuffer( "" ); } else { number = new StringBuffer( mantissa.substring( 0, dotInd ) ); fraction = new StringBuffer( mantissa.substring( dotInd + 1 ) ); } int numFigs = number.length(); int fracFigs = fraction.length(); if ( ( numFigs == 0 || number.equals( "0" ) ) && fracFigs > 0 ) { // Don't count leading zeros in the fraction. numFigs = 0; for ( int i = 0; i < fraction.length(); ++i ) { if ( fraction.charAt( i ) != '0' ) break; --fracFigs; } } int mantFigs = numFigs + fracFigs; if ( sigFigs > mantFigs ) { // We want more figures; just append zeros to the fraction. for ( int i = mantFigs; i < sigFigs; ++i ) fraction.append( '0' ); } else if ( sigFigs < mantFigs && sigFigs >= numFigs ) { // Want fewer figures in the fraction; chop. fraction.setLength( fraction.length() - ( fracFigs - ( sigFigs - numFigs ) ) ); // Round? } else if ( sigFigs < numFigs ) { // Want fewer figures in the number; turn them to zeros. fraction.setLength( 0 ); // should already be zero, but make sure for ( int i = sigFigs; i < numFigs; ++i ) number.setCharAt( i, '0' ); // Round? } // Else sigFigs == mantFigs, which is fine. if ( fraction.length() == 0 ) return sign + number + exponent; else return sign + number + "." + fraction + exponent; } /// Improved version of Double.toString(), returns more decimal places. //

// The JDK 1.0.2 version of Double.toString() returns only six decimal // places on some systems. In JDK 1.1 full precision is returned on // all platforms. // @deprecated // @see java.lang.Double#toString public static String doubleToString( double d ) { // Handle special numbers first, to avoid complications. if ( Double.isNaN( d ) ) return "NaN"; if ( d == Double.NEGATIVE_INFINITY ) return "-Inf"; if ( d == Double.POSITIVE_INFINITY ) return "Inf"; // Grab the sign, and then make the number positive for simplicity. boolean negative = false; if ( d < 0.0D ) { negative = true; d = -d; } // Get the native version of the unsigned value, as a template. String unsStr = Double.toString( d ); // Dissect out the exponent. String mantStr, expStr; int exp; int eInd = unsStr.indexOf( 'e' ); if ( eInd == -1 ) // it may be 'e' or 'E' eInd = unsStr.indexOf( 'E' ); if ( eInd == -1 ) { mantStr = unsStr; expStr = ""; exp = 0; } else { mantStr = unsStr.substring( 0, eInd ); expStr = unsStr.substring( eInd + 1 ); if ( expStr.startsWith( "+" ) ) exp = Integer.parseInt( expStr.substring( 1 ) ); else exp = Integer.parseInt( expStr ); } // Dissect out the number part. String numStr; int dotInd = mantStr.indexOf( '.' ); if ( dotInd == -1 ) numStr = mantStr; else numStr = mantStr.substring( 0, dotInd ); long num; if ( numStr.length() == 0 ) num = 0; else num = Integer.parseInt( numStr ); // Build the new mantissa. StringBuffer newMantBuf = new StringBuffer( numStr + "." ); double p = Math.pow( 10, exp ); double frac = d - num * p; String digits = "0123456789"; int nDigits = 16 - numStr.length(); // about 16 digits in a double for ( int i = 0; i < nDigits; ++i ) { p /= 10.0D; int dig = (int) ( frac / p ); if ( dig < 0 ) dig = 0; if ( dig > 9 ) dig = 9; newMantBuf.append( digits.charAt( dig ) ); frac -= dig * p; } if ( (int) ( frac / p + 0.5D ) == 1 ) { // Round up. boolean roundMore = true; for ( int i = newMantBuf.length() - 1; i >= 0; --i ) { int dig = digits.indexOf( newMantBuf.charAt( i ) ); if ( dig == -1 ) continue; ++dig; if ( dig == 10 ) { newMantBuf.setCharAt( i, '0' ); continue; } newMantBuf.setCharAt( i, digits.charAt( dig ) ); roundMore = false; break; } if ( roundMore ) { // If this happens, we need to prepend a 1. But I haven't // found a test case yet, so I'm leaving it out for now. // But if you get this message, please let me know! newMantBuf.append( "ROUNDMORE" ); } } // Chop any trailing zeros. int len = newMantBuf.length(); while ( newMantBuf.charAt( len - 1 ) == '0' ) newMantBuf.setLength( --len ); // And chop a trailing dot, if any. if ( newMantBuf.charAt( len - 1 ) == '.' ) newMantBuf.setLength( --len ); // Done. return ( negative ? "-" : "" ) + newMantBuf + ( expStr.length() != 0 ? ( "e" + expStr ) : "" ); } /****************************************************************************** /// Test program. public static void main( String[] args ) { System.out.println( "Starting tests." ); show( Fmt.fmt( "Hello there." ) ); show( Fmt.fmt( 123 ) ); show( Fmt.fmt( 123, 10 ) ); show( Fmt.fmt( 123, 10, Fmt.ZF ) ); show( Fmt.fmt( 123, 10, Fmt.LJ ) ); show( Fmt.fmt( -123 ) ); show( Fmt.fmt( -123, 10 ) ); show( Fmt.fmt( -123, 10, Fmt.ZF ) ); show( Fmt.fmt( -123, 10, Fmt.LJ ) ); show( Fmt.fmt( (byte) 0xbe, 22, Fmt.OC ) ); show( Fmt.fmt( (short) 0xbabe, 22, Fmt.OC ) ); show( Fmt.fmt( 0xcafebabe, 22, Fmt.OC ) ); show( Fmt.fmt( 0xdeadbeefcafebabeL, 22, Fmt.OC ) ); show( Fmt.fmt( 0x8000000000000000L, 22, Fmt.OC ) ); show( Fmt.fmt( (byte) 0xbe, 16, Fmt.HX ) ); show( Fmt.fmt( (short) 0xbabe, 16, Fmt.HX ) ); show( Fmt.fmt( 0xcafebabe, 16, Fmt.HX ) ); show( Fmt.fmt( 0xdeadbeefcafebabeL, 16, Fmt.HX ) ); show( Fmt.fmt( 0x8000000000000000L, 16, Fmt.HX ) ); show( Fmt.fmt( 'c' ) ); show( Fmt.fmt( new java.util.Date() ) ); show( Fmt.fmt( 123.456F ) ); show( Fmt.fmt( 123456000000000000.0F ) ); show( Fmt.fmt( 123.456F, 0, 8 ) ); show( Fmt.fmt( 123.456F, 0, 7 ) ); show( Fmt.fmt( 123.456F, 0, 6 ) ); show( Fmt.fmt( 123.456F, 0, 5 ) ); show( Fmt.fmt( 123.456F, 0, 4 ) ); show( Fmt.fmt( 123.456F, 0, 3 ) ); show( Fmt.fmt( 123.456F, 0, 2 ) ); show( Fmt.fmt( 123.456F, 0, 1 ) ); show( Fmt.fmt( 123456000000000000.0F, 0, 4 ) ); show( Fmt.fmt( -123.456F, 0, 4 ) ); show( Fmt.fmt( -123456000000000000.0F, 0, 4 ) ); show( Fmt.fmt( 123.0F ) ); show( Fmt.fmt( 123.0D ) ); show( Fmt.fmt( 1.234567890123456789F ) ); show( Fmt.fmt( 1.234567890123456789D ) ); show( Fmt.fmt( 1234567890123456789F ) ); show( Fmt.fmt( 1234567890123456789D ) ); show( Fmt.fmt( 0.000000000000000000001234567890123456789F ) ); show( Fmt.fmt( 0.000000000000000000001234567890123456789D ) ); show( Fmt.fmt( 12300.0F ) ); show( Fmt.fmt( 12300.0D ) ); show( Fmt.fmt( 123000.0F ) ); show( Fmt.fmt( 123000.0D ) ); show( Fmt.fmt( 1230000.0F ) ); show( Fmt.fmt( 1230000.0D ) ); show( Fmt.fmt( 12300000.0F ) ); show( Fmt.fmt( 12300000.0D ) ); show( Fmt.fmt( Float.NaN ) ); show( Fmt.fmt( Float.POSITIVE_INFINITY ) ); show( Fmt.fmt( Float.NEGATIVE_INFINITY ) ); show( Fmt.fmt( Double.NaN ) ); show( Fmt.fmt( Double.POSITIVE_INFINITY ) ); show( Fmt.fmt( Double.NEGATIVE_INFINITY ) ); show( Fmt.fmt( 1.0F / 8.0F ) ); show( Fmt.fmt( 1.0D / 8.0D ) ); System.out.println( "Done with tests." ); } private static void show( String str ) { System.out.println( "#" + str + "#" ); } ******************************************************************************/ }