Speed up %d for locales with non-ASCII digits.

The small "localizeDigits" part of this change is the big speedup. It takes
%d in the "ar" Locale from 5x more expensive than "en_US", to just a small
percentage more expensive.

The big part of this change: removing the useless Transformer and FloatUtils
classes touches many lines (mostly just changing the level of indentation),
and only has a small percentage effect on performance. The motivation for
this is actually primarily that it makes the code easier to read, and makes
it easier to share code.

Change-Id: I07e86a3ee448510d9ae36579a9af283349f777f1
diff --git a/libcore/luni/src/main/java/java/util/Formatter.java b/libcore/luni/src/main/java/java/util/Formatter.java
index db35ef1..a27e7a1 100644
--- a/libcore/luni/src/main/java/java/util/Formatter.java
+++ b/libcore/luni/src/main/java/java/util/Formatter.java
@@ -529,6 +529,9 @@
 public final class Formatter implements Closeable, Flushable {
     private static final char[] ZEROS = new char[] { '0', '0', '0', '0', '0', '0', '0', '0', '0' };
 
+    // The cached line separator.
+    private static String lineSeparator;
+
     /**
      * The enumeration giving the available styles for formatting very large
      * decimal numbers.
@@ -544,13 +547,16 @@
         DECIMAL_FLOAT
     }
 
+    // User-settable parameters.
     private Appendable out;
-
     private Locale locale;
 
+    // Implementation details.
+    private Object arg;
     private boolean closed = false;
-
+    private FormatToken formatToken;
     private IOException lastIOException;
+    private LocaleData localeData;
 
     /**
      * Constructs a {@code Formatter}.
@@ -824,9 +830,7 @@
      * @throws UnsupportedEncodingException
      *             if the charset with the specified name is not supported.
      */
-    public Formatter(OutputStream os, String csn)
-            throws UnsupportedEncodingException {
-
+    public Formatter(OutputStream os, String csn) throws UnsupportedEncodingException {
         this(os, csn, Locale.getDefault());
     }
 
@@ -870,7 +874,7 @@
         locale = Locale.getDefault();
     }
 
-    private void checkClosed() {
+    private void checkNotClosed() {
         if (closed) {
             throw new FormatterClosedException();
         }
@@ -884,7 +888,7 @@
      *             if the {@code Formatter} has been closed.
      */
     public Locale locale() {
-        checkClosed();
+        checkNotClosed();
         return locale;
     }
 
@@ -896,7 +900,7 @@
      *             if the {@code Formatter} has been closed.
      */
     public Appendable out() {
-        checkClosed();
+        checkNotClosed();
         return out;
     }
 
@@ -911,7 +915,7 @@
      */
     @Override
     public String toString() {
-        checkClosed();
+        checkNotClosed();
         return out.toString();
     }
 
@@ -923,7 +927,7 @@
      *             if the {@code Formatter} has been closed.
      */
     public void flush() {
-        checkClosed();
+        checkNotClosed();
         if (out instanceof Flushable) {
             try {
                 ((Flushable) out).flush();
@@ -986,17 +990,10 @@
      *             if the {@code Formatter} has been closed.
      */
     public Formatter format(String format, Object... args) {
-        doFormat(format, args);
-        return this;
+        return format(this.locale, format, args);
     }
 
     /**
-     * Cached transformer. Improves performance when format() is called multiple
-     * times.
-     */
-    private Transformer transformer;
-
-    /**
      * Writes a formatted string to the output destination of the {@code Formatter}.
      *
      * @param l
@@ -1022,7 +1019,8 @@
     public Formatter format(Locale l, String format, Object... args) {
         Locale originalLocale = locale;
         try {
-            this.locale = l;
+            this.locale = (l == null ? Locale.US : l);
+            this.localeData = LocaleData.get(locale);
             doFormat(format, args);
         } finally {
             this.locale = originalLocale;
@@ -1031,15 +1029,9 @@
     }
 
     private void doFormat(String format, Object... args) {
-        checkClosed();
-
-        // Reuse the previous transformer if the locale matches.
-        if (transformer == null || !transformer.locale.equals(locale)) {
-            transformer = new Transformer(this, locale);
-        }
+        checkNotClosed();
 
         FormatSpecifierParser fsp = new FormatSpecifierParser(format);
-
         int currentObjectIndex = 0;
         Object lastArgument = null;
         boolean hasLastArgumentSet = false;
@@ -1068,7 +1060,7 @@
                     hasLastArgumentSet = true;
                 }
 
-                CharSequence substitution = transformer.transform(token, argument);
+                CharSequence substitution = transform(token, argument);
                 // The substitution is null if we called Formattable.formatTo.
                 if (substitution != null) {
                     outputCharSequence(substitution, 0, substitution.length());
@@ -1398,923 +1390,820 @@
         }
     }
 
+    private NumberFormat getNumberFormat() {
+        return LocaleCache.getNumberFormat(locale);
+    }
+
     /*
-     * Transforms the argument to the formatted string according to the format
-     * information contained in the format token.
+     * Gets the formatted string according to the format token and the
+     * argument.
      */
-    private static class Transformer {
-        private Formatter formatter;
-        private FormatToken formatToken;
-        private Object arg;
-        private Locale locale;
-        private LocaleData localeData;
-        private static String lineSeparator;
+    private CharSequence transform(FormatToken token, Object argument) {
+        this.formatToken = token;
+        this.arg = argument;
 
-        Transformer(Formatter formatter, Locale locale) {
-            this.formatter = formatter;
-            this.locale = (locale == null ? Locale.US : locale);
-            this.localeData = LocaleData.get(locale);
-        }
-
-        private NumberFormat getNumberFormat() {
-            return LocaleCache.getNumberFormat(locale);
-        }
-
-        /*
-         * Gets the formatted string according to the format token and the
-         * argument.
-         */
-        CharSequence transform(FormatToken token, Object argument) {
-            this.formatToken = token;
-            this.arg = argument;
-
-            // There are only two format specifiers that matter: "%d" and "%s".
-            // Nothing else is common in the wild. We fast-path these two to
-            // avoid the heavyweight machinery needed to cope with flags, width,
-            // and precision.
-            if (token.isDefault()) {
-                switch (token.getConversionType()) {
-                case 's':
-                    if (arg == null) {
-                        return "null";
-                    } else if (!(arg instanceof Formattable)) {
-                        return arg.toString();
-                    }
-                    break;
-                case 'd':
-                    if (arg instanceof Integer || arg instanceof Long || arg instanceof Short || arg instanceof Byte) {
-                        if (localeData.zeroDigit == '0') {
-                            return arg.toString();
-                        }
-                    }
-                }
-            }
-
-            formatToken.checkFlags(arg);
-            CharSequence result;
+        // There are only two format specifiers that matter: "%d" and "%s".
+        // Nothing else is common in the wild. We fast-path these two to
+        // avoid the heavyweight machinery needed to cope with flags, width,
+        // and precision.
+        if (token.isDefault()) {
             switch (token.getConversionType()) {
-                case 'B':
-                case 'b': {
-                    result = transformFromBoolean();
-                    break;
+            case 's':
+                if (arg == null) {
+                    return "null";
+                } else if (!(arg instanceof Formattable)) {
+                    return arg.toString();
                 }
-                case 'H':
-                case 'h': {
-                    result = transformFromHashCode();
-                    break;
-                }
-                case 'S':
-                case 's': {
-                    result = transformFromString();
-                    break;
-                }
-                case 'C':
-                case 'c': {
-                    result = transformFromCharacter();
-                    break;
-                }
-                case 'd':
-                case 'o':
-                case 'x':
-                case 'X': {
-                    if (arg == null || arg instanceof BigInteger) {
-                        result = transformFromBigInteger();
-                    } else {
-                        result = transformFromInteger();
-                    }
-                    break;
-                }
-                case 'e':
-                case 'E':
-                case 'g':
-                case 'G':
-                case 'f':
-                case 'a':
-                case 'A': {
-                    result = transformFromFloat();
-                    break;
-                }
-                case '%': {
-                    result = transformFromPercent();
-                    break;
-                }
-                case 'n': {
-                    result = transformFromLineSeparator();
-                    break;
-                }
-                case 't':
-                case 'T': {
-                    result = transformFromDateTime();
-                    break;
-                }
-                default:
-                    throw token.unknownFormatConversionException();
-            }
-
-            if (Character.isUpperCase(token.getConversionType())) {
-                if (result != null) {
-                    result = result.toString().toUpperCase(locale);
+                break;
+            case 'd':
+                if (arg instanceof Integer || arg instanceof Long || arg instanceof Short || arg instanceof Byte) {
+                    String result = arg.toString();
+                    return (localeData.zeroDigit == '0') ? result : localizeDigits(result);
                 }
             }
-            return result;
         }
 
-        private IllegalFormatConversionException badArgumentType() {
-            throw new IllegalFormatConversionException(formatToken.getConversionType(),
-                    arg.getClass());
-        }
-
-        /*
-         * Transforms the Boolean argument to a formatted string.
-         */
-        private CharSequence transformFromBoolean() {
-            CharSequence result;
-            if (arg instanceof Boolean) {
-                result = arg.toString();
-            } else if (arg == null) {
-                result = "false";
+        formatToken.checkFlags(arg);
+        CharSequence result;
+        switch (token.getConversionType()) {
+        case 'B': case 'b':
+            result = transformFromBoolean();
+            break;
+        case 'H': case 'h':
+            result = transformFromHashCode();
+            break;
+        case 'S': case 's':
+            result = transformFromString();
+            break;
+        case 'C': case 'c':
+            result = transformFromCharacter();
+            break;
+        case 'd': case 'o': case 'x': case 'X':
+            if (arg == null || arg instanceof BigInteger) {
+                result = transformFromBigInteger();
             } else {
-                result = "true";
+                result = transformFromInteger();
             }
-            return padding(result, 0);
+            break;
+        case 'A': case 'a': case 'E': case 'e': case 'f': case 'G': case 'g':
+            result = transformFromFloat();
+            break;
+        case '%':
+            result = transformFromPercent();
+            break;
+        case 'n':
+            result = transformFromLineSeparator();
+            break;
+        case 't': case 'T':
+            result = transformFromDateTime();
+            break;
+        default:
+            throw token.unknownFormatConversionException();
         }
 
-        /*
-         * Transforms the hash code of the argument to a formatted string.
-         */
-        private CharSequence transformFromHashCode() {
-            CharSequence result;
-            if (arg == null) {
-                result = "null";
-            } else {
-                result = Integer.toHexString(arg.hashCode());
-            }
-            return padding(result, 0);
-        }
-
-        /*
-         * Transforms the String to a formatted string.
-         */
-        private CharSequence transformFromString() {
-            if (arg instanceof Formattable) {
-                int flags = 0;
-                if (formatToken.flagMinus) {
-                    flags |= FormattableFlags.LEFT_JUSTIFY;
-                }
-                if (formatToken.flagSharp) {
-                    flags |= FormattableFlags.ALTERNATE;
-                }
-                if (Character.isUpperCase(formatToken.getConversionType())) {
-                    flags |= FormattableFlags.UPPERCASE;
-                }
-                ((Formattable) arg).formatTo(formatter, flags, formatToken.getWidth(),
-                        formatToken.getPrecision());
-                // all actions have been taken out in the
-                // Formattable.formatTo, thus there is nothing to do, just
-                // returns null, which tells the Parser to add nothing to the
-                // output.
-                return null;
-            }
-            CharSequence result = arg != null ? arg.toString() : "null";
-            return padding(result, 0);
-        }
-
-        /*
-         * Transforms the Character to a formatted string.
-         */
-        private CharSequence transformFromCharacter() {
-            if (arg == null) {
-                return padding("null", 0);
-            }
-            if (arg instanceof Character) {
-                return padding(String.valueOf(arg), 0);
-            } else if (arg instanceof Byte || arg instanceof Short || arg instanceof Integer) {
-                int codePoint = ((Number) arg).intValue();
-                if (!Character.isValidCodePoint(codePoint)) {
-                    throw new IllegalFormatCodePointException(codePoint);
-                }
-                CharSequence result = (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT)
-                        ? String.valueOf((char) codePoint)
-                        : String.valueOf(Character.toChars(codePoint));
-                return padding(result, 0);
-            } else {
-                throw badArgumentType();
+        if (Character.isUpperCase(token.getConversionType())) {
+            if (result != null) {
+                result = result.toString().toUpperCase(locale);
             }
         }
+        return result;
+    }
 
-        /*
-         * Transforms percent to a formatted string. Only '-' is legal flag.
-         * Precision and arguments are illegal.
-         */
-        private CharSequence transformFromPercent() {
-            return padding("%", 0);
+    private IllegalFormatConversionException badArgumentType() {
+        throw new IllegalFormatConversionException(formatToken.getConversionType(), arg.getClass());
+    }
+
+    /**
+     * Returns a CharSequence corresponding to {@code s} with all the ASCII digits replaced
+     * by digits appropriate to this formatter's locale. Other characters remain unchanged.
+     */
+    private CharSequence localizeDigits(String s) {
+        int length = s.length();
+        int offsetToLocalizedDigits = localeData.zeroDigit - '0';
+        StringBuilder result = new StringBuilder(length);
+        for (int i = 0; i < length; ++i) {
+            char ch = s.charAt(i);
+            if (ch >= '0' && ch <= '9') {
+                ch += offsetToLocalizedDigits;
+            }
+            result.append(ch);
         }
+        return result;
+    }
 
-        /*
-         * Transforms line separator to a formatted string. Any flag, width,
-         * precision or argument is illegal.
-         */
-        private CharSequence transformFromLineSeparator() {
-            if (lineSeparator == null) {
-                lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
-                    public String run() {
-                        return System.getProperty("line.separator");
-                    }
-                });
-            }
-            return lineSeparator;
+    private CharSequence transformFromBoolean() {
+        CharSequence result;
+        if (arg instanceof Boolean) {
+            result = arg.toString();
+        } else if (arg == null) {
+            result = "false";
+        } else {
+            result = "true";
         }
+        return padding(result, 0);
+    }
 
-        /*
-         * Pads characters to the formatted string.
-         */
-        private CharSequence padding(CharSequence source, int startIndex) {
-            boolean sourceIsStringBuilder = (source instanceof StringBuilder);
-
-            int start = startIndex;
-            int width = formatToken.getWidth();
-            int precision = formatToken.getPrecision();
-
-            int length = source.length();
-            if (precision >= 0) {
-                length = Math.min(length, precision);
-                if (sourceIsStringBuilder) {
-                    ((StringBuilder) source).setLength(length);
-                } else {
-                    source = source.subSequence(0, length);
-                }
-            }
-            if (width > 0) {
-                width = Math.max(source.length(), width);
-            }
-            if (length >= width) {
-                return source;
-            }
-
-            char paddingChar = '\u0020'; // space as padding char.
-            if (formatToken.flagZero) {
-                if (formatToken.getConversionType() == 'd') {
-                    paddingChar = localeData.zeroDigit;
-                } else {
-                    paddingChar = '0'; // No localized digits for bases other than decimal.
-                }
-            } else {
-                // if padding char is space, always pad from the start.
-                start = 0;
-            }
-            char[] paddingChars = new char[width - length];
-            Arrays.fill(paddingChars, paddingChar);
-
-            boolean paddingRight = formatToken.flagMinus;
-            StringBuilder result = toStringBuilder(source);
-            if (paddingRight) {
-                result.append(paddingChars);
-            } else {
-                result.insert(start, paddingChars);
-            }
-            return result;
+    private CharSequence transformFromHashCode() {
+        CharSequence result;
+        if (arg == null) {
+            result = "null";
+        } else {
+            result = Integer.toHexString(arg.hashCode());
         }
+        return padding(result, 0);
+    }
 
-        private StringBuilder toStringBuilder(CharSequence cs) {
-            return cs instanceof StringBuilder ? (StringBuilder) cs : new StringBuilder(cs);
-        }
-
-        private StringBuilder wrapParentheses(StringBuilder result) {
-            result.setCharAt(0, '('); // Replace the '-'.
-            if (formatToken.flagZero) {
-                formatToken.setWidth(formatToken.getWidth() - 1);
-                result = (StringBuilder) padding(result, 1);
-                result.append(')');
-            } else {
-                result.append(')');
-                result = (StringBuilder) padding(result, 0);
+    private CharSequence transformFromString() {
+        if (arg instanceof Formattable) {
+            int flags = 0;
+            if (formatToken.flagMinus) {
+                flags |= FormattableFlags.LEFT_JUSTIFY;
             }
-            return result;
-        }
-
-        /*
-         * Transforms the Integer to a formatted string.
-         */
-        private CharSequence transformFromInteger() {
-            int startIndex = 0;
-            StringBuilder result = new StringBuilder();
-            char currentConversionType = formatToken.getConversionType();
-
-            long value;
-            if (arg instanceof Long) {
-                value = ((Long) arg).longValue();
-            } else if (arg instanceof Integer) {
-                value = ((Integer) arg).longValue();
-            } else if (arg instanceof Short) {
-                value = ((Short) arg).longValue();
-            } else if (arg instanceof Byte) {
-                value = ((Byte) arg).longValue();
-            } else {
-                throw badArgumentType();
-            }
-
             if (formatToken.flagSharp) {
-                if (currentConversionType == 'o') {
-                    result.append("0");
-                    startIndex += 1;
-                } else {
-                    result.append("0x");
-                    startIndex += 2;
-                }
+                flags |= FormattableFlags.ALTERNATE;
             }
-
-            if ('d' == currentConversionType) {
-                if (formatToken.flagComma || localeData.zeroDigit != '0') {
-                    NumberFormat numberFormat = getNumberFormat();
-                    numberFormat.setGroupingUsed(formatToken.flagComma);
-                    result.append(numberFormat.format(arg));
-                } else {
-                    result.append(value);
-                }
-
-                if (value < 0) {
-                    if (formatToken.flagParenthesis) {
-                        return wrapParentheses(result);
-                    } else if (formatToken.flagZero) {
-                        startIndex++;
-                    }
-                } else {
-                    if (formatToken.flagAdd) {
-                        result.insert(0, '+');
-                        startIndex += 1;
-                    } else if (formatToken.flagSpace) {
-                        result.insert(0, ' ');
-                        startIndex += 1;
-                    }
-                }
-            } else {
-                // Undo sign-extension, since we'll be using Long.to(Octal|Hex)String.
-                if (arg instanceof Byte) {
-                    value &= 0xffL;
-                } else if (arg instanceof Short) {
-                    value &= 0xffffL;
-                } else if (arg instanceof Integer) {
-                    value &= 0xffffffffL;
-                }
-                if ('o' == currentConversionType) {
-                    result.append(Long.toOctalString(value));
-                } else {
-                    result.append(Long.toHexString(value));
-                }
+            if (Character.isUpperCase(formatToken.getConversionType())) {
+                flags |= FormattableFlags.UPPERCASE;
             }
-
-            return padding(result, startIndex);
+            ((Formattable) arg).formatTo(this, flags, formatToken.getWidth(),
+                    formatToken.getPrecision());
+            // all actions have been taken out in the
+            // Formattable.formatTo, thus there is nothing to do, just
+            // returns null, which tells the Parser to add nothing to the
+            // output.
+            return null;
         }
+        CharSequence result = arg != null ? arg.toString() : "null";
+        return padding(result, 0);
+    }
 
-        private CharSequence transformFromSpecialNumber() {
-            if (!(arg instanceof Number) || arg instanceof BigDecimal) {
-                return null;
-            }
-
-            Number number = (Number) arg;
-            double d = number.doubleValue();
-            String source = null;
-            if (Double.isNaN(d)) {
-                source = "NaN";
-            } else if (d == Double.POSITIVE_INFINITY) {
-                if (formatToken.flagAdd) {
-                    source = "+Infinity";
-                } else if (formatToken.flagSpace) {
-                    source = " Infinity";
-                } else {
-                    source = "Infinity";
-                }
-            } else if (d == Double.NEGATIVE_INFINITY) {
-                if (formatToken.flagParenthesis) {
-                    source = "(Infinity)";
-                } else {
-                    source = "-Infinity";
-                }
-            } else {
-                return null;
-            }
-
-            formatToken.setPrecision(FormatToken.UNSET);
-            formatToken.flagZero = false;
-            return padding(source, 0);
-        }
-
-        private CharSequence transformFromNull() {
-            formatToken.flagZero = false;
+    private CharSequence transformFromCharacter() {
+        if (arg == null) {
             return padding("null", 0);
         }
-
-        /*
-         * Transforms a BigInteger to a formatted string.
-         */
-        private CharSequence transformFromBigInteger() {
-            int startIndex = 0;
-            boolean isNegative = false;
-            StringBuilder result = new StringBuilder();
-            BigInteger bigInt = (BigInteger) arg;
-            char currentConversionType = formatToken.getConversionType();
-
-            if (bigInt == null) {
-                return transformFromNull();
+        if (arg instanceof Character) {
+            return padding(String.valueOf(arg), 0);
+        } else if (arg instanceof Byte || arg instanceof Short || arg instanceof Integer) {
+            int codePoint = ((Number) arg).intValue();
+            if (!Character.isValidCodePoint(codePoint)) {
+                throw new IllegalFormatCodePointException(codePoint);
             }
+            CharSequence result = (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT)
+                    ? String.valueOf((char) codePoint)
+                    : String.valueOf(Character.toChars(codePoint));
+            return padding(result, 0);
+        } else {
+            throw badArgumentType();
+        }
+    }
 
-            isNegative = (bigInt.compareTo(BigInteger.ZERO) < 0);
+    private CharSequence transformFromPercent() {
+        return padding("%", 0);
+    }
 
-            if ('d' == currentConversionType) {
-                NumberFormat numberFormat = getNumberFormat();
-                numberFormat.setGroupingUsed(formatToken.flagComma);
-                result.append(numberFormat.format(bigInt));
-            } else if ('o' == currentConversionType) {
-                // convert BigInteger to a string presentation using radix 8
-                result.append(bigInt.toString(8));
-            } else {
-                // convert BigInteger to a string presentation using radix 16
-                result.append(bigInt.toString(16));
-            }
-            if (formatToken.flagSharp) {
-                startIndex = isNegative ? 1 : 0;
-                if (currentConversionType == 'o') {
-                    result.insert(startIndex, "0");
-                    startIndex += 1;
-                } else if (currentConversionType == 'x' || currentConversionType == 'X') {
-                    result.insert(startIndex, "0x");
-                    startIndex += 2;
+    private CharSequence transformFromLineSeparator() {
+        if (lineSeparator == null) {
+            lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
+                public String run() {
+                    return System.getProperty("line.separator");
                 }
+            });
+        }
+        return lineSeparator;
+    }
+
+    private CharSequence padding(CharSequence source, int startIndex) {
+        int start = startIndex;
+        int width = formatToken.getWidth();
+        int precision = formatToken.getPrecision();
+
+        int length = source.length();
+        if (precision >= 0) {
+            length = Math.min(length, precision);
+            if (source instanceof StringBuilder) {
+                ((StringBuilder) source).setLength(length);
+            } else {
+                source = source.subSequence(0, length);
+            }
+        }
+        if (width > 0) {
+            width = Math.max(source.length(), width);
+        }
+        if (length >= width) {
+            return source;
+        }
+
+        char paddingChar = '\u0020'; // space as padding char.
+        if (formatToken.flagZero) {
+            if (formatToken.getConversionType() == 'd') {
+                paddingChar = localeData.zeroDigit;
+            } else {
+                paddingChar = '0'; // No localized digits for bases other than decimal.
+            }
+        } else {
+            // if padding char is space, always pad from the start.
+            start = 0;
+        }
+        char[] paddingChars = new char[width - length];
+        Arrays.fill(paddingChars, paddingChar);
+
+        boolean paddingRight = formatToken.flagMinus;
+        StringBuilder result = toStringBuilder(source);
+        if (paddingRight) {
+            result.append(paddingChars);
+        } else {
+            result.insert(start, paddingChars);
+        }
+        return result;
+    }
+
+    private StringBuilder toStringBuilder(CharSequence cs) {
+        return cs instanceof StringBuilder ? (StringBuilder) cs : new StringBuilder(cs);
+    }
+
+    private StringBuilder wrapParentheses(StringBuilder result) {
+        result.setCharAt(0, '('); // Replace the '-'.
+        if (formatToken.flagZero) {
+            formatToken.setWidth(formatToken.getWidth() - 1);
+            result = (StringBuilder) padding(result, 1);
+            result.append(')');
+        } else {
+            result.append(')');
+            result = (StringBuilder) padding(result, 0);
+        }
+        return result;
+    }
+
+    private CharSequence transformFromInteger() {
+        int startIndex = 0;
+        StringBuilder result = new StringBuilder();
+        char currentConversionType = formatToken.getConversionType();
+
+        long value;
+        if (arg instanceof Long) {
+            value = ((Long) arg).longValue();
+        } else if (arg instanceof Integer) {
+            value = ((Integer) arg).longValue();
+        } else if (arg instanceof Short) {
+            value = ((Short) arg).longValue();
+        } else if (arg instanceof Byte) {
+            value = ((Byte) arg).longValue();
+        } else {
+            throw badArgumentType();
+        }
+
+        if (formatToken.flagSharp) {
+            if (currentConversionType == 'o') {
+                result.append("0");
+                startIndex += 1;
+            } else {
+                result.append("0x");
+                startIndex += 2;
+            }
+        }
+
+        if ('d' == currentConversionType) {
+            if (formatToken.flagComma) {
+                NumberFormat numberFormat = getNumberFormat();
+                numberFormat.setGroupingUsed(true);
+                result.append(numberFormat.format(arg));
+            } else if (localeData.zeroDigit != '0') {
+                result.append(localizeDigits(Long.toString(value)));
+            } else {
+                result.append(value);
             }
 
-            if (!isNegative) {
+            if (value < 0) {
+                if (formatToken.flagParenthesis) {
+                    return wrapParentheses(result);
+                } else if (formatToken.flagZero) {
+                    startIndex++;
+                }
+            } else {
                 if (formatToken.flagAdd) {
                     result.insert(0, '+');
                     startIndex += 1;
-                }
-                if (formatToken.flagSpace) {
+                } else if (formatToken.flagSpace) {
                     result.insert(0, ' ');
                     startIndex += 1;
                 }
             }
-
-            /* pad paddingChar to the output */
-            if (isNegative && formatToken.flagParenthesis) {
-                return wrapParentheses(result);
+        } else {
+            // Undo sign-extension, since we'll be using Long.to(Octal|Hex)String.
+            if (arg instanceof Byte) {
+                value &= 0xffL;
+            } else if (arg instanceof Short) {
+                value &= 0xffffL;
+            } else if (arg instanceof Integer) {
+                value &= 0xffffffffL;
             }
-            if (isNegative && formatToken.flagZero) {
-                startIndex++;
+            if ('o' == currentConversionType) {
+                result.append(Long.toOctalString(value));
+            } else {
+                result.append(Long.toHexString(value));
             }
-            return padding(result, startIndex);
         }
 
-        /*
-         * Transforms a Float,Double or BigDecimal to a formatted string.
-         */
-        private CharSequence transformFromFloat() {
-            StringBuilder result = new StringBuilder();
-            int startIndex = 0;
-            char currentConversionType = formatToken.getConversionType();
+        return padding(result, startIndex);
+    }
 
-            if (arg == null) {
-                return transformFromNull();
-            }
+    private CharSequence transformFromSpecialNumber() {
+        if (!(arg instanceof Number) || arg instanceof BigDecimal) {
+            return null;
+        }
 
-            if (!(arg instanceof Float || arg instanceof Double || arg instanceof BigDecimal)) {
-                throw badArgumentType();
-            }
-
-            CharSequence specialNumberResult = transformFromSpecialNumber();
-            if (null != specialNumberResult) {
-                return specialNumberResult;
-            }
-
-            if (currentConversionType != 'a' && currentConversionType != 'A' &&
-                    !formatToken.isPrecisionSet()) {
-                formatToken.setPrecision(FormatToken.DEFAULT_PRECISION);
-            }
-
-            // output result
-            FloatUtil floatUtil = new FloatUtil(result, formatToken,
-                    (DecimalFormat) getNumberFormat(), localeData, arg);
-            floatUtil.transform(currentConversionType);
-
-            formatToken.setPrecision(FormatToken.UNSET);
-
-            if (localeData.minusSign == result.charAt(0)) {
-                if (formatToken.flagParenthesis) {
-                    return wrapParentheses(result);
-                }
+        Number number = (Number) arg;
+        double d = number.doubleValue();
+        String source = null;
+        if (Double.isNaN(d)) {
+            source = "NaN";
+        } else if (d == Double.POSITIVE_INFINITY) {
+            if (formatToken.flagAdd) {
+                source = "+Infinity";
+            } else if (formatToken.flagSpace) {
+                source = " Infinity";
             } else {
-                if (formatToken.flagSpace) {
-                    result.insert(0, ' ');
-                    startIndex++;
-                }
-                if (formatToken.flagAdd) {
-                    result.insert(0, floatUtil.getAddSign());
-                    startIndex++;
-                }
+                source = "Infinity";
             }
-
-            char firstChar = result.charAt(0);
-            if (formatToken.flagZero && (firstChar == floatUtil.getAddSign() || firstChar == localeData.minusSign)) {
-                startIndex = 1;
+        } else if (d == Double.NEGATIVE_INFINITY) {
+            if (formatToken.flagParenthesis) {
+                source = "(Infinity)";
+            } else {
+                source = "-Infinity";
             }
+        } else {
+            return null;
+        }
 
-            if (currentConversionType == 'a' || currentConversionType == 'A') {
+        formatToken.setPrecision(FormatToken.UNSET);
+        formatToken.flagZero = false;
+        return padding(source, 0);
+    }
+
+    private CharSequence transformFromNull() {
+        formatToken.flagZero = false;
+        return padding("null", 0);
+    }
+
+    private CharSequence transformFromBigInteger() {
+        int startIndex = 0;
+        StringBuilder result = new StringBuilder();
+        BigInteger bigInt = (BigInteger) arg;
+        char currentConversionType = formatToken.getConversionType();
+
+        if (bigInt == null) {
+            return transformFromNull();
+        }
+
+        boolean isNegative = (bigInt.compareTo(BigInteger.ZERO) < 0);
+
+        if ('d' == currentConversionType) {
+            NumberFormat numberFormat = getNumberFormat();
+            numberFormat.setGroupingUsed(formatToken.flagComma);
+            result.append(numberFormat.format(bigInt));
+        } else if ('o' == currentConversionType) {
+            // convert BigInteger to a string presentation using radix 8
+            result.append(bigInt.toString(8));
+        } else {
+            // convert BigInteger to a string presentation using radix 16
+            result.append(bigInt.toString(16));
+        }
+        if (formatToken.flagSharp) {
+            startIndex = isNegative ? 1 : 0;
+            if (currentConversionType == 'o') {
+                result.insert(startIndex, "0");
+                startIndex += 1;
+            } else if (currentConversionType == 'x' || currentConversionType == 'X') {
+                result.insert(startIndex, "0x");
                 startIndex += 2;
             }
-            return padding(result, startIndex);
         }
 
-        /*
-         * Transforms a Date to a formatted string.
-         */
-        private CharSequence transformFromDateTime() {
-            if (arg == null) {
-                return transformFromNull();
+        if (!isNegative) {
+            if (formatToken.flagAdd) {
+                result.insert(0, '+');
+                startIndex += 1;
             }
+            if (formatToken.flagSpace) {
+                result.insert(0, ' ');
+                startIndex += 1;
+            }
+        }
 
-            Calendar calendar;
-            if (arg instanceof Calendar) {
-                calendar = (Calendar) arg;
-            } else {
-                Date date = null;
-                if (arg instanceof Long) {
-                    date = new Date(((Long) arg).longValue());
-                } else if (arg instanceof Date) {
-                    date = (Date) arg;
-                } else {
-                    throw badArgumentType();
-                }
-                calendar = Calendar.getInstance(locale);
-                calendar.setTime(date);
-            }
+        /* pad paddingChar to the output */
+        if (isNegative && formatToken.flagParenthesis) {
+            return wrapParentheses(result);
+        }
+        if (isNegative && formatToken.flagZero) {
+            startIndex++;
+        }
+        return padding(result, startIndex);
+    }
 
-            StringBuilder result = new StringBuilder();
-            if (!appendT(result, formatToken.getDateSuffix(), calendar)) {
-                throw formatToken.unknownFormatConversionException();
-            }
-            return padding(result, 0);
+    private CharSequence transformFromDateTime() {
+        if (arg == null) {
+            return transformFromNull();
         }
-        
-        private boolean appendT(StringBuilder result, char conversion, Calendar calendar) {
-            switch (conversion) {
-            case 'A':
-                result.append(localeData.longWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
-                return true;
-            case 'a':
-                result.append(localeData.shortWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
-                return true;
-            case 'B':
-                result.append(localeData.longMonthNames[calendar.get(Calendar.MONTH)]);
-                return true;
-            case 'b': case 'h':
-                result.append(localeData.shortMonthNames[calendar.get(Calendar.MONTH)]);
-                return true;
-            case 'C':
-                appendLocalized(result, calendar.get(Calendar.YEAR) / 100, 2);
-                return true;
-            case 'D':
-                appendT(result, 'm', calendar);
-                result.append('/');
-                appendT(result, 'd', calendar);
-                result.append('/');
-                appendT(result, 'y', calendar);
-                return true;
-            case 'F':
-                appendT(result, 'Y', calendar);
-                result.append('-');
-                appendT(result, 'm', calendar);
-                result.append('-');
-                appendT(result, 'd', calendar);
-                return true;
-            case 'H':
-                appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 2);
-                return true;
-            case 'I':
-                appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 2);
-                return true;
-            case 'L':
-                appendLocalized(result, calendar.get(Calendar.MILLISECOND), 3);
-                return true;
-            case 'M':
-                appendLocalized(result, calendar.get(Calendar.MINUTE), 2);
-                return true;
-            case 'N':
-                appendLocalized(result, calendar.get(Calendar.MILLISECOND) * 1000000L, 9);
-                return true;
-            case 'Q':
-                appendLocalized(result, calendar.getTimeInMillis(), 0);
-                return true;
-            case 'R':
-                appendT(result, 'H', calendar);
-                result.append(':');
-                appendT(result, 'M', calendar);
-                return true;
-            case 'S':
-                appendLocalized(result, calendar.get(Calendar.SECOND), 2);
-                return true;
-            case 'T':
-                appendT(result, 'H', calendar);
-                result.append(':');
-                appendT(result, 'M', calendar);
-                result.append(':');
-                appendT(result, 'S', calendar);
-                return true;
-            case 'Y':
-                appendLocalized(result, calendar.get(Calendar.YEAR), 4);
-                return true;
-            case 'Z':
-                TimeZone timeZone = calendar.getTimeZone();
-                result.append(timeZone.getDisplayName(timeZone.inDaylightTime(calendar.getTime()),
-                        TimeZone.SHORT, locale));
-                return true;
-            case 'c':
-                appendT(result, 'a', calendar);
-                result.append(' ');
-                appendT(result, 'b', calendar);
-                result.append(' ');
-                appendT(result, 'd', calendar);
-                result.append(' ');
-                appendT(result, 'T', calendar);
-                result.append(' ');
-                appendT(result, 'Z', calendar);
-                result.append(' ');
-                appendT(result, 'Y', calendar);
-                return true;
-            case 'd':
-                appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 2);
-                return true;
-            case 'e':
-                appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 0);
-                return true;
-            case 'j':
-                appendLocalized(result, calendar.get(Calendar.DAY_OF_YEAR), 3);
-                return true;
-            case 'k':
-                appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 0);
-                return true;
-            case 'l':
-                appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 0);
-                return true;
-            case 'm':
-                // Calendar.JANUARY is 0; humans want January represented as 1.
-                appendLocalized(result, calendar.get(Calendar.MONTH) + 1, 2);
-                return true;
-            case 'p':
-                result.append(localeData.amPm[calendar.get(Calendar.AM_PM)].toLowerCase(locale));
-                return true;
-            case 'r':
-                appendT(result, 'I', calendar);
-                result.append(':');
-                appendT(result, 'M', calendar);
-                result.append(':');
-                appendT(result, 'S', calendar);
-                result.append(' ');
-                result.append(localeData.amPm[calendar.get(Calendar.AM_PM)]);
-                return true;
-            case 's':
-                appendLocalized(result, calendar.getTimeInMillis() / 1000, 0);
-                return true;
-            case 'y':
-                appendLocalized(result, calendar.get(Calendar.YEAR) % 100, 2);
-                return true;
-            case 'z':
-                long offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
-                char sign = '+';
-                if (offset < 0) {
-                    sign = '-';
-                    offset = -offset;
-                }
-                result.append(sign);
-                appendLocalized(result, offset / 3600000, 2);
-                appendLocalized(result, (offset % 3600000) / 60000, 2);
-                return true;
-            }
-            return false;
-        }
-        
-        private int to12Hour(int hour) {
-            return hour == 0 ? 12 : hour;
-        }
-        
-        private void appendLocalized(StringBuilder result, long value, int width) {
-            int paddingIndex = result.length();
-            char zeroDigit = localeData.zeroDigit;
-            if (zeroDigit == '0') {
-                result.append(value);
+
+        Calendar calendar;
+        if (arg instanceof Calendar) {
+            calendar = (Calendar) arg;
+        } else {
+            Date date = null;
+            if (arg instanceof Long) {
+                date = new Date(((Long) arg).longValue());
+            } else if (arg instanceof Date) {
+                date = (Date) arg;
             } else {
-                NumberFormat numberFormat = getNumberFormat();
-                numberFormat.setGroupingUsed(false);
-                result.append(numberFormat.format(value));
+                throw badArgumentType();
             }
-            int zeroCount = width - (result.length() - paddingIndex);
-            if (zeroCount <= 0) {
-                return;
+            calendar = Calendar.getInstance(locale);
+            calendar.setTime(date);
+        }
+
+        StringBuilder result = new StringBuilder();
+        if (!appendT(result, formatToken.getDateSuffix(), calendar)) {
+            throw formatToken.unknownFormatConversionException();
+        }
+        return padding(result, 0);
+    }
+        
+    private boolean appendT(StringBuilder result, char conversion, Calendar calendar) {
+        switch (conversion) {
+        case 'A':
+            result.append(localeData.longWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
+            return true;
+        case 'a':
+            result.append(localeData.shortWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
+            return true;
+        case 'B':
+            result.append(localeData.longMonthNames[calendar.get(Calendar.MONTH)]);
+            return true;
+        case 'b': case 'h':
+            result.append(localeData.shortMonthNames[calendar.get(Calendar.MONTH)]);
+            return true;
+        case 'C':
+            appendLocalized(result, calendar.get(Calendar.YEAR) / 100, 2);
+            return true;
+        case 'D':
+            appendT(result, 'm', calendar);
+            result.append('/');
+            appendT(result, 'd', calendar);
+            result.append('/');
+            appendT(result, 'y', calendar);
+            return true;
+        case 'F':
+            appendT(result, 'Y', calendar);
+            result.append('-');
+            appendT(result, 'm', calendar);
+            result.append('-');
+            appendT(result, 'd', calendar);
+            return true;
+        case 'H':
+            appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 2);
+            return true;
+        case 'I':
+            appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 2);
+            return true;
+        case 'L':
+            appendLocalized(result, calendar.get(Calendar.MILLISECOND), 3);
+            return true;
+        case 'M':
+            appendLocalized(result, calendar.get(Calendar.MINUTE), 2);
+            return true;
+        case 'N':
+            appendLocalized(result, calendar.get(Calendar.MILLISECOND) * 1000000L, 9);
+            return true;
+        case 'Q':
+            appendLocalized(result, calendar.getTimeInMillis(), 0);
+            return true;
+        case 'R':
+            appendT(result, 'H', calendar);
+            result.append(':');
+            appendT(result, 'M', calendar);
+            return true;
+        case 'S':
+            appendLocalized(result, calendar.get(Calendar.SECOND), 2);
+            return true;
+        case 'T':
+            appendT(result, 'H', calendar);
+            result.append(':');
+            appendT(result, 'M', calendar);
+            result.append(':');
+            appendT(result, 'S', calendar);
+            return true;
+        case 'Y':
+            appendLocalized(result, calendar.get(Calendar.YEAR), 4);
+            return true;
+        case 'Z':
+            TimeZone timeZone = calendar.getTimeZone();
+            result.append(timeZone.getDisplayName(timeZone.inDaylightTime(calendar.getTime()),
+                    TimeZone.SHORT, locale));
+            return true;
+        case 'c':
+            appendT(result, 'a', calendar);
+            result.append(' ');
+            appendT(result, 'b', calendar);
+            result.append(' ');
+            appendT(result, 'd', calendar);
+            result.append(' ');
+            appendT(result, 'T', calendar);
+            result.append(' ');
+            appendT(result, 'Z', calendar);
+            result.append(' ');
+            appendT(result, 'Y', calendar);
+            return true;
+        case 'd':
+            appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 2);
+            return true;
+        case 'e':
+            appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 0);
+            return true;
+        case 'j':
+            appendLocalized(result, calendar.get(Calendar.DAY_OF_YEAR), 3);
+            return true;
+        case 'k':
+            appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 0);
+            return true;
+        case 'l':
+            appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 0);
+            return true;
+        case 'm':
+            // Calendar.JANUARY is 0; humans want January represented as 1.
+            appendLocalized(result, calendar.get(Calendar.MONTH) + 1, 2);
+            return true;
+        case 'p':
+            result.append(localeData.amPm[calendar.get(Calendar.AM_PM)].toLowerCase(locale));
+            return true;
+        case 'r':
+            appendT(result, 'I', calendar);
+            result.append(':');
+            appendT(result, 'M', calendar);
+            result.append(':');
+            appendT(result, 'S', calendar);
+            result.append(' ');
+            result.append(localeData.amPm[calendar.get(Calendar.AM_PM)]);
+            return true;
+        case 's':
+            appendLocalized(result, calendar.getTimeInMillis() / 1000, 0);
+            return true;
+        case 'y':
+            appendLocalized(result, calendar.get(Calendar.YEAR) % 100, 2);
+            return true;
+        case 'z':
+            long offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+            char sign = '+';
+            if (offset < 0) {
+                sign = '-';
+                offset = -offset;
             }
-            if (zeroDigit == '0') {
-                result.insert(paddingIndex, ZEROS, 0, zeroCount);
-            } else {
-                for (int i = 0; i < zeroCount; ++i) {
-                    result.insert(paddingIndex, zeroDigit);
-                }
+            result.append(sign);
+            appendLocalized(result, offset / 3600000, 2);
+            appendLocalized(result, (offset % 3600000) / 60000, 2);
+            return true;
+        }
+        return false;
+    }
+    
+    private int to12Hour(int hour) {
+        return hour == 0 ? 12 : hour;
+    }
+    
+    private void appendLocalized(StringBuilder result, long value, int width) {
+        int paddingIndex = result.length();
+        char zeroDigit = localeData.zeroDigit;
+        if (zeroDigit == '0') {
+            result.append(value);
+        } else {
+            result.append(localizeDigits(Long.toString(value)));
+        }
+        int zeroCount = width - (result.length() - paddingIndex);
+        if (zeroCount <= 0) {
+            return;
+        }
+        if (zeroDigit == '0') {
+            result.insert(paddingIndex, ZEROS, 0, zeroCount);
+        } else {
+            for (int i = 0; i < zeroCount; ++i) {
+                result.insert(paddingIndex, zeroDigit);
             }
         }
     }
 
-    // TODO: merge this into Transformer; the distinction is not obviously useful.
-    private static class FloatUtil {
-        private final StringBuilder result;
-        private final DecimalFormat decimalFormat;
-        private final LocaleData localeData;
-        private final FormatToken formatToken;
-        private final Object argument;
-
-        FloatUtil(StringBuilder result, FormatToken formatToken, DecimalFormat decimalFormat,
-                LocaleData localeData, Object argument) {
-            this.result = result;
-            this.formatToken = formatToken;
-            this.decimalFormat = decimalFormat;
-            this.localeData = localeData;
-            this.argument = argument;
+    private CharSequence transformFromFloat() {
+        if (arg == null) {
+            return transformFromNull();
         }
 
-        void transform(char conversionType) {
-            switch (conversionType) {
-            case 'a': case 'A':
-                transform_a();
-                break;
-            case 'e': case 'E':
-                transform_e();
-                break;
-            case 'f':
-                transform_f();
-                break;
-            case 'g':
-            case 'G':
-                transform_g();
-                break;
-            default:
-                throw formatToken.unknownFormatConversionException();
+        if (!(arg instanceof Float || arg instanceof Double || arg instanceof BigDecimal)) {
+            throw badArgumentType();
+        }
+
+        CharSequence specialNumberResult = transformFromSpecialNumber();
+        if (specialNumberResult != null) {
+            return specialNumberResult;
+        }
+
+        char conversionType = formatToken.getConversionType();
+        if (conversionType != 'a' && conversionType != 'A' && !formatToken.isPrecisionSet()) {
+            formatToken.setPrecision(FormatToken.DEFAULT_PRECISION);
+        }
+
+        StringBuilder result = new StringBuilder();
+        switch (conversionType) {
+        case 'a': case 'A':
+            transform_a(result);
+            break;
+        case 'e': case 'E':
+            transform_e(result);
+            break;
+        case 'f':
+            transform_f(result);
+            break;
+        case 'g':
+        case 'G':
+            transform_g(result);
+            break;
+        default:
+            throw formatToken.unknownFormatConversionException();
+        }
+
+        formatToken.setPrecision(FormatToken.UNSET);
+
+        int startIndex = 0;
+        if (localeData.minusSign == result.charAt(0)) {
+            if (formatToken.flagParenthesis) {
+                return wrapParentheses(result);
+            }
+        } else {
+            if (formatToken.flagSpace) {
+                result.insert(0, ' ');
+                startIndex++;
+            }
+            if (formatToken.flagAdd) {
+                result.insert(0, '+');
+                startIndex++;
             }
         }
 
-        char getAddSign() {
-            return '+';
+        char firstChar = result.charAt(0);
+        if (formatToken.flagZero && (firstChar == '+' || firstChar == localeData.minusSign)) {
+            startIndex = 1;
         }
 
-        void transform_e() {
-            StringBuilder pattern = new StringBuilder();
-            pattern.append('0');
-            if (formatToken.getPrecision() > 0) {
-                pattern.append('.');
-                char[] zeros = new char[formatToken.getPrecision()];
-                Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
-                pattern.append(zeros);
-            }
-            pattern.append("E+00");
-            decimalFormat.applyPattern(pattern.toString());
-            String formattedString = decimalFormat.format(argument);
-            result.append(formattedString.replace('E', 'e'));
-
-            // if the flag is sharp and decimal separator is always given out.
-            if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
-                int indexOfE = result.indexOf("e");
-                result.insert(indexOfE, localeData.decimalSeparator);
-            }
+        if (conversionType == 'a' || conversionType == 'A') {
+            startIndex += 2;
         }
+        return padding(result, startIndex);
+    }
 
-        void transform_g() {
-            int precision = formatToken.getPrecision();
-            precision = (0 == precision ? 1 : precision);
+    private void transform_e(StringBuilder result) {
+        StringBuilder pattern = new StringBuilder();
+        pattern.append('0');
+        if (formatToken.getPrecision() > 0) {
+            pattern.append('.');
+            char[] zeros = new char[formatToken.getPrecision()];
+            Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
+            pattern.append(zeros);
+        }
+        pattern.append("E+00");
+        DecimalFormat decimalFormat = (DecimalFormat) getNumberFormat();
+        decimalFormat.applyPattern(pattern.toString());
+        String formattedString = decimalFormat.format(arg);
+        result.append(formattedString.replace('E', 'e'));
+
+        // if the flag is sharp and decimal separator is always given out.
+        if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
+            int indexOfE = result.indexOf("e");
+            result.insert(indexOfE, localeData.decimalSeparator);
+        }
+    }
+
+    private void transform_g(StringBuilder result) {
+        int precision = formatToken.getPrecision();
+        precision = (0 == precision ? 1 : precision);
+        formatToken.setPrecision(precision);
+
+        double d = ((Number) arg).doubleValue();
+        if (d == 0.0) {
+            precision--;
             formatToken.setPrecision(precision);
+            transform_f(result);
+            return;
+        }
 
-            if (0.0 == ((Number) argument).doubleValue()) {
-                precision--;
+        boolean requireScientificRepresentation = true;
+        d = Math.abs(d);
+        if (Double.isInfinite(d)) {
+            precision = formatToken.getPrecision();
+            precision--;
+            formatToken.setPrecision(precision);
+            transform_e(result);
+            return;
+        }
+        BigDecimal b = new BigDecimal(d, new MathContext(precision));
+        d = b.doubleValue();
+        long l = b.longValue();
+
+        if (d >= 1 && d < Math.pow(10, precision)) {
+            if (l < Math.pow(10, precision)) {
+                requireScientificRepresentation = false;
+                precision -= String.valueOf(l).length();
+                precision = precision < 0 ? 0 : precision;
+                l = Math.round(d * Math.pow(10, precision + 1));
+                if (String.valueOf(l).length() <= formatToken.getPrecision()) {
+                    precision++;
+                }
                 formatToken.setPrecision(precision);
-                transform_f();
-                return;
             }
-
-            boolean requireScientificRepresentation = true;
-            double d = ((Number) argument).doubleValue();
-            d = Math.abs(d);
-            if (Double.isInfinite(d)) {
-                precision = formatToken.getPrecision();
-                precision--;
-                formatToken.setPrecision(precision);
-                transform_e();
-                return;
-            }
-            BigDecimal b = new BigDecimal(d, new MathContext(precision));
-            d = b.doubleValue();
-            long l = b.longValue();
-
-            if (d >= 1 && d < Math.pow(10, precision)) {
-                if (l < Math.pow(10, precision)) {
-                    requireScientificRepresentation = false;
-                    precision -= String.valueOf(l).length();
-                    precision = precision < 0 ? 0 : precision;
-                    l = Math.round(d * Math.pow(10, precision + 1));
-                    if (String.valueOf(l).length() <= formatToken
-                            .getPrecision()) {
-                        precision++;
-                    }
+        } else {
+            l = b.movePointRight(4).longValue();
+            if (d >= Math.pow(10, -4) && d < 1) {
+                requireScientificRepresentation = false;
+                precision += 4 - String.valueOf(l).length();
+                l = b.movePointRight(precision + 1).longValue();
+                if (String.valueOf(l).length() <= formatToken.getPrecision()) {
+                    precision++;
+                }
+                l = b.movePointRight(precision).longValue();
+                if (l >= Math.pow(10, precision - 4)) {
                     formatToken.setPrecision(precision);
                 }
+            }
+        }
+        if (requireScientificRepresentation) {
+            precision = formatToken.getPrecision();
+            precision--;
+            formatToken.setPrecision(precision);
+            transform_e(result);
+        } else {
+            transform_f(result);
+        }
+    }
 
-            } else {
-                l = b.movePointRight(4).longValue();
-                if (d >= Math.pow(10, -4) && d < 1) {
-                    requireScientificRepresentation = false;
-                    precision += 4 - String.valueOf(l).length();
-                    l = b.movePointRight(precision + 1).longValue();
-                    if (String.valueOf(l).length() <= formatToken
-                            .getPrecision()) {
-                        precision++;
-                    }
-                    l = b.movePointRight(precision).longValue();
-                    if (l >= Math.pow(10, precision - 4)) {
-                        formatToken.setPrecision(precision);
-                    }
+    private void transform_f(StringBuilder result) {
+        // TODO: store a default DecimalFormat we can clone?
+        String pattern = "0.000000";
+        DecimalFormat decimalFormat = (DecimalFormat) getNumberFormat();
+        if (formatToken.flagComma || formatToken.getPrecision() != 6) {
+            StringBuilder patternBuilder = new StringBuilder();
+            if (formatToken.flagComma) {
+                patternBuilder.append(',');
+                int groupingSize = decimalFormat.getGroupingSize();
+                if (groupingSize > 1) {
+                    char[] sharps = new char[groupingSize - 1];
+                    Arrays.fill(sharps, '#');
+                    patternBuilder.append(sharps);
                 }
             }
-            if (requireScientificRepresentation) {
-                precision = formatToken.getPrecision();
-                precision--;
-                formatToken.setPrecision(precision);
-                transform_e();
-            } else {
-                transform_f();
+            patternBuilder.append('0');
+            if (formatToken.getPrecision() > 0) {
+                patternBuilder.append('.');
+                char[] zeros = new char[formatToken.getPrecision()];
+                Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
+                patternBuilder.append(zeros);
             }
+            pattern = patternBuilder.toString();
+        }
+        // TODO: if DecimalFormat.toPattern was cheap, we could make this cheap (preferably *in* DecimalFormat).
+        decimalFormat.applyPattern(pattern);
+        result.append(decimalFormat.format(arg));
+        // if the flag is sharp and decimal separator is always given out.
+        if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
+            result.append(localeData.decimalSeparator);
+        }
+    }
+
+    private void transform_a(StringBuilder result) {
+        if (arg instanceof Float) {
+            result.append(Float.toHexString(((Float) arg).floatValue()));
+        } else if (arg instanceof Double) {
+            result.append(Double.toHexString(((Double) arg).doubleValue()));
+        } else {
+            throw badArgumentType();
         }
 
-        void transform_f() {
-            // TODO: store a default DecimalFormat we can clone?
-            String pattern = "0.000000";
-            if (formatToken.flagComma || formatToken.getPrecision() != 6) {
-                StringBuilder patternBuilder = new StringBuilder();
-                if (formatToken.flagComma) {
-                    patternBuilder.append(',');
-                    int groupingSize = decimalFormat.getGroupingSize();
-                    if (groupingSize > 1) {
-                        char[] sharps = new char[groupingSize - 1];
-                        Arrays.fill(sharps, '#');
-                        patternBuilder.append(sharps);
-                    }
-                }
-                patternBuilder.append('0');
-                if (formatToken.getPrecision() > 0) {
-                    patternBuilder.append('.');
-                    char[] zeros = new char[formatToken.getPrecision()];
-                    Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
-                    patternBuilder.append(zeros);
-                }
-                pattern = patternBuilder.toString();
-            }
-            // TODO: if DecimalFormat.toPattern was cheap, we could make this cheap (preferably *in* DecimalFormat).
-            decimalFormat.applyPattern(pattern);
-            result.append(decimalFormat.format(argument));
-            // if the flag is sharp and decimal separator is always given out.
-            if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
-                result.append(localeData.decimalSeparator);
-            }
+        if (!formatToken.isPrecisionSet()) {
+            return;
         }
 
-        void transform_a() {
-            if (argument instanceof Float) {
-                Float F = (Float) argument;
-                result.append(Float.toHexString(F.floatValue()));
-            } else if (argument instanceof Double) {
-                Double D = (Double) argument;
-                result.append(Double.toHexString(D.doubleValue()));
-            } else {
-                throw badArgumentType();
-            }
+        int precision = formatToken.getPrecision();
+        precision = (0 == precision ? 1 : precision);
+        int indexOfFirstFractionalDigit = result.indexOf(".") + 1;
+        int indexOfP = result.indexOf("p");
+        int fractionalLength = indexOfP - indexOfFirstFractionalDigit;
 
-            if (!formatToken.isPrecisionSet()) {
-                return;
-            }
-
-            int precision = formatToken.getPrecision();
-            precision = (0 == precision ? 1 : precision);
-            int indexOfFirstFractionalDigit = result.indexOf(".") + 1;
-            int indexOfP = result.indexOf("p");
-            int fractionalLength = indexOfP - indexOfFirstFractionalDigit;
-
-            if (fractionalLength == precision) {
-                return;
-            }
-
-            if (fractionalLength < precision) {
-                char zeros[] = new char[precision - fractionalLength];
-                Arrays.fill(zeros, '0'); // %a shouldn't be localized.
-                result.insert(indexOfP, zeros);
-                return;
-            }
-            result.delete(indexOfFirstFractionalDigit + precision, indexOfP);
+        if (fractionalLength == precision) {
+            return;
         }
 
-        private IllegalFormatConversionException badArgumentType() {
-            throw new IllegalFormatConversionException(formatToken.getConversionType(),
-                    argument.getClass());
+        if (fractionalLength < precision) {
+            char zeros[] = new char[precision - fractionalLength];
+            Arrays.fill(zeros, '0'); // %a shouldn't be localized.
+            result.insert(indexOfP, zeros);
+            return;
         }
+        result.delete(indexOfFirstFractionalDigit + precision, indexOfP);
     }
 
     private static class FormatSpecifierParser {