Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
BEGIN LICENSE BLOCK ***** Version: CPL 1.0/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Common Public License Version 1.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.eclipse.org/legal/cpl-v10.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com> Alternatively, the contents of this file may be used under the terms of either of the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the CPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the CPL, the GPL or the LGPL. END LICENSE BLOCK ***
  
  package org.jruby.util;
  
  import java.util.HashMap;
  import java.util.Locale;
  import java.util.Map;
  
  import org.jruby.Ruby;

Author(s):
Bill Dortch
  
  public class Sprintf {
      private static final int FLAG_NONE        = 0;
      private static final int FLAG_SPACE       = 1;
      private static final int FLAG_ZERO        = 1 << 1;
      private static final int FLAG_PLUS        = 1 << 2;
      private static final int FLAG_MINUS       = 1 << 3;
      private static final int FLAG_SHARP       = 1 << 4;
      private static final int FLAG_WIDTH       = 1 << 5;
      private static final int FLAG_PRECISION   = 1 << 6;
      
      private static final byte[] PREFIX_OCTAL     = {'0'};
      private static final byte[] PREFIX_HEX_LC    = {'0','x'};
      private static final byte[] PREFIX_HEX_UC    = {'0','X'};
      private static final byte[] PREFIX_BINARY_LC = {'0','b'};
      private static final byte[] PREFIX_BINARY_UC = {'0','B'};
      
      private static final byte[] PREFIX_NEGATIVE = {'.','.'};
      
      private static final byte[] NAN_VALUE       = {'N','a','N'};
      private static final byte[] INFINITY_VALUE  = {'I','n','f'};
         
      private static final BigInteger BIG_32 = BigInteger.valueOf(((long). + 1L) << 1);
      private static final BigInteger BIG_64 = .shiftLeft(32);
      private static final BigInteger BIG_MINUS_32 = BigInteger.valueOf((long). << 1);
      private static final BigInteger BIG_MINUS_64 = .shiftLeft(32);
  
      private static final String ERR_MALFORMED_FORMAT = "malformed format string";
      private static final String ERR_MALFORMED_NUM = "malformed format string - %[0-9]";
      private static final String ERR_MALFORMED_DOT_NUM = "malformed format string - %.[0-9]";
      private static final String ERR_MALFORMED_STAR_NUM = "malformed format string - %*[0-9]";
      private static final String ERR_ILLEGAL_FORMAT_CHAR = "illegal format character - %";
      private static final String ERR_MALFORMED_NAME = "malformed name - unmatched parenthesis";
  
      private static final ThreadLocal<Map<LocaleNumberFormat>> LOCALE_NUMBER_FORMATS = new ThreadLocal<Map<LocaleNumberFormat>>() {
          protected Map<LocaleNumberFormatinitialValue() {
              return new HashMap<LocaleNumberFormat>();
          }
      };
      
      private static final class Args {
         private final Ruby runtime;
         private final Locale locale;
         private final IRubyObject rubyObject;
         private final RubyArray rubyArray;
         private final RubyHash rubyHash;
         private final int length;
         private int unnumbered// last index (+1) accessed by next()
         private int numbered;   // last index (+1) accessed by get()
         
         Args(Locale localeIRubyObject rubyObject) {
             if (rubyObject == nullthrow new IllegalArgumentException("null IRubyObject passed to sprintf");
             this. = locale == null ? Locale.getDefault() : locale;
             this. = rubyObject;
             if (rubyObject instanceof RubyArray) {
                 this. = (RubyArray)rubyObject;
                 this. = null;
                 this. = .size();
             } else if (rubyObject instanceof RubyHash && rubyObject.getRuntime().is1_9()) {
                 // allow a hash for args if in 1.9 mode
                 this. = (RubyHash)rubyObject;
                 this. = null;
                 this. = -1;
             } else {
                 this. = 1;
                 this. = null;
                 this. = null;
             }
             this. = rubyObject.getRuntime();
         }
         
         Args(IRubyObject rubyObject) {
             this(Locale.getDefault(),rubyObject);
         }
 
         // temporary hack to handle non-Ruby values
         // will come up with better solution shortly
         Args(Ruby runtimelong value) {
             this(RubyFixnum.newFixnum(runtime,value));
         }
         
         void raiseArgumentError(String message) {
             throw .newArgumentError(message);
         }
 
         void raiseKeyError(String message) {
             throw .newKeyError(message);
         }
         
         void warn(ID idString message) {
             .getWarnings().warn(idmessage);
         }
         
         void warning(ID idString message) {
             if (.isVerbose()) .getWarnings().warning(idmessage);
         }
         
         IRubyObject next(ByteList name) {
             // for 1.9 hash args
             if (.is1_9()) {
                 if (name != null) {
                     if ( == nullraiseArgumentError("positional args mixed with named args");
 
                     IRubyObject object = .fastARef(.newSymbol(name));
                     if (object == nullraiseKeyError("key<" + name + "> not found");
                     return object;
                 } else if ( != null) {
                     raiseArgumentError("positional args mixed with named args");
                 }
             }
 
             // this is the order in which MRI does these two tests
             if ( > 0) raiseArgumentError("unnumbered" + ( + 1) + "mixed with numbered");
             if ( >= raiseArgumentError("too few arguments");
             IRubyObject object =  == null ?  : .eltInternal();
             ++;
             return object;
         }
         
         IRubyObject get(int index) {
             // for 1.9 hash args
             if ( != nullraiseArgumentError("positional args mixed with named args");
             // this is the order in which MRI does these tests
             if ( > 0) raiseArgumentError("numbered("++") after unnumbered("++")");
             if (index < 0) raiseArgumentError("invalid index - " + (index + 1) + '$');
             if (index >= raiseArgumentError("too few arguments");
              = index + 1;
             return  == null ?  : .eltInternal(index);
         }
         
         IRubyObject getNth(int formatIndex) {
             return get(formatIndex - 1);
         }
         
         int nextInt() {
             return intValue(next(null));
         }
 
         int getNthInt(int formatIndex) {
             return intValue(get(formatIndex - 1));
         }
         
         int intValue(IRubyObject obj) {
             if (obj instanceof RubyNumericreturn (int)((RubyNumeric)obj).getLongValue();
 
             // basically just forcing a TypeError here to match MRI
             obj = TypeConverter.convertToType(objobj.getRuntime().getFixnum(), "to_int"true);
             return (int)((RubyFixnum)obj).getLongValue();
         }
         
         byte getDecimalSeparator() {
             // not saving DFS instance, as it will only be used once (at most) per call
             return (byte)new DecimalFormatSymbols().getDecimalSeparator();
         }
     } // Args
 
     // static methods only
     private Sprintf () {}
     
     // Special form of sprintf that returns a RubyString and handles
     // tainted strings correctly.
     public static boolean sprintf(ByteList toLocale localeCharSequence formatIRubyObject args) {
         return rubySprintfToBuffer(toformatnew Args(localeargs));
     }
 
     // Special form of sprintf that returns a RubyString and handles
     // tainted strings correctly. Version for 1.9.
     public static boolean sprintf1_9(ByteList toLocale localeCharSequence formatIRubyObject args) {
         return rubySprintfToBuffer(toformatnew Args(localeargs), false);
     }
 
     public static boolean sprintf(ByteList toCharSequence formatIRubyObject args) {
         return rubySprintf(toformatnew Args(args));
     }
 
     public static boolean sprintf(Ruby runtimeByteList toCharSequence formatint arg) {
         return rubySprintf(toformatnew Args(runtime, (long)arg));
     }
 
     public static boolean sprintf(ByteList toRubyString formatIRubyObject args) {
         return rubySprintf(toformat.getByteList(), new Args(args));
     }
 
     private static boolean rubySprintf(ByteList toCharSequence charFormatArgs args) {
         return rubySprintfToBuffer(tocharFormatargs);
     }
 
     private static boolean rubySprintfToBuffer(ByteList bufCharSequence charFormatArgs args) {
         return rubySprintfToBuffer(bufcharFormatargstrue);
     }
 
     private static boolean rubySprintfToBuffer(ByteList bufCharSequence charFormatArgs argsboolean usePrefixForZero) {
         Ruby runtime = args.runtime;
         boolean tainted = false;
         final byte[] format;
 
         int offset;
         int length;
         int start;
         int mark;
         ByteList name = null;
         Encoding encoding = null;
 
         // used for RubyString functions to manage encoding, etc
         RubyString wrapper = RubyString.newString(runtimebuf);
 
         if (charFormat instanceof ByteList) {
             ByteList list = (ByteList)charFormat;
             format = list.getUnsafeBytes();
             int begin = list.begin(); 
             offset = begin;
             length = begin + list.length();
             start = begin;
             mark = begin;
             encoding = list.getEncoding();
         } else {
             format = stringToBytes(charFormatfalse);
             offset = 0;
             length = charFormat.length();
             start = 0;
             mark = 0;
             encoding = .;
         }
 
         while (offset < length) {
             start = offset;
             for ( ; offset < length && format[offset] != '%'offset++) {}
 
             if (offset > start) {
                 buf.append(format,start,offset-start);
                 start = offset;
             }
             if (offset++ >= lengthbreak;
 
             IRubyObject arg = null;
             int flags = 0;
             int width = 0;
             int precision = 0;
             int number = 0;
             byte fchar = 0;
             boolean incomplete = true;
             for ( ; incomplete && offset < length ; ) {
                 switch (fchar = format[offset]) {
                 default:
                     if (fchar == '\0' && flags == ) {
                         // MRI 1.8.6 behavior: null byte after '%'
                         // leads to "%" string. Null byte in
                         // other places, like "%5\0", leads to error.
                         buf.append('%');
                         buf.append(fchar);
                         incomplete = false;
                         offset++;
                         break;
                     } else if (isPrintable(fchar)) {
                         raiseArgumentError(args,"malformed format string - %" + (char)fchar);
                     } else {
                         raiseArgumentError(args,);
                     }
                     break;
 
                 case '<': {
                     // Ruby 1.9 named args
                     int nameStart = ++offset;
                     int nameEnd = nameStart;
 
                     for ( ; offset < length ; offset++) {
                         if (format[offset] == '>') {
                             nameEnd = offset;
                             offset++;
                             break;
                         }
                     }
 
                     if (nameEnd == nameStartraiseArgumentError(args);
 
                     ByteList oldName = name;
                     name = new ByteList(formatnameStartnameEnd - nameStartencodingfalse);
 
                     if (oldName != nullraiseArgumentError(args"name<" + name + "> after <" + oldName + ">");
 
                     break;
                 }
 
                 case '{': {
                     // Ruby 1.9 named replacement
                     int nameStart = ++offset;
                     int nameEnd = nameStart;
 
                     for ( ; offset < length ; offset++) {
                         if (format[offset] == '}') {
                             nameEnd = offset;
                             offset++;
                             break;
                         }
                     }
 
                     if (nameEnd == nameStartraiseArgumentError(args);
 
                     ByteList localName = new ByteList(formatnameStartnameEnd - nameStartencodingfalse);
                     buf.append(args.next(localName).asString().getByteList());
                     incomplete = false;
 
                     break;
                 }
 
                 case ' ':
                     flags |= ;
                     offset++;
                     break;
                 case '0':
                     flags |= ;
                     offset++;
                     break;
                 case '+':
                     flags |= ;
                     offset++;
                     break;
                 case '-':
                     flags |= ;
                     offset++;
                     break;
                 case '#':
                     flags |= ;
                     offset++;
                     break;
                 case '1':case '2':case '3':case '4':case '5':
                 case '6':case '7':case '8':case '9':
                     // MRI doesn't flag it as an error if width is given multiple
                     // times as a number (but it does for *)
                     number = 0;
                     for ( ; offset < length && isDigit(fchar = format[offset]); offset++) {
                         number = extendWidth(argsnumberfchar);
                     }
                     checkOffset(args,offset,length,);
                     if (fchar == '$') {
                         if (arg != null) {
                             raiseArgumentError(args,"value given twice - " + number + "$");
                         }
                         arg = args.getNth(number);
                         offset++;
                     } else {
                         width = number;
                         flags |= ;
                     }
                     break;
                 
                 case '*':
                     if ((flags & ) != 0) {
                         raiseArgumentError(args,"width given twice");
                     }
                     flags |= ;
                     // TODO: factor this chunk as in MRI/YARV GETASTER
                     checkOffset(args,++offset,length,);
                     mark = offset;
                     number = 0;
                     for ( ; offset < length && isDigit(fchar = format[offset]); offset++) {
                         number = extendWidth(args,number,fchar);
                     }
                     checkOffset(args,offset,length,);
                     if (fchar == '$') {
                         width = args.getNthInt(number);
                         if (width < 0) {
                             flags |= ;
                             width = -width;
                         }
                         offset++;
                     } else {
                         width = args.nextInt();
                         if (width < 0) {
                             flags |= ;
                             width = -width;
                         }
                         // let the width (if any), get processed in the next loop,
                         // so any leading 0 gets treated correctly 
                         offset = mark;
                     }
                     break;
                 
                 case '.':
                     if ((flags & ) != 0) {
                         raiseArgumentError(args,"precision given twice");
                     }
                     flags |= ;
                     checkOffset(args,++offset,length,);
                     fchar = format[offset];
                     if (fchar == '*') {
                         // TODO: factor this chunk as in MRI/YARV GETASTER
                         checkOffset(args,++offset,length,);
                         mark = offset;
                         number = 0;
                         for ( ; offset < length && isDigit(fchar = format[offset]); offset++) {
                             number = extendWidth(args,number,fchar);
                         }
                         checkOffset(args,offset,length,);
                         if (fchar == '$') {
                             precision = args.getNthInt(number);
                             if (precision < 0) {
                                 flags &= ~;
                             }
                             offset++;
                         } else {
                             precision = args.nextInt();
                             if (precision < 0) {
                                 flags &= ~;
                             }
                             // let the width (if any), get processed in the next loop,
                             // so any leading 0 gets treated correctly 
                             offset = mark;
                         }
                     } else {
                         number = 0;
                         for ( ; offset < length && isDigit(fchar = format[offset]); offset++) {
                             number = extendWidth(args,number,fchar);
                         }
                         checkOffset(args,offset,length,);
                         precision = number;
                     }
                     break;
 
                 case '\n':
                     offset--;
                 case '%':
                     if (flags != ) {
                         raiseArgumentError(args,);
                     }
                     buf.append('%');
                     offset++;
                     incomplete = false;
                     break;
 
                 case 'c': {
                     if (arg == null || name != null) {
                         arg = args.next(name);
                         name = null;
                     }
                     
                     int c = 0;
                     // MRI 1.8.5-p12 doesn't support 1-char strings, but
                     // YARV 0.4.1 does. I don't think it hurts to include
                     // this; sprintf('%c','a') is nicer than sprintf('%c','a'[0])
                     if (arg instanceof RubyString) {
                         ByteList bytes = ((RubyString)arg).getByteList();
                         if (bytes.length() == 1) {
                             c = bytes.getUnsafeBytes()[bytes.begin()];
                         } else {
                             raiseArgumentError(args,"%c requires a character");
                         }
                     } else {
                         c = args.intValue(arg);
                     }
                     if ((flags & ) != 0 && width > 1) {
                         if ((flags & ) != 0) {
                             buf.append(c);
                             buf.fill(' 'width-1);
                         } else {
                             buf.fill(' ',width-1);
                             buf.append(c);
                         }
                     } else {
                         buf.append(c);
                     }
                     offset++;
                     incomplete = false;
                     break;
                 }
                 case 'p':
                 case 's': {
                     if (arg == null || name != null) {
                         arg = args.next(name);
                         name = null;
                     }
 
                     if (fchar == 'p') {
                         arg = arg.callMethod(arg.getRuntime().getCurrentContext(),"inspect");
                     }
                     RubyString strArg = arg.asString();
                     ByteList bytes = strArg.getByteList();
                     Encoding enc = wrapper.checkEncoding(strArg);
                     int len = bytes.length();
                     int strLen = strArg.strLength();
 
                     if (arg.isTaint()) tainted = true;
 
                     if ((flags & ) != 0 && precision < len) {
                         // TODO: precision is not considering actual character length
                         // See below as well.
                         len = precision;
                         strLen = precision;
                     }
                     // TODO: adjust length so it won't fall in the middle
                     // of a multi-byte character. MRI's sprintf.c uses tables
                     // in a modified version of regex.c, which assume some
                     // particular  encoding for a given installation/application.
                     // (See regex.c#re_mbcinit in ruby-1.8.5-p12)
                     //
                     // This is only an issue if the user specifies a precision
                     // that causes the string to be truncated. The same issue
                     // would arise taking a substring of a ByteList-backed RubyString.
 
                     if ((flags & ) != 0 && width > strLen) {
                         width -= strLen;
                         if ((flags & ) != 0) {
                             buf.append(bytes.getUnsafeBytes(),bytes.begin(),len);
                             buf.fill(' ',width);
                         } else {
                             buf.fill(' ',width);
                             buf.append(bytes.getUnsafeBytes(),bytes.begin(),len);
                         }
                     } else {
                         buf.append(bytes.getUnsafeBytes(),bytes.begin(),len);
                     }
                     offset++;
                     incomplete = false;
                     buf.setEncoding(enc);
                     break;
                 }
                 case 'd':
                 case 'i':
                 case 'o':
                 case 'x':
                 case 'X':
                 case 'b':
                 case 'B':
                 case 'u': {
                     if (arg == null || name != null) {
                         arg = args.next(name);
                         name = null;
                     }
 
                     int type = arg.getMetaClass().;
                     if (type != . && type != .) {
                         switch(type) {
                         case .:
                             arg = RubyNumeric.dbl2num(arg.getRuntime(),((RubyFloat)arg).getValue());
                             break;
                         case .:
                             arg = ((RubyString)arg).stringToInum(0, true);
                             break;
                         default:
                             if (arg.respondsTo("to_int")) {
                                 arg = TypeConverter.convertToType(argarg.getRuntime().getInteger(), "to_int"true);
                             } else {
                                 arg = TypeConverter.convertToType(argarg.getRuntime().getInteger(), "to_i"true);
                             }
                             break;
                         }
                         type = arg.getMetaClass().;
                     }
                     byte[] bytes = null;
                     int first = 0;
                     byte[] prefix = null;
                     boolean sign;
                     boolean negative;
                     byte signChar = 0;
                     byte leadChar = 0;
                     int base;
 
                     // 'd' and 'i' are the same
                     if (fchar == 'i'fchar = 'd';
 
                     // 'u' with space or plus flags is same as 'd'
                     if (fchar == 'u' && (flags & ( | )) != 0) {
                         fchar = 'd';
                     }
                     sign = (fchar == 'd' || (flags & ( | )) != 0);
 
                     switch (fchar) {
                     case 'o':
                         base = 8; break;
                     case 'x':
                     case 'X':
                         base = 16; break;
                     case 'b':
                     case 'B':
                         base = 2; break;
                     case 'u':
                     case 'd':
                     default:
                         base = 10; break;
                     }
                     // We depart here from strict adherence to MRI code, as MRI
                     // uses C-sprintf, in part, to format numeric output, while
                     // we'll use Java's numeric formatting code (and our own).
                     boolean zero;
                     if (type == .) {
                         negative = ((RubyFixnum)arg).getLongValue() < 0;
                         zero = ((RubyFixnum)arg).getLongValue() == 0;
                         if (negative && fchar == 'u') {
                             bytes = getUnsignedNegativeBytes((RubyFixnum)arg);
                         } else {
                             bytes = getFixnumBytes((RubyFixnum)arg,base,sign,fchar=='X');
                         }
                     } else {
                         negative = ((RubyBignum)arg).getValue().signum() < 0;
                         zero = ((RubyBignum)arg).getValue().equals(.);
                         if (negative && fchar == 'u' && usePrefixForZero) {
                             bytes = getUnsignedNegativeBytes((RubyBignum)arg);
                         } else {
                             bytes = getBignumBytes((RubyBignum)arg,base,sign,fchar=='X');
                         }
                     }
                     if ((flags & ) != 0) {
                         if (!zero || usePrefixForZero) {
                             switch (fchar) {
                             case 'o'prefix = break;
                             case 'x'prefix = break;
                             case 'X'prefix = break;
                             case 'b'prefix = break;
                             case 'B'prefix = break;
                             }
                         }
                         if (prefix != nullwidth -= prefix.length;
                     }
                     int len = 0;
                     if (sign) {
                         if (negative) {
                             signChar = '-';
                             width--;
                             first = 1; // skip '-' in bytes, will add where appropriate
                         } else if ((flags & ) != 0) {
                             signChar = '+';
                             width--;
                         } else if ((flags & ) != 0) {
                             signChar = ' ';
                             width--;
                         }
                     } else if (negative) {
                         if (base == 10) {
                             warning(.args"negative number for %u specifier");
                             leadChar = '.';
                             len += 2;
                         } else {
                             if ((flags & ( | )) == 0) len += 2; // ..
 
                             first = skipSignBits(bytes,base);
                             switch(fchar) {
                             case 'b':
                             case 'B':
                                 leadChar = '1';
                                 break;
                             case 'o':
                                 leadChar = '7';
                                 break;
                             case 'x':
                                 leadChar = 'f';
                                 break;
                             case 'X':
                                 leadChar = 'F';
                                 break;
                             }
                             if (leadChar != 0) len++;
                         }
                     }
                     int numlen = bytes.length - first;
                     len += numlen;
                     
                     if ((flags & (|)) == ) {
                         precision = width;
                         width = 0;
                     } else {
                         if (precision < lenprecision = len;
 
                         width -= precision;
                     }
                     if ((flags & ) == 0) {
                         buf.fill(' ',width);
                         width = 0;
                     }
                     if (signChar != 0) buf.append(signChar);
                     if (prefix != nullbuf.append(prefix);
 
                     if (len < precision) {
                         if (leadChar == 0) {
                             if (fchar != 'd' || usePrefixForZero || !negative ||
                                     ((flags & ) != 0 && (flags & ) == 0)) {
                                 buf.fill('0'precision - len);
                             }
                         } else if (leadChar == '.') {
                             buf.fill(leadChar,precision-len);
                             buf.append();
                         } else if (!usePrefixForZero) {
                             buf.append();
                             buf.fill(leadChar,precision - len - 1);
                         } else {
                             buf.fill(leadChar,precision-len+1); // the 1 is for the stripped sign char
                         }
                     } else if (leadChar != 0) {
                         if (((flags & ( | )) == 0 && usePrefixForZero) ||
                                 (!usePrefixForZero && "xXbBo".indexOf(fchar) != -1)) {
                             buf.append();
                         }
                         if (leadChar != '.'buf.append(leadChar);
                     }
                     buf.append(bytes,first,numlen);
 
                     if (width > 0) buf.fill(' ',width);
                     if (len < precision && fchar == 'd' && negative && 
                             !usePrefixForZero && (flags & ) != 0) {
                         buf.fill(' 'precision - len);
                     }
                                         
                     offset++;
                     incomplete = false;
                     break;
                 }
                 case 'E':
                 case 'e':
                 case 'f':
                 case 'G':
                 case 'g': {
                     if (arg == null || name != null) {
                         arg = args.next(name);
                         name = null;
                     }
                     
                     if (!(arg instanceof RubyFloat)) {
                         // FIXME: what is correct 'recv' argument?
                         // (this does produce the desired behavior)
                         if (usePrefixForZero) {
                             arg = RubyKernel.new_float(arg,arg);
                         } else {
                             arg = RubyKernel.new_float19(arg,arg);
                         }
                     }
                     double dval = ((RubyFloat)arg).getDoubleValue();
                     boolean nan = dval != dval;
                     boolean inf = dval == . || dval == .;
                     boolean negative = dval < 0.0d || (dval == 0.0d && (new Float(dval)).equals(new Float(-0.0)));
                     
                     byte[] digits;
                     int nDigits = 0;
                     int exponent = 0;
 
                     int len = 0;
                     byte signChar;
                     
                     if (nan || inf) {
                         if (nan) {
                             digits = ;
                             len = .;
                         } else {
                             digits = ;
                             len = .;
                         }
                         if (negative) {
                             signChar = '-';
                             width--;
                         } else if ((flags & ) != 0) {
                             signChar = '+';
                             width--;
                         } else if ((flags & ) != 0) {
                             signChar = ' ';
                             width--;
                         } else {
                             signChar = 0;
                         }
                         width -= len;
                         
                         if (width > 0 && (flags & (|)) == 0) {
                             buf.fill(' ',width);
                             width = 0;
                         }
                         if (signChar != 0) buf.append(signChar);
 
                         if (width > 0 && (flags & ) == 0) {
                             buf.fill('0',width);
                             width = 0;
                         }
                         buf.append(digits);
                         if (width > 0) buf.fill(' 'width);
 
                         offset++;
                         incomplete = false;
                         break;
                     }
 
                     NumberFormat nf = getNumberFormat(args.locale);
                     nf.setMaximumFractionDigits(.);
                     String str = nf.format(dval);
                     
                     // grrr, arghh, want to subclass sun.misc.FloatingDecimal, but can't,
                     // so we must do all this (the next 70 lines of code), which has already
                     // been done by FloatingDecimal.
                     int strlen = str.length();
                     digits = new byte[strlen];
                     int nTrailingZeroes = 0;
                     int i = negative ? 1 : 0;
                     int decPos = 0;
                     byte ival;
                 int_loop:
                     for ( ; i < strlen ; ) {
                         switch(ival = (byte)str.charAt(i++)) {
                         case '0':
                             if (nDigits > 0) nTrailingZeroes++;
 
                             break// switch
                         case '1'case '2'case '3'case '4':
                         case '5'case '6'case '7'case '8'case '9':
                             if (nTrailingZeroes > 0) {
                                 for ( ; nTrailingZeroes > 0 ; nTrailingZeroes-- ) {
                                     digits[nDigits++] = '0';
                                 }
                             }
                             digits[nDigits++] = ival;
                             break// switch
                         case '.':
                             break int_loop;
                         }
                     }
                     decPos = nDigits + nTrailingZeroes;
                 dec_loop:
                     for ( ; i < strlen ; ) {
                         switch(ival = (byte)str.charAt(i++)) {
                         case '0':
                             if (nDigits > 0) {
                                 nTrailingZeroes++;
                             } else {
                                 exponent--;
                             }
                             break// switch
                         case '1'case '2'case '3'case '4':
                         case '5'case '6'case '7'case '8'case '9':
                             if (nTrailingZeroes > 0) {
                                 for ( ; nTrailingZeroes > 0 ; nTrailingZeroes--  ) {
                                     digits[nDigits++] = '0';
                                 }
                             }
                             digits[nDigits++] = ival;
                             break// switch
                         case 'E':
                             break dec_loop;
                         }
                     }
                     if (i < strlen) {
                         int expSign;
                         int expVal = 0;
                         if (str.charAt(i) == '-') {
                             expSign = -1;
                             i++;
                         } else {
                             expSign = 1;
                         }
                         for ( ; i < strlen ; ) {
                             expVal = expVal * 10 + ((int)str.charAt(i++)-(int)'0');
                         }
                         exponent += expVal * expSign;
                     }
                     exponent += decPos - nDigits;
 
                     // gotta have at least a zero...
                     if (nDigits == 0) {
                         digits[0] = '0';
                         nDigits = 1;
                         exponent = 0;
                     }
 
                     // OK, we now have the significand in digits[0...nDigits]
                     // and the exponent in exponent.  We're ready to format.
 
                     int intDigitsintZeroesintLength;
                     int decDigitsdecZeroesdecLength;
                     byte expChar;
 
                     if (negative) {
                         signChar = '-';
                         width--;
                     } else if ((flags & ) != 0) {
                         signChar = '+';
                         width--;
                     } else if ((flags & ) != 0) {
                         signChar = ' ';
                         width--;
                     } else {
                         signChar = 0;
                     }
                     if ((flags & ) == 0) {
                         precision = 6;
                     }
                     
                     switch(fchar) {
                     case 'E':
                     case 'G':
                         expChar = 'E';
                         break;
                     case 'e':
                     case 'g':
                         expChar = 'e';
                         break;
                     default:
                         expChar = 0;
                     }
 
                     switch (fchar) {
                     case 'g':
                     case 'G':
                         // an empirically derived rule: precision applies to
                         // significand length, irrespective of exponent
 
                         // an official rule, clarified: if the exponent
                         // <clarif>after adjusting for exponent form</clarif>
                         // is < -4,  or the exponent <clarif>after adjusting 
                         // for exponent form</clarif> is greater than the
                         // precision, use exponent form
                         boolean expForm = (exponent + nDigits - 1 < -4 ||
                             exponent + nDigits > (precision == 0 ? 1 : precision));
                         // it would be nice (and logical!) if exponent form 
                         // behaved like E/e, and decimal form behaved like f,
                         // but no such luck. hence: 
                         if (expForm) {
                             // intDigits isn't used here, but if it were, it would be 1
                             /* intDigits = 1; */
                             decDigits = nDigits - 1;
                             // precision for G/g includes integer digits
                             precision = Math.max(0,precision - 1);
 
                             if (precision < decDigits) {
                                 int n = round(digits,nDigits,precision,precision!=0);
                                 if (n > nDigitsnDigits = n;
                                 decDigits = Math.min(nDigits - 1,precision);
                             }
                             exponent += nDigits - 1;
                             
                             boolean isSharp = (flags & ) != 0;
 
                             // deal with length/width
 			    
                             len++; // first digit is always printed
 
                             // MRI behavior: Be default, 2 digits
                             // in the exponent. Use 3 digits
                             // only when necessary.
                             // See comment for writeExp method for more details.
                             if (exponent > 99) {
                             	len += 5; // 5 -> e+nnn / e-nnn
                             } else {
                             	len += 4; // 4 -> e+nn / e-nn
                             }
 
                             if (isSharp) {
                             	// in this mode, '.' is always printed
                             	len++;
                            }
                            if (precision > 0) {
                            	if (!isSharp) {
                            	    // MRI behavior: In this mode
                            	    // trailing zeroes are removed:
                            	    // 1.500E+05 -> 1.5E+05 
                            	    int j = decDigits;
                            	    for (; j >= 1; j--) {
                            	        if (digits[j]== '0') {
                            	            decDigits--;
                            	        } else {
                            	            break;
                            	        }
                            	    }
                            	    if (decDigits > 0) {
                            	        len += 1; // '.' is printed
                            	        len += decDigits;
                            	    }
                            	} else  {
                            	    // all precision numebers printed
                            	    len += precision;
                            	}
                            }
                            width -= len;
                            if (width > 0 && (flags & (|)) == 0) {
                                buf.fill(' ',width);
                                width = 0;
                            }
                            if (signChar != 0) {
                                buf.append(signChar);
                            }
                            if (width > 0 && (flags & ) == 0) {
                                buf.fill('0',width);
                                width = 0;
                            }
                            // now some data...
                            buf.append(digits[0]);
                            boolean dotToPrint = isSharp
                                    || (precision > 0 && decDigits > 0);
                            if (dotToPrint) {
                            	buf.append(args.getDecimalSeparator()); // '.'
                            }
                            if (precision > 0 && decDigits > 0) {
                            	buf.append(digits, 1, decDigits);
                            	precision -= decDigits;
                            }
                            if (precision > 0 && isSharp) {
                            	buf.fill('0'precision);
                            }