[Jython-checkins] jython (merge default -> default): Merge float formatting work to trunk

jeff.allen jython-checkins at python.org
Thu Apr 24 00:04:13 CEST 2014


http://hg.python.org/jython/rev/3fef65b876ec
changeset:   7218:3fef65b876ec
parent:      7211:e9c44964857e
parent:      7217:1beea214e208
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Wed Apr 23 20:39:02 2014 +0100
summary:
  Merge float formatting work to trunk

files:
  Lib/test/test_complex.py                                    |  213 ++-
  Lib/test/test_float.py                                      |   52 +-
  Lib/test/test_float_jy.py                                   |   19 +-
  src/org/python/core/PyComplex.java                          |  203 +-
  src/org/python/core/PyFloat.java                            |  277 +-
  src/org/python/core/PyInteger.java                          |   93 +-
  src/org/python/core/PyString.java                           |   69 +-
  src/org/python/core/__builtin__.java                        |   21 +-
  src/org/python/core/stringlib/FloatFormatter.java           |  880 ++++++++++
  src/org/python/core/stringlib/Formatter.java                |  247 --
  src/org/python/core/stringlib/InternalFormat.java           |  824 +++++++++
  src/org/python/core/stringlib/InternalFormatSpec.java       |   58 +-
  src/org/python/core/stringlib/InternalFormatSpecParser.java |   29 +-
  src/org/python/core/util/ExtraMath.java                     |   54 +-
  14 files changed, 2443 insertions(+), 596 deletions(-)


diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -220,6 +220,7 @@
     def test_conjugate(self):
         self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)
 
+    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_constructor(self):
         class OS:
             def __init__(self, value): self.value = value
@@ -264,6 +265,188 @@
         self.assertAlmostEqual(complex("-1"), -1)
         self.assertAlmostEqual(complex("+1"), +1)
         #FIXME: these are not working in Jython.
+        self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
+        self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
+        # ]
+        self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
+        #FIXME: these are not working in Jython.
+        self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j)
+        self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j)
+        self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j)
+        # ]
+        self.assertAlmostEqual(complex("J"), 1j)
+        #FIXME: this is not working in Jython.
+        self.assertAlmostEqual(complex("( j )"), 1j)
+        # ]
+        self.assertAlmostEqual(complex("+J"), 1j)
+        #FIXME: this is not working in Jython.
+        self.assertAlmostEqual(complex("( -j)"), -1j)
+        # ]
+        self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j)
+        self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
+        self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)
+
+        class complex2(complex): pass
+        self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j)
+        self.assertAlmostEqual(complex(real=17, imag=23), 17+23j)
+        self.assertAlmostEqual(complex(real=17+23j), 17+23j)
+        self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j)
+        self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j)
+
+        # check that the sign of a zero in the real or imaginary part
+        # is preserved when constructing from two floats.  (These checks
+        # are harmless on systems without support for signed zeros.)
+        def split_zeros(x):
+            """Function that produces different results for 0. and -0."""
+            return atan2(x, -1.)
+
+        self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.))
+        #FIXME: this is not working in Jython.
+        self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.))
+        # ]
+        self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.))
+        self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.))
+
+        c = 3.14 + 1j
+        self.assertTrue(complex(c) is c)
+        del c
+
+        self.assertRaises(TypeError, complex, "1", "1")
+        self.assertRaises(TypeError, complex, 1, "1")
+
+        if test_support.have_unicode:
+            self.assertEqual(complex(unicode("  3.14+J  ")), 3.14+1j)
+
+        # SF bug 543840:  complex(string) accepts strings with \0
+        # Fixed in 2.3.
+        self.assertRaises(ValueError, complex, '1+1j\0j')
+
+        self.assertRaises(TypeError, int, 5+3j)
+        self.assertRaises(TypeError, long, 5+3j)
+        self.assertRaises(TypeError, float, 5+3j)
+        self.assertRaises(ValueError, complex, "")
+        self.assertRaises(TypeError, complex, None)
+        self.assertRaises(ValueError, complex, "\0")
+        self.assertRaises(ValueError, complex, "3\09")
+        self.assertRaises(TypeError, complex, "1", "2")
+        self.assertRaises(TypeError, complex, "1", 42)
+        self.assertRaises(TypeError, complex, 1, "2")
+        self.assertRaises(ValueError, complex, "1+")
+        self.assertRaises(ValueError, complex, "1+1j+1j")
+        self.assertRaises(ValueError, complex, "--")
+        self.assertRaises(ValueError, complex, "(1+2j")
+        self.assertRaises(ValueError, complex, "1+2j)")
+        self.assertRaises(ValueError, complex, "1+(2j)")
+        self.assertRaises(ValueError, complex, "(1+2j)123")
+        if test_support.have_unicode:
+            self.assertRaises(ValueError, complex, unicode("x"))
+        #FIXME: these are raising wrong errors in Jython.
+        self.assertRaises(ValueError, complex, "1j+2")
+        self.assertRaises(ValueError, complex, "1e1ej")
+        self.assertRaises(ValueError, complex, "1e++1ej")
+        self.assertRaises(ValueError, complex, ")1+2j(")
+        # ]
+
+        # the following three are accepted by Python 2.6
+        #FIXME: these are raising wrong errors in Jython.
+        self.assertRaises(ValueError, complex, "1..1j")
+        self.assertRaises(ValueError, complex, "1.11.1j")
+        self.assertRaises(ValueError, complex, "1e1.1j")
+        # ]
+
+        #FIXME: not working in Jython.
+        if test_support.have_unicode:
+            # check that complex accepts long unicode strings
+            self.assertEqual(type(complex(unicode("1"*500))), complex)
+        # ]
+
+        class EvilExc(Exception):
+            pass
+
+        class evilcomplex:
+            def __complex__(self):
+                raise EvilExc
+
+        self.assertRaises(EvilExc, complex, evilcomplex())
+
+        class float2:
+            def __init__(self, value):
+                self.value = value
+            def __float__(self):
+                return self.value
+
+        self.assertAlmostEqual(complex(float2(42.)), 42)
+        self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
+        self.assertRaises(TypeError, complex, float2(None))
+
+        class complex0(complex):
+            """Test usage of __complex__() when inheriting from 'complex'"""
+            def __complex__(self):
+                return 42j
+
+        class complex1(complex):
+            """Test usage of __complex__() with a __new__() method"""
+            def __new__(self, value=0j):
+                return complex.__new__(self, 2*value)
+            def __complex__(self):
+                return self
+
+        class complex2(complex):
+            """Make sure that __complex__() calls fail if anything other than a
+            complex is returned"""
+            def __complex__(self):
+                return None
+
+        self.assertAlmostEqual(complex(complex0(1j)), 42j)
+        self.assertAlmostEqual(complex(complex1(1j)), 2j)
+        self.assertRaises(TypeError, complex, complex2(1j))
+
+    def test_constructor_jy(self):
+        # These are the parts of test_constructor that work in Jython.
+        # Delete this test when test_constructor skip is removed.
+        class OS:
+            def __init__(self, value): self.value = value
+            def __complex__(self): return self.value
+        class NS(object):
+            def __init__(self, value): self.value = value
+            def __complex__(self): return self.value
+        self.assertEqual(complex(OS(1+10j)), 1+10j)
+        self.assertEqual(complex(NS(1+10j)), 1+10j)
+        self.assertRaises(TypeError, complex, OS(None))
+        self.assertRaises(TypeError, complex, NS(None))
+
+        self.assertAlmostEqual(complex("1+10j"), 1+10j)
+        self.assertAlmostEqual(complex(10), 10+0j)
+        self.assertAlmostEqual(complex(10.0), 10+0j)
+        self.assertAlmostEqual(complex(10L), 10+0j)
+        self.assertAlmostEqual(complex(10+0j), 10+0j)
+        self.assertAlmostEqual(complex(1,10), 1+10j)
+        self.assertAlmostEqual(complex(1,10L), 1+10j)
+        self.assertAlmostEqual(complex(1,10.0), 1+10j)
+        self.assertAlmostEqual(complex(1L,10), 1+10j)
+        self.assertAlmostEqual(complex(1L,10L), 1+10j)
+        self.assertAlmostEqual(complex(1L,10.0), 1+10j)
+        self.assertAlmostEqual(complex(1.0,10), 1+10j)
+        self.assertAlmostEqual(complex(1.0,10L), 1+10j)
+        self.assertAlmostEqual(complex(1.0,10.0), 1+10j)
+        self.assertAlmostEqual(complex(3.14+0j), 3.14+0j)
+        self.assertAlmostEqual(complex(3.14), 3.14+0j)
+        self.assertAlmostEqual(complex(314), 314.0+0j)
+        self.assertAlmostEqual(complex(314L), 314.0+0j)
+        self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j)
+        self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j)
+        self.assertAlmostEqual(complex(314, 0), 314.0+0j)
+        self.assertAlmostEqual(complex(314L, 0L), 314.0+0j)
+        self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j)
+        self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j)
+        self.assertAlmostEqual(complex(0j, 3.14), 3.14j)
+        self.assertAlmostEqual(complex(0.0, 3.14), 3.14j)
+        self.assertAlmostEqual(complex("1"), 1+0j)
+        self.assertAlmostEqual(complex("1j"), 1j)
+        self.assertAlmostEqual(complex(),  0)
+        self.assertAlmostEqual(complex("-1"), -1)
+        self.assertAlmostEqual(complex("+1"), +1)
+        #FIXME: these are not working in Jython.
         #self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
         #self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
         self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
@@ -458,7 +641,7 @@
         for num in nums:
             self.assertAlmostEqual((num.real**2 + num.imag**2)  ** 0.5, abs(num))
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
+    @unittest.skipIf(test_support.is_jython, "FIXME: str.__complex__ not working in Jython")
     def test_repr(self):
         self.assertEqual(repr(1+6j), '(1+6j)')
         self.assertEqual(repr(1-6j), '(1-6j)')
@@ -482,6 +665,32 @@
         self.assertEqual(repr(complex(0, -INF)), "-infj")
         self.assertEqual(repr(complex(0, NAN)), "nanj")
 
+    def test_repr_jy(self):
+        # These are just the cases that Jython can do from test_repr
+        # Delete this test when test_repr passes
+        self.assertEqual(repr(1+6j), '(1+6j)')
+        self.assertEqual(repr(1-6j), '(1-6j)')
+
+        self.assertNotEqual(repr(-(1+0j)), '(-1+-0j)')
+
+        # Fails to round-trip:
+#         self.assertEqual(1-6j,complex(repr(1-6j)))
+#         self.assertEqual(1+6j,complex(repr(1+6j)))
+#         self.assertEqual(-6j,complex(repr(-6j)))
+#         self.assertEqual(6j,complex(repr(6j)))
+
+        self.assertEqual(repr(complex(1., INF)), "(1+infj)")
+        self.assertEqual(repr(complex(1., -INF)), "(1-infj)")
+        self.assertEqual(repr(complex(INF, 1)), "(inf+1j)")
+        self.assertEqual(repr(complex(-INF, INF)), "(-inf+infj)")
+        self.assertEqual(repr(complex(NAN, 1)), "(nan+1j)")
+        self.assertEqual(repr(complex(1, NAN)), "(1+nanj)")
+        self.assertEqual(repr(complex(NAN, NAN)), "(nan+nanj)")
+
+        self.assertEqual(repr(complex(0, INF)), "infj")
+        self.assertEqual(repr(complex(0, -INF)), "-infj")
+        self.assertEqual(repr(complex(0, NAN)), "nanj")
+
     def test_neg(self):
         self.assertEqual(-(1+6j), -1-6j)
 
@@ -501,7 +710,6 @@
                 fo.close()
             test_support.unlink(test_support.TESTFN)
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_getnewargs(self):
         self.assertEqual((1+2j).__getnewargs__(), (1.0, 2.0))
         self.assertEqual((1-2j).__getnewargs__(), (1.0, -2.0))
@@ -557,7 +765,6 @@
                 self.assertFloatsAreIdentical(0.0 + z.imag,
                                               0.0 + roundtrip.imag)
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_format(self):
         # empty format string is same as str()
         self.assertEqual(format(1+3j, ''), str(1+3j))
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -76,8 +76,6 @@
         # Double check.
         self.assertEqual(type(int(n)), type(n))
 
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: int boundries not right on Jython")
     def test_conversion_to_int(self):
         # Check that floats within the range of an int convert to type
         # int, not long.  (issue #11144.)
@@ -638,16 +636,12 @@
                 if not math.isnan(arg) and copysign(1.0, arg) > 0.0:
                     self.assertEqual(fmt % -arg, '-' + rhs)
 
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: not working on Jython")
     def test_issue5864(self):
         self.assertEqual(format(123.456, '.4'), '123.5')
         self.assertEqual(format(1234.56, '.4'), '1.235e+03')
         self.assertEqual(format(12345.6, '.4'), '1.235e+04')
 
 class ReprTestCase(unittest.TestCase):
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: not working on Jython")
     def test_repr(self):
         floats_file = open(os.path.join(os.path.split(__file__)[0],
                            'floating_points.txt'))
@@ -736,11 +730,9 @@
 
         self.assertRaises(TypeError, round, INF, 0.0)
         self.assertRaises(TypeError, round, -INF, 1.0)
-        self.assertRaises(TypeError, round, NAN, "ceci n'est pas un integer")
+        self.assertRaises(TypeError, round, NAN, "ceci n'est pas un entier")
         self.assertRaises(TypeError, round, -0.0, 1j)
 
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: not working in Jython")
     def test_large_n(self):
         for n in [324, 325, 400, 2**31-1, 2**31, 2**32, 2**100]:
             self.assertEqual(round(123.456, n), 123.456)
@@ -753,8 +745,6 @@
         self.assertEqual(round(1e150, 309), 1e150)
         self.assertEqual(round(1.4e-315, 315), 1e-315)
 
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: not working in Jython")
     def test_small_n(self):
         for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]:
             self.assertEqual(round(123.456, n), 0.0)
@@ -762,8 +752,6 @@
             self.assertEqual(round(1e300, n), 0.0)
             self.assertEqual(round(1e-320, n), 0.0)
 
-    @unittest.skipIf(test_support.is_jython,
-                     "FIXME: not working in Jython")
     def test_overflow(self):
         self.assertRaises(OverflowError, round, 1.6e308, -308)
         self.assertRaises(OverflowError, round, -1.7e308, -308)
@@ -855,7 +843,7 @@
 
 
     @unittest.skipIf(test_support.is_jython,
-                     "FIXME: formatting specials imperfect in Jython")
+                     "FIXME: %-formatting specials imperfect in Jython")
     @requires_IEEE_754
     def test_format_specials(self):
         # Test formatting of nans and infs.
@@ -890,6 +878,42 @@
             test(sfmt, NAN, ' nan')
             test(sfmt, -NAN, ' nan')
 
+    @requires_IEEE_754
+    def test_format_specials_jy(self):
+        # Test formatting of nans and infs (suppressing %-formatting).
+        # This is just a crudely restricted copy of test_format_specials.
+        # Delete this test when we no longer have to skip test_format_specials.
+
+        def test(fmt, value, expected):
+            # Test with only format().
+            #self.assertEqual(fmt % value, expected, fmt)
+            if not '#' in fmt:
+                # Until issue 7094 is implemented, format() for floats doesn't
+                #  support '#' formatting
+                fmt = fmt[1:] # strip off the %
+                self.assertEqual(format(value, fmt), expected, fmt)
+
+        for fmt in ['%e', '%f', '%g', '%.0e', '%.6f', '%.20g',
+                    '%#e', '%#f', '%#g', '%#.20e', '%#.15f', '%#.3g']:
+            pfmt = '%+' + fmt[1:]
+            sfmt = '% ' + fmt[1:]
+            test(fmt, INF, 'inf')
+            test(fmt, -INF, '-inf')
+            test(fmt, NAN, 'nan')
+            test(fmt, -NAN, 'nan')
+            # When asking for a sign, it's always provided. nans are
+            #  always positive.
+            test(pfmt, INF, '+inf')
+            test(pfmt, -INF, '-inf')
+            test(pfmt, NAN, '+nan')
+            test(pfmt, -NAN, '+nan')
+            # When using ' ' for a sign code, only infs can be negative.
+            #  Others have a space.
+            test(sfmt, INF, ' inf')
+            test(sfmt, -INF, '-inf')
+            test(sfmt, NAN, ' nan')
+            test(sfmt, -NAN, ' nan')
+
 
 # Beginning with Python 2.6 float has cross platform compatible
 # ways to create and represent inf and nan
diff --git a/Lib/test/test_float_jy.py b/Lib/test/test_float_jy.py
--- a/Lib/test/test_float_jy.py
+++ b/Lib/test/test_float_jy.py
@@ -14,19 +14,22 @@
     def test_float_repr(self):
         self.assertEqual(repr(12345678.000000005), '12345678.000000006')
         self.assertEqual(repr(12345678.0000000005), '12345678.0')
-        self.assertEqual(repr(math.pi**-100),
-                         jython and '1.9275814160560203e-50' or '1.9275814160560206e-50')
+        self.assertEqual(repr(math.pi**-100), '1.9275814160560206e-50')
         self.assertEqual(repr(-1.0), '-1.0')
-        self.assertEqual(repr(-9876.543210),
-                         jython and '-9876.54321' or '-9876.5432099999998')
+        self.assertEqual(repr(-9876.543210), '-9876.54321')
         self.assertEqual(repr(0.123456789e+35), '1.23456789e+34')
 
+    def test_float_repr2(self):
+        # Quite possibly these divergences result from JDK bug JDK-4511638:
+        self.assertEqual(repr(9876.543210e+15),
+                              jython and '9.876543209999999e+18' or '9.87654321e+18')
+        self.assertEqual(repr(1235235235235240000.0),
+                              jython and '1.2352352352352399e+18' or '1.23523523523524e+18')
+
     def test_float_str(self):
         self.assertEqual(str(12345678.000005), '12345678.0')
-        self.assertEqual(str(12345678.00005),
-                         jython and '12345678.0' or '12345678.0001')
-        self.assertEqual(str(12345678.00005),
-                         jython and '12345678.0' or '12345678.0001')
+        self.assertEqual(str(12345678.00005), '12345678.0001')
+        self.assertEqual(str(12345678.00005), '12345678.0001')
         self.assertEqual(str(12345678.0005), '12345678.0005')
         self.assertEqual(str(math.pi**-100), '1.92758141606e-50')
         self.assertEqual(str(0.0), '0.0')
diff --git a/src/org/python/core/PyComplex.java b/src/org/python/core/PyComplex.java
--- a/src/org/python/core/PyComplex.java
+++ b/src/org/python/core/PyComplex.java
@@ -1,13 +1,10 @@
-/*
- * Copyright (c) Corporation for National Research Initiatives
- * Copyright (c) Jython Developers
- */
+// Copyright (c) Corporation for National Research Initiatives
+// Copyright (c) Jython Developers
 package org.python.core;
 
-import org.python.core.stringlib.Formatter;
-import org.python.core.stringlib.InternalFormatSpec;
-import org.python.core.stringlib.InternalFormatSpecParser;
-
+import org.python.core.stringlib.FloatFormatter;
+import org.python.core.stringlib.InternalFormat;
+import org.python.core.stringlib.InternalFormat.Spec;
 import org.python.expose.ExposedGet;
 import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
@@ -22,6 +19,11 @@
 
     public static final PyType TYPE = PyType.fromClass(PyComplex.class);
 
+    /** Format specification used by repr(). */
+    static final Spec SPEC_REPR = InternalFormat.fromText(" >r"); // but also minFracDigits=0
+    /** Format specification used by str() and none-format. (As CPython, but is that right?) */
+    static final Spec SPEC_STR = InternalFormat.fromText(" >.12g");
+
     static PyComplex J = new PyComplex(0, 1.);
 
     @ExposedGet(doc = BuiltinDocs.complex_real_doc)
@@ -46,7 +48,7 @@
 
     @ExposedNew
     public static PyObject complex_new(PyNewWrapper new_, boolean init, PyType subtype,
-                                       PyObject[] args, String[] keywords) {
+            PyObject[] args, String[] keywords) {
         ArgParser ap = new ArgParser("complex", args, keywords, "real", "imag");
         PyObject real = ap.getPyObject(0, Py.Zero);
         PyObject imag = ap.getPyObject(1, null);
@@ -54,14 +56,12 @@
         // Special-case for single argument that is already complex
         if (real.getType() == TYPE && new_.for_type == subtype && imag == null) {
             return real;
-        }
-        if (real instanceof PyString) {
+        } else if (real instanceof PyString) {
             if (imag != null) {
                 throw Py.TypeError("complex() can't take second arg if first is a string");
             }
             return real.__complex__();
-        }
-        if (imag != null && imag instanceof PyString) {
+        } else if (imag != null && imag instanceof PyString) {
             throw Py.TypeError("complex() second arg can't be a string");
         }
 
@@ -79,7 +79,7 @@
         PyComplex complexImag;
         PyFloat toFloat = null;
         if (real instanceof PyComplex) {
-            complexReal = (PyComplex) real;
+            complexReal = (PyComplex)real;
         } else {
             try {
                 toFloat = real.__float__();
@@ -96,7 +96,7 @@
         if (imag == null) {
             complexImag = new PyComplex(0.0);
         } else if (imag instanceof PyComplex) {
-            complexImag = (PyComplex) imag;
+            complexImag = (PyComplex)imag;
         } else {
             toFloat = null;
             try {
@@ -129,7 +129,7 @@
 
     public static String toString(double value) {
         if (value == Math.floor(value) && value <= Long.MAX_VALUE && value >= Long.MIN_VALUE) {
-            return Long.toString((long) value);
+            return Long.toString((long)value);
         } else {
             return Double.toString(value);
         }
@@ -137,20 +137,51 @@
 
     @Override
     public String toString() {
-        return complex_toString();
+        return __str__().toString();
     }
 
-    @ExposedMethod(names = {"__repr__", "__str__"}, doc = BuiltinDocs.complex___str___doc)
-    final String complex_toString() {
-        if (real == 0.) {
-            return toString(imag) + "j";
+    @Override
+    public PyString __str__() {
+        return complex___str__();
+    }
+
+    @ExposedMethod(doc = BuiltinDocs.complex___str___doc)
+    final PyString complex___str__() {
+        return Py.newString(formatComplex(SPEC_STR));
+    }
+
+    @Override
+    public PyString __repr__() {
+        return complex___repr__();
+    }
+
+    @ExposedMethod(doc = BuiltinDocs.complex___repr___doc)
+    final PyString complex___repr__() {
+        return Py.newString(formatComplex(SPEC_REPR));
+    }
+
+    /**
+     * Format this complex according to the specification passed in. Supports <code>__str__</code>
+     * and <code>__repr__</code>, and none-format.
+     * <p>
+     * In general, the output is surrounded in parentheses, like <code>"(12.34+24.68j)"</code>.
+     * However, if the real part is zero, only the imaginary part is printed, and without
+     * parentheses like <code>"24.68j"</code>. The number format specification passed in is used
+     * without padding to width, for the real and imaginary parts individually.
+     *
+     * @param spec parsed format specification string
+     * @return formatted value
+     */
+    private String formatComplex(Spec spec) {
+        FloatFormatter f = new FloatFormatter(spec, 2, 3); // Two elements + "(j)".length
+        // Even in r-format, complex strips *all* the trailing zeros.
+        f.setMinFracDigits(0);
+        if (real == 0.0) {
+            f.format(imag).append('j');
         } else {
-            if (imag >= 0) {
-                return String.format("(%s+%sj)", toString(real), toString(imag));
-            } else {
-                return String.format("(%s-%sj)", toString(real), toString(-imag));
-            }
+            f.append('(').format(real).format(imag, "+").append("j)").pad();
         }
+        return f.getResult();
     }
 
     @Override
@@ -164,7 +195,7 @@
             return new PyFloat(real).hashCode();
         } else {
             long v = Double.doubleToLongBits(real) ^ Double.doubleToLongBits(imag);
-            return (int) v ^ (int) (v >> 32);
+            return (int)v ^ (int)(v >> 32);
         }
     }
 
@@ -282,21 +313,18 @@
     }
 
     /**
-     * Coercion logic for complex. Implemented as a final method to avoid
-     * invocation of virtual methods from the exposed coerce.
+     * Coercion logic for complex. Implemented as a final method to avoid invocation of virtual
+     * methods from the exposed coerce.
      */
     final PyObject complex___coerce_ex__(PyObject other) {
         if (other instanceof PyComplex) {
             return other;
-        }
-        if (other instanceof PyFloat) {
-            return new PyComplex(((PyFloat) other).getValue(), 0);
-        }
-        if (other instanceof PyInteger) {
-            return new PyComplex(((PyInteger) other).getValue(), 0);
-        }
-        if (other instanceof PyLong) {
-            return new PyComplex(((PyLong) other).doubleValue(), 0);
+        } else if (other instanceof PyFloat) {
+            return new PyComplex(((PyFloat)other).getValue(), 0);
+        } else if (other instanceof PyInteger) {
+            return new PyComplex(((PyInteger)other).getValue(), 0);
+        } else if (other instanceof PyLong) {
+            return new PyComplex(((PyLong)other).doubleValue(), 0);
         }
         return Py.None;
     }
@@ -308,16 +336,13 @@
 
     private final PyComplex coerce(PyObject other) {
         if (other instanceof PyComplex) {
-            return (PyComplex) other;
-        }
-        if (other instanceof PyFloat) {
-            return new PyComplex(((PyFloat) other).getValue(), 0);
-        }
-        if (other instanceof PyInteger) {
-            return new PyComplex(((PyInteger) other).getValue(), 0);
-        }
-        if (other instanceof PyLong) {
-            return new PyComplex(((PyLong) other).doubleValue(), 0);
+            return (PyComplex)other;
+        } else if (other instanceof PyFloat) {
+            return new PyComplex(((PyFloat)other).getValue(), 0);
+        } else if (other instanceof PyInteger) {
+            return new PyComplex(((PyInteger)other).getValue(), 0);
+        } else if (other instanceof PyLong) {
+            return new PyComplex(((PyLong)other).doubleValue(), 0);
         }
         throw Py.TypeError("xxx");
     }
@@ -377,8 +402,8 @@
     }
 
     private final static PyObject _mul(PyComplex o1, PyComplex o2) {
-        return new PyComplex(o1.real * o2.real - o1.imag * o2.imag,
-                             o1.real * o2.imag + o1.imag * o2.real);
+        return new PyComplex(o1.real * o2.real - o1.imag * o2.imag, //
+                o1.real * o2.imag + o1.imag * o2.real);
     }
 
     @Override
@@ -417,14 +442,14 @@
             }
             double ratio = b.imag / b.real;
             double denom = b.real + b.imag * ratio;
-            return new PyComplex((a.real + a.imag * ratio) / denom,
-                                 (a.imag - a.real * ratio) / denom);
+            return new PyComplex((a.real + a.imag * ratio) / denom, //
+                    (a.imag - a.real * ratio) / denom);
         } else {
             /* divide tops and bottom by b.imag */
             double ratio = b.real / b.imag;
             double denom = b.real * ratio + b.imag;
-            return new PyComplex((a.real * ratio + a.imag) / denom,
-                                 (a.imag * ratio - a.real) / denom);
+            return new PyComplex((a.real * ratio + a.imag) / denom, //
+                    (a.imag * ratio - a.real) / denom);
         }
     }
 
@@ -437,8 +462,7 @@
     final PyObject complex___div__(PyObject right) {
         if (!canCoerce(right)) {
             return null;
-        }
-        if (Options.division_warning >= 2) {
+        } else if (Options.division_warning >= 2) {
             Py.warning(Py.DeprecationWarning, "classic complex division");
         }
         return _div(this, coerce(right));
@@ -453,8 +477,7 @@
     final PyObject complex___rdiv__(PyObject left) {
         if (!canCoerce(left)) {
             return null;
-        }
-        if (Options.division_warning >= 2) {
+        } else if (Options.division_warning >= 2) {
             Py.warning(Py.DeprecationWarning, "classic complex division");
         }
         return _div(coerce(left), this);
@@ -550,7 +573,7 @@
 
     private static PyObject _mod(PyComplex value, PyComplex right) {
         Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated");
-        PyComplex z = (PyComplex) _div(value, right);
+        PyComplex z = (PyComplex)_div(value, right);
 
         z.real = Math.floor(z.real);
         z.imag = 0.0;
@@ -586,7 +609,7 @@
 
     private static PyObject _divmod(PyComplex value, PyComplex right) {
         Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated");
-        PyComplex z = (PyComplex) _div(value, right);
+        PyComplex z = (PyComplex)_div(value, right);
 
         z.real = Math.floor(z.real);
         z.imag = 0.0;
@@ -636,12 +659,12 @@
         return complex___pow__(right, modulo);
     }
 
-    @ExposedMethod(type = MethodType.BINARY, defaults = "null", doc = BuiltinDocs.complex___pow___doc)
+    @ExposedMethod(type = MethodType.BINARY, defaults = "null",
+            doc = BuiltinDocs.complex___pow___doc)
     final PyObject complex___pow__(PyObject right, PyObject modulo) {
         if (modulo != null) {
             throw Py.ValueError("complex modulo");
-        }
-        if (!canCoerce(right)) {
+        } else if (!canCoerce(right)) {
             return null;
         }
         return _pow(this, coerce(right));
@@ -677,7 +700,7 @@
         }
 
         // Check for integral powers
-        int iexp = (int) yr;
+        int iexp = (int)yr;
         if (yi == 0 && yr == iexp && iexp >= -128 && iexp <= 128) {
             return ipow(value, iexp);
         }
@@ -764,6 +787,7 @@
         return new PyComplex(real, imag);
     }
 
+    @Override
     public PyComplex conjugate() {
         return complex_conjugate();
     }
@@ -775,7 +799,7 @@
 
     @ExposedMethod(doc = BuiltinDocs.complex___getnewargs___doc)
     final PyTuple complex___getnewargs__() {
-        return new PyTuple(new PyComplex(real, imag));
+        return new PyTuple(new PyFloat(real), new PyFloat(imag));
     }
 
     @Override
@@ -790,43 +814,44 @@
 
     @ExposedMethod(doc = BuiltinDocs.complex___format___doc)
     final PyObject complex___format__(PyObject formatSpec) {
-        return formatImpl(real, imag, formatSpec);
-    }
-
-    static PyObject formatImpl(double r, double i, PyObject formatSpec) {
         if (!(formatSpec instanceof PyString)) {
             throw Py.TypeError("__format__ requires str or unicode");
         }
 
-        PyString formatSpecStr = (PyString) formatSpec;
+        PyString formatSpecStr = (PyString)formatSpec;
         String result;
         try {
             String specString = formatSpecStr.getString();
-            InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
-            switch (spec.type) {
-                case '\0': /* No format code: like 'g', but with at least one decimal. */
-                case 'e':
-                case 'E':
-                case 'f':
-                case 'F':
-                case 'g':
-                case 'G':
-                case 'n':
-                case '%':
-                    result = Formatter.formatComplex(r, i, spec);
-                    break;
-                default:
-                    /* unknown */
-                    throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'complex'",
-                                            spec.type));
+            Spec spec = InternalFormat.fromText(specString);
+            if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) {
+                throw FloatFormatter.unknownFormat(spec.type, "complex");
+            } else if (spec.alternate) {
+                throw FloatFormatter.alternateFormNotAllowed("complex");
+            } else if (spec.fill == '0') {
+                throw FloatFormatter.zeroPaddingNotAllowed("complex");
+            } else if (spec.align == '=') {
+                throw FloatFormatter.alignmentNotAllowed('=', "complex");
+            } else {
+                if (spec.type == Spec.NONE) {
+                    // In none-format, we take the default type and precision from __str__.
+                    spec = spec.withDefaults(SPEC_STR);
+                    // And then we use the __str__ mechanism to get parentheses or real 0 elision.
+                    result = formatComplex(spec);
+                } else {
+                    // In any other format, the defaults those commonly used for numeric formats.
+                    spec = spec.withDefaults(Spec.NUMERIC);
+                    FloatFormatter f = new FloatFormatter(spec, 2, 1);// 2 floats + "j"
+                    // Convert as both parts per specification
+                    f.format(real).format(imag, "+").append('j').pad();
+                    result = f.getResult();
+                }
             }
         } catch (IllegalArgumentException e) {
-            throw Py.ValueError(e.getMessage());
+            throw Py.ValueError(e.getMessage());    // XXX Can this be reached?
         }
         return formatSpecStr.createInstance(result);
     }
 
-
     @Override
     public boolean isNumberType() {
         return true;
diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java
--- a/src/org/python/core/PyFloat.java
+++ b/src/org/python/core/PyFloat.java
@@ -1,23 +1,20 @@
-/*
- * Copyright (c) Corporation for National Research Initiatives
- * Copyright (c) Jython Developers
- */
+// Copyright (c) Corporation for National Research Initiatives
+// Copyright (c) Jython Developers
 package org.python.core;
 
-import org.python.core.stringlib.Formatter;
-import org.python.core.stringlib.InternalFormatSpec;
-import org.python.core.stringlib.InternalFormatSpecParser;
-import org.python.modules.math;
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 
+import org.python.core.stringlib.FloatFormatter;
+import org.python.core.stringlib.InternalFormat;
+import org.python.core.stringlib.InternalFormat.Spec;
 import org.python.expose.ExposedClassMethod;
 import org.python.expose.ExposedGet;
 import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
 import org.python.expose.ExposedType;
 import org.python.expose.MethodType;
+import org.python.modules.math;
 
 /**
  * A builtin python float.
@@ -27,9 +24,10 @@
 
     public static final PyType TYPE = PyType.fromClass(PyFloat.class);
 
-    /** Precisions used by repr() and str(), respectively. */
-    private static final int PREC_REPR = 17;
-    private static final int PREC_STR = 12;
+    /** Format specification used by repr(). */
+    static final Spec SPEC_REPR = InternalFormat.fromText(" >r");
+    /** Format specification used by str(). */
+    static final Spec SPEC_STR = Spec.NUMERIC;
 
     private final double value;
 
@@ -47,12 +45,12 @@
     }
 
     public PyFloat(float v) {
-        this((double) v);
+        this((double)v);
     }
 
     @ExposedNew
     public static PyObject float_new(PyNewWrapper new_, boolean init, PyType subtype,
-                                     PyObject[] args, String[] keywords) {
+            PyObject[] args, String[] keywords) {
         ArgParser ap = new ArgParser("float", args, keywords, new String[] {"x"}, 0);
         PyObject x = ap.getPyObject(0, null);
         if (x == null) {
@@ -69,9 +67,9 @@
                 if (e.match(Py.AttributeError)) {
                     // Translate AttributeError to TypeError
                     // XXX: We are using the same message as CPython, even if
-                    //      it is not strictly correct (instances of types
-                    //      that implement the __float__ method are also
-                    //      valid arguments)
+                    // it is not strictly correct (instances of types
+                    // that implement the __float__ method are also
+                    // valid arguments)
                     throw Py.TypeError("float() argument must be a string or a number");
                 }
                 throw e;
@@ -96,9 +94,9 @@
 
     @ExposedClassMethod(doc = BuiltinDocs.float_fromhex_doc)
     public static PyObject float_fromhex(PyType type, PyObject o) {
-        //XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes
-        //     non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which
-        //     causes extra complexity here.
+        // XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes
+        // non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which
+        // causes extra complexity here.
 
         String message = "invalid hexadecimal floating-point string";
         boolean negative = false;
@@ -108,19 +106,16 @@
 
         if (value.length() == 0) {
             throw Py.ValueError(message);
-        }
-        if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) {
+        } else if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) {
             return new PyFloat(Double.NaN);
-        }
-        if (value.equals("inf") ||value.equals("infinity") ||
-                value.equals("+inf") ||value.equals("+infinity")) {
+        } else if (value.equals("inf") || value.equals("infinity") || value.equals("+inf")
+                || value.equals("+infinity")) {
             return new PyFloat(Double.POSITIVE_INFINITY);
-        }
-        if (value.equals("-inf") || value.equals("-infinity")) {
+        } else if (value.equals("-inf") || value.equals("-infinity")) {
             return new PyFloat(Double.NEGATIVE_INFINITY);
         }
 
-        //Strip and record + or -
+        // Strip and record + or -
         if (value.charAt(0) == '-') {
             value = value.substring(1);
             negative = true;
@@ -131,17 +126,17 @@
             throw Py.ValueError(message);
         }
 
-        //Append 0x if not present.
+        // Append 0x if not present.
         if (!value.startsWith("0x") && !value.startsWith("0X")) {
             value = "0x" + value;
         }
 
-        //reattach - if needed.
+        // reattach - if needed.
         if (negative) {
             value = "-" + value;
         }
 
-        //Append p if not present.
+        // Append p if not present.
         if (value.indexOf('p') == -1) {
             value = value + "p0";
         }
@@ -159,7 +154,7 @@
 
     // @ExposedClassMethod(doc = BuiltinDocs.float_hex_doc)
     // public static PyObject float_hex(PyType type, double value) {
-    //     return new PyString(Double.toHexString(value));
+    // return new PyString(Double.toHexString(value));
     // }
 
     private String pyHexString(Double f) {
@@ -167,25 +162,31 @@
         // the most efficient, but we don't expect this to be a hot
         // spot in our code either
         String java_hex = Double.toHexString(getValue());
-        if (java_hex.equals("Infinity")) return "inf";
-        if (java_hex.equals("-Infinity")) return "-inf";
-        if (java_hex.equals("NaN")) return "nan";
-        if (java_hex.equals("0x0.0p0")) return "0x0.0p+0";
-        if (java_hex.equals("-0x0.0p0")) return "-0x0.0p+0";
+        if (java_hex.equals("Infinity")) {
+            return "inf";
+        } else if (java_hex.equals("-Infinity")) {
+            return "-inf";
+        } else if (java_hex.equals("NaN")) {
+            return "nan";
+        } else if (java_hex.equals("0x0.0p0")) {
+            return "0x0.0p+0";
+        } else if (java_hex.equals("-0x0.0p0")) {
+            return "-0x0.0p+0";
+        }
 
-	// replace hex rep of MpE to conform with Python such that
-        //   1. M is padded to 16 digits (ignoring a leading -)
-        //   2. Mp+E if E>=0
+        // replace hex rep of MpE to conform with Python such that
+        // 1. M is padded to 16 digits (ignoring a leading -)
+        // 2. Mp+E if E>=0
         // example: result of 42.0.hex() is translated from
-        //   0x1.5p5 to 0x1.5000000000000p+5
+        // 0x1.5p5 to 0x1.5000000000000p+5
         int len = java_hex.length();
         boolean start_exponent = false;
         StringBuilder py_hex = new StringBuilder(len + 1);
         int padding = f > 0 ? 17 : 18;
-        for (int i=0; i < len; i++) {
+        for (int i = 0; i < len; i++) {
             char c = java_hex.charAt(i);
             if (c == 'p') {
-                for (int pad=i; pad < padding; pad++) {
+                for (int pad = i; pad < padding; pad++) {
                     py_hex.append('0');
                 }
                 start_exponent = true;
@@ -194,9 +195,9 @@
                     py_hex.append('+');
                 }
                 start_exponent = false;
-	    }
+            }
             py_hex.append(c);
-	}
+        }
         return py_hex.toString();
     }
 
@@ -224,7 +225,7 @@
 
     @ExposedMethod(doc = BuiltinDocs.float___str___doc)
     final PyString float___str__() {
-        return Py.newString(formatDouble(PREC_STR));
+        return Py.newString(formatDouble(SPEC_STR));
     }
 
     @Override
@@ -234,34 +235,19 @@
 
     @ExposedMethod(doc = BuiltinDocs.float___repr___doc)
     final PyString float___repr__() {
-        return Py.newString(formatDouble(PREC_REPR));
+        return Py.newString(formatDouble(SPEC_REPR));
     }
 
-    private String formatDouble(int precision) {
-        if (Double.isNaN(value)) {
-            return "nan";
-        } else if (value == Double.NEGATIVE_INFINITY) {
-            return "-inf";
-        } else if (value == Double.POSITIVE_INFINITY) {
-            return "inf";
-        }
-
-        String result = String.format("%%.%dg", precision);
-        result = Py.newString(result).__mod__(this).toString();
-
-        int i = 0;
-        if (result.startsWith("-")) {
-            i++;
-        }
-        for (; i < result.length(); i++) {
-            if (!Character.isDigit(result.charAt(i))) {
-                break;
-            }
-        }
-        if (i == result.length()) {
-            result += ".0";
-        }
-        return result;
+    /**
+     * Format this float according to the specification passed in. Supports <code>__str__</code> and
+     * <code>__repr__</code>.
+     *
+     * @param spec parsed format specification string
+     * @return formatted value
+     */
+    private String formatDouble(Spec spec) {
+        FloatFormatter f = new FloatFormatter(spec);
+        return f.format(value).getResult();
     }
 
     @Override
@@ -274,23 +260,22 @@
         double value = getValue();
         if (Double.isInfinite(value)) {
             return value < 0 ? -271828 : 314159;
-        }
-        if (Double.isNaN(value)) {
+        } else if (Double.isNaN(value)) {
             return 0;
         }
-        
+
         double intPart = Math.floor(value);
         double fractPart = value - intPart;
 
         if (fractPart == 0) {
             if (intPart <= Integer.MAX_VALUE && intPart >= Integer.MIN_VALUE) {
-                return (int) value;
+                return (int)value;
             } else {
                 return __long__().hashCode();
             }
         } else {
             long v = Double.doubleToLongBits(getValue());
-            return (int) v ^ (int) (v >> 32);
+            return (int)v ^ (int)(v >> 32);
         }
     }
 
@@ -307,10 +292,9 @@
     @Override
     public Object __tojava__(Class<?> c) {
         if (c == Double.TYPE || c == Number.class || c == Double.class || c == Object.class
-            || c == Serializable.class) {
+                || c == Serializable.class) {
             return new Double(getValue());
-        }
-        if (c == Float.TYPE || c == Float.class) {
+        } else if (c == Float.TYPE || c == Float.class) {
             return new Float(getValue());
         }
         return super.__tojava__(c);
@@ -345,7 +329,7 @@
 
     @Override
     public PyObject __ge__(PyObject other) {
-        //NaN >= anything is always false.
+        // NaN >= anything is always false.
         if (Double.isNaN(getValue())) {
             return Py.False;
         }
@@ -354,7 +338,7 @@
 
     @Override
     public PyObject __lt__(PyObject other) {
-        //NaN < anything is always false.
+        // NaN < anything is always false.
         if (Double.isNaN(getValue())) {
             return Py.False;
         }
@@ -363,7 +347,7 @@
 
     @Override
     public PyObject __le__(PyObject other) {
-        //NaN >= anything is always false.
+        // NaN >= anything is always false.
         if (Double.isNaN(getValue())) {
             return Py.False;
         }
@@ -382,7 +366,7 @@
         double j;
 
         if (other instanceof PyFloat) {
-            j = ((PyFloat) other).getValue();
+            j = ((PyFloat)other).getValue();
         } else if (!isFinite()) {
             // we're infinity: our magnitude exceeds any finite
             // integer, so it doesn't matter which int we compare i
@@ -393,10 +377,10 @@
                 return -2;
             }
         } else if (other instanceof PyInteger) {
-            j = ((PyInteger) other).getValue();
+            j = ((PyInteger)other).getValue();
         } else if (other instanceof PyLong) {
             BigDecimal v = new BigDecimal(getValue());
-            BigDecimal w = new BigDecimal(((PyLong) other).getValue());
+            BigDecimal w = new BigDecimal(((PyLong)other).getValue());
             return v.compareTo(w);
         } else {
             return -2;
@@ -425,21 +409,18 @@
     }
 
     /**
-     * Coercion logic for float. Implemented as a final method to avoid
-     * invocation of virtual methods from the exposed coerce.
+     * Coercion logic for float. Implemented as a final method to avoid invocation of virtual
+     * methods from the exposed coerce.
      */
     final Object float___coerce_ex__(PyObject other) {
         if (other instanceof PyFloat) {
             return other;
+        } else if (other instanceof PyInteger) {
+            return new PyFloat((double)((PyInteger)other).getValue());
+        } else if (other instanceof PyLong) {
+            return new PyFloat(((PyLong)other).doubleValue());
         } else {
-            if (other instanceof PyInteger) {
-                return new PyFloat((double) ((PyInteger) other).getValue());
-            }
-            if (other instanceof PyLong) {
-                return new PyFloat(((PyLong) other).doubleValue());
-            } else {
-                return Py.None;
-            }
+            return Py.None;
         }
     }
 
@@ -449,11 +430,11 @@
 
     private static double coerce(PyObject other) {
         if (other instanceof PyFloat) {
-            return ((PyFloat) other).getValue();
+            return ((PyFloat)other).getValue();
         } else if (other instanceof PyInteger) {
-            return ((PyInteger) other).getValue();
+            return ((PyInteger)other).getValue();
         } else if (other instanceof PyLong) {
-            return ((PyLong) other).doubleValue();
+            return ((PyLong)other).doubleValue();
         } else {
             throw Py.TypeError("xxx");
         }
@@ -544,8 +525,7 @@
     final PyObject float___div__(PyObject right) {
         if (!canCoerce(right)) {
             return null;
-        }
-        if (Options.division_warning >= 2) {
+        } else if (Options.division_warning >= 2) {
             Py.warning(Py.DeprecationWarning, "classic float division");
         }
 
@@ -565,8 +545,7 @@
     final PyObject float___rdiv__(PyObject left) {
         if (!canCoerce(left)) {
             return null;
-        }
-        if (Options.division_warning >= 2) {
+        } else if (Options.division_warning >= 2) {
             Py.warning(Py.DeprecationWarning, "classic float division");
         }
 
@@ -667,7 +646,7 @@
             return null;
         }
         double rightv = coerce(right);
-        return new PyFloat(modulo(getValue(),rightv));
+        return new PyFloat(modulo(getValue(), rightv));
     }
 
     @Override
@@ -729,18 +708,16 @@
         return float___pow__(right, modulo);
     }
 
-    @ExposedMethod(type = MethodType.BINARY, defaults = "null",
-                   doc = BuiltinDocs.float___pow___doc)
+    @ExposedMethod(type = MethodType.BINARY, defaults = "null", //
+            doc = BuiltinDocs.float___pow___doc)
     final PyObject float___pow__(PyObject right, PyObject modulo) {
         if (!canCoerce(right)) {
             return null;
-        }
-
-        if (modulo != null) {
+        } else if (modulo != null) {
             throw Py.TypeError("pow() 3rd argument not allowed unless all arguments are integers");
         }
 
-        return _pow( getValue(), coerce(right), modulo);
+        return _pow(getValue(), coerce(right), modulo);
     }
 
     @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.float___rpow___doc)
@@ -769,8 +746,7 @@
         if (value == 0.0) {
             if (iw < 0.0) {
                 throw Py.ZeroDivisionError("0.0 cannot be raised to a negative power");
-            }
-            if (Double.isNaN(iw)) {
+            } else if (Double.isNaN(iw)) {
                 return new PyFloat(Double.NaN);
             }
             return new PyFloat(0);
@@ -835,12 +811,18 @@
         return float___int__();
     }
 
+    /** Smallest value that cannot be represented as an int */
+    private static double INT_LONG_BOUNDARY = -(double)Integer.MIN_VALUE; // 2^31
+
     @ExposedMethod(doc = BuiltinDocs.float___int___doc)
     final PyObject float___int__() {
-        if (getValue() <= Integer.MAX_VALUE && getValue() >= Integer.MIN_VALUE) {
-            return new PyInteger((int) getValue());
+        double v = getValue();
+        if (v < INT_LONG_BOUNDARY && v > -(INT_LONG_BOUNDARY + 1.0)) {
+            // v will fit into an int (when rounded towards zero).
+            return new PyInteger((int)v);
+        } else {
+            return __long__();
         }
-        return __long__();
     }
 
     @Override
@@ -902,7 +884,7 @@
     @ExposedMethod(doc = BuiltinDocs.float_is_integer_doc)
     final boolean float_is_integer() {
         if (Double.isInfinite(value)) {
-            return false; 
+            return false;
         }
         return Math.floor(value) == value;
     }
@@ -929,41 +911,30 @@
 
     @ExposedMethod(doc = BuiltinDocs.float___format___doc)
     final PyObject float___format__(PyObject formatSpec) {
-        return formatImpl(getValue(), formatSpec);
-    }
-
-    static PyObject formatImpl(double d, PyObject formatSpec) {
         if (!(formatSpec instanceof PyString)) {
             throw Py.TypeError("__format__ requires str or unicode");
         }
 
-        PyString formatSpecStr = (PyString) formatSpec;
+        PyString formatSpecStr = (PyString)formatSpec;
         String result;
         try {
             String specString = formatSpecStr.getString();
-            InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
-            if (spec.type == '\0'){
-                return (Py.newFloat(d)).__str__();
-            }
-            switch (spec.type) {
-                case '\0': /* No format code: like 'g', but with at least one decimal. */
-                case 'e':
-                case 'E':
-                case 'f':
-                case 'F':
-                case 'g':
-                case 'G':
-                case 'n':
-                case '%':
-                    result = Formatter.formatFloat(d, spec);
-                    break;
-                default:
-                    /* unknown */
-                    throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'",
-                                            spec.type));
+            Spec spec = InternalFormat.fromText(specString);
+            if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) {
+                throw FloatFormatter.unknownFormat(spec.type, "float");
+            } else if (spec.alternate) {
+                throw FloatFormatter.alternateFormNotAllowed("float");
+            } else {
+                // spec may be incomplete. The defaults are those commonly used for numeric formats.
+                spec = spec.withDefaults(Spec.NUMERIC);
+                // Get a formatter for the spec.
+                FloatFormatter f = new FloatFormatter(spec);
+                // Convert as per specification.
+                f.format(value).pad();
+                result = f.getResult();
             }
         } catch (IllegalArgumentException e) {
-            throw Py.ValueError(e.getMessage());
+            throw Py.ValueError(e.getMessage());    // XXX Can this be reached?
         }
         return formatSpecStr.createInstance(result);
     }
@@ -979,14 +950,15 @@
         PyTuple frexp = math.frexp(value);
         double float_part = ((Double)frexp.get(0)).doubleValue();
         int exponent = ((Integer)frexp.get(1)).intValue();
-        for (int i=0; i<300 && float_part != Math.floor(float_part); i++) {
+        for (int i = 0; i < 300 && float_part != Math.floor(float_part); i++) {
             float_part *= 2.0;
             exponent--;
         }
-        /* self == float_part * 2**exponent exactly and float_part is integral.
-           If FLT_RADIX != 2, the 300 steps may leave a tiny fractional part
-           to be truncated by PyLong_FromDouble(). */
-        
+        /*
+         * self == float_part * 2**exponent exactly and float_part is integral. If FLT_RADIX != 2,
+         * the 300 steps may leave a tiny fractional part to be truncated by PyLong_FromDouble().
+         */
+
         PyLong numerator = new PyLong(float_part);
         PyLong denominator = new PyLong(1);
         PyLong py_exponent = new PyLong(Math.abs(exponent));
@@ -1013,9 +985,7 @@
     // but this is what Python demands
     public enum Format {
 
-        UNKNOWN("unknown"),
-        BE("IEEE, big-endian"),
-        LE("IEEE, little-endian");
+        UNKNOWN("unknown"), BE("IEEE, big-endian"), LE("IEEE, little-endian");
 
         private final String format;
 
@@ -1027,6 +997,7 @@
             return format;
         }
     }
+
     // subset of IEEE-754, the JVM is big-endian
     public static volatile Format double_format = Format.BE;
     public static volatile Format float_format = Format.BE;
@@ -1050,14 +1021,14 @@
         }
         if (Format.LE.format().equals(format)) {
             throw Py.ValueError(String.format("can only set %s format to 'unknown' or the "
-                                              + "detected platform value", typestr));
+                    + "detected platform value", typestr));
         } else if (Format.BE.format().equals(format)) {
             new_format = Format.BE;
         } else if (Format.UNKNOWN.format().equals(format)) {
             new_format = Format.UNKNOWN;
         } else {
-            throw Py.ValueError("__setformat__() argument 2 must be 'unknown', " +
-                                "'IEEE, little-endian' or 'IEEE, big-endian'");
+            throw Py.ValueError("__setformat__() argument 2 must be 'unknown', "
+                    + "'IEEE, little-endian' or 'IEEE, big-endian'");
         }
         if (new_format != null) {
             if ("double".equals(typestr)) {
diff --git a/src/org/python/core/PyInteger.java b/src/org/python/core/PyInteger.java
--- a/src/org/python/core/PyInteger.java
+++ b/src/org/python/core/PyInteger.java
@@ -1,7 +1,5 @@
-/*
- * Copyright (c) Corporation for National Research Initiatives
- * Copyright (c) Jython Developers
- */
+// Copyright (c) Corporation for National Research Initiatives
+// Copyright (c) Jython Developers
 package org.python.core;
 
 import java.io.Serializable;
@@ -112,9 +110,9 @@
      * Convert all sorts of object types to either <code>PyInteger</code> or <code>PyLong</code>,
      * using their {@link PyObject#__int__()} method, whether exposed or not, or if that raises an
      * exception (as the base <code>PyObject</code> one does), using any <code>__trunc__()</code>
-     * the type may have exposed. If all this fails, this method raises an exception. Equivalent to CPython
-     * <code>PyNumber_Int()</code>.
-     * 
+     * the type may have exposed. If all this fails, this method raises an exception. Equivalent to
+     * CPython <code>PyNumber_Int()</code>.
+     *
      * @param x to convert to an int
      * @return int or long result.
      * @throws PyException (TypeError) if no method of conversion can be found
@@ -151,7 +149,7 @@
     /**
      * Helper called on whatever exposed method <code>__trunc__</code> returned: it may be
      * <code>int</code>, <code>long</code> or something with an exposed <code>__int__</code>.
-     * 
+     *
      * @return convert to an int.
      * @throws TypeError and AttributeError.
      */
@@ -160,7 +158,7 @@
             PyObject i = integral.invoke("__int__");
             if (!(i instanceof PyInteger) && !(i instanceof PyLong)) {
                 throw Py.TypeError(String.format("__trunc__ returned non-Integral (type %.200s)",
-                                                 integral.getType().fastGetName()));
+                        integral.getType().fastGetName()));
             }
             return i;
         }
@@ -225,7 +223,7 @@
     @Override
     public Object __tojava__(Class<?> c) {
         if (c == Integer.TYPE || c == Number.class || c == Object.class || c == Integer.class
-            || c == Serializable.class) {
+                || c == Serializable.class) {
             return new Integer(getValue());
         }
 
@@ -233,10 +231,10 @@
             return new Boolean(getValue() != 0);
         }
         if (c == Byte.TYPE || c == Byte.class) {
-            return new Byte((byte) getValue());
+            return new Byte((byte)getValue());
         }
         if (c == Short.TYPE || c == Short.class) {
-            return new Short((short) getValue());
+            return new Short((short)getValue());
         }
 
         if (c == Long.TYPE || c == Long.class) {
@@ -276,8 +274,8 @@
     }
 
     /**
-     * Coercion logic for int. Implemented as a final method to avoid
-     * invocation of virtual methods from the exposed coerced.
+     * Coercion logic for int. Implemented as a final method to avoid invocation of virtual methods
+     * from the exposed coerced.
      */
     final Object int___coerce_ex__(PyObject other) {
         return other instanceof PyInteger ? other : Py.None;
@@ -289,7 +287,7 @@
 
     private static final int coerce(PyObject other) {
         if (other instanceof PyInteger) {
-            return ((PyInteger) other).getValue();
+            return ((PyInteger)other).getValue();
         }
         throw Py.TypeError("xxx");
     }
@@ -311,7 +309,7 @@
         if ((x ^ a) >= 0 || (x ^ b) >= 0) {
             return Py.newInteger(x);
         }
-        return new PyLong((long) a + (long) b);
+        return new PyLong((long)a + (long)b);
     }
 
     @Override
@@ -329,7 +327,7 @@
         if ((x ^ a) >= 0 || (x ^ ~b) >= 0) {
             return Py.newInteger(x);
         }
-        return new PyLong((long) a - (long) b);
+        return new PyLong((long)a - (long)b);
     }
 
     @Override
@@ -374,7 +372,7 @@
         x *= rightv;
 
         if (x <= Integer.MAX_VALUE && x >= Integer.MIN_VALUE) {
-            return Py.newInteger((int) x);
+            return Py.newInteger((int)x);
         }
         return __long__().__mul__(right);
     }
@@ -400,12 +398,12 @@
 
         // If the signs of x and y differ, and the remainder is non-0, C89 doesn't define
         // whether xdivy is now the floor or the ceiling of the infinitely precise
-        // quotient.  We want the floor, and we have it iff the remainder's sign matches
+        // quotient. We want the floor, and we have it iff the remainder's sign matches
         // y's.
         if (xmody != 0 && ((y < 0 && xmody > 0) || (y > 0 && xmody < 0))) {
             xmody += y;
             --xdivy;
-            //assert(xmody && ((y ^ xmody) >= 0));
+            // assert(xmody && ((y ^ xmody) >= 0));
         }
         return xdivy;
     }
@@ -568,8 +566,8 @@
         return int___pow__(right, modulo);
     }
 
-    @ExposedMethod(type = MethodType.BINARY, defaults = {"null"},
-                   doc = BuiltinDocs.int___pow___doc)
+    @ExposedMethod(type = MethodType.BINARY, defaults = {"null"}, //
+            doc = BuiltinDocs.int___pow___doc)
     final PyObject int___pow__(PyObject right, PyObject modulo) {
         if (!canCoerce(right)) {
             return null;
@@ -599,8 +597,8 @@
         return __rpow__(left, null);
     }
 
-    private static PyObject _pow(int value, int pow, PyObject modulo, PyObject left,
-                                 PyObject right) {
+    private static PyObject _pow(int value, int pow, PyObject modulo,//
+            PyObject left, PyObject right) {
         int mod = 0;
         long tmp = value;
         boolean neg = false;
@@ -663,7 +661,6 @@
         return Py.newInteger(result);
     }
 
-
     @Override
     public PyObject __lshift__(PyObject right) {
         return int___lshift__(right);
@@ -673,7 +670,7 @@
     final PyObject int___lshift__(PyObject right) {
         int rightv;
         if (right instanceof PyInteger) {
-            rightv = ((PyInteger) right).getValue();
+            rightv = ((PyInteger)right).getValue();
         } else if (right instanceof PyLong) {
             return int___long__().__lshift__(right);
         } else {
@@ -696,7 +693,7 @@
     final PyObject int___rlshift__(PyObject left) {
         int leftv;
         if (left instanceof PyInteger) {
-            leftv = ((PyInteger) left).getValue();
+            leftv = ((PyInteger)left).getValue();
         } else if (left instanceof PyLong) {
             return left.__rlshift__(int___long__());
         } else {
@@ -724,7 +721,7 @@
     final PyObject int___rshift__(PyObject right) {
         int rightv;
         if (right instanceof PyInteger) {
-            rightv = ((PyInteger) right).getValue();
+            rightv = ((PyInteger)right).getValue();
         } else if (right instanceof PyLong) {
             return int___long__().__rshift__(right);
         } else {
@@ -746,7 +743,7 @@
     final PyObject int___rrshift__(PyObject left) {
         int leftv;
         if (left instanceof PyInteger) {
-            leftv = ((PyInteger) left).getValue();
+            leftv = ((PyInteger)left).getValue();
         } else if (left instanceof PyLong) {
             return left.__rshift__(int___long__());
         } else {
@@ -773,7 +770,7 @@
     final PyObject int___and__(PyObject right) {
         int rightv;
         if (right instanceof PyInteger) {
-            rightv = ((PyInteger) right).getValue();
+            rightv = ((PyInteger)right).getValue();
         } else if (right instanceof PyLong) {
             return int___long__().__and__(right);
         } else {
@@ -797,7 +794,7 @@
     final PyObject int___xor__(PyObject right) {
         int rightv;
         if (right instanceof PyInteger) {
-            rightv = ((PyInteger) right).getValue();
+            rightv = ((PyInteger)right).getValue();
         } else if (right instanceof PyLong) {
             return int___long__().__xor__(right);
         } else {
@@ -811,7 +808,7 @@
     final PyObject int___rxor__(PyObject left) {
         int leftv;
         if (left instanceof PyInteger) {
-            leftv = ((PyInteger) left).getValue();
+            leftv = ((PyInteger)left).getValue();
         } else if (left instanceof PyLong) {
             return left.__rxor__(int___long__());
         } else {
@@ -830,7 +827,7 @@
     final PyObject int___or__(PyObject right) {
         int rightv;
         if (right instanceof PyInteger) {
-            rightv = ((PyInteger) right).getValue();
+            rightv = ((PyInteger)right).getValue();
         } else if (right instanceof PyLong) {
             return int___long__().__or__(right);
         } else {
@@ -921,7 +918,7 @@
 
     @ExposedMethod(doc = BuiltinDocs.int___float___doc)
     final PyFloat int___float__() {
-        return new PyFloat((double) getValue());
+        return new PyFloat((double)getValue());
     }
 
     @Override
@@ -979,7 +976,7 @@
 
     @ExposedMethod(doc = BuiltinDocs.int___getnewargs___doc)
     final PyTuple int___getnewargs__() {
-        return new PyTuple(new PyObject[]{new PyInteger(this.getValue())});
+        return new PyTuple(new PyObject[] {new PyInteger(this.getValue())});
     }
 
     @Override
@@ -1026,7 +1023,7 @@
             throw Py.TypeError("__format__ requires str or unicode");
         }
 
-        PyString formatSpecStr = (PyString) formatSpec;
+        PyString formatSpecStr = (PyString)formatSpec;
         String result;
         try {
             String specString = formatSpecStr.getString();
@@ -1052,10 +1049,10 @@
 
         int sign;
         if (value instanceof Integer) {
-            int intValue = (Integer) value;
+            int intValue = (Integer)value;
             sign = intValue < 0 ? -1 : intValue == 0 ? 0 : 1;
         } else {
-            sign = ((BigInteger) value).signum();
+            sign = ((BigInteger)value).signum();
         }
 
         String strValue;
@@ -1065,20 +1062,20 @@
         if (spec.type == 'c') {
             if (spec.sign != '\0') {
                 throw new IllegalArgumentException("Sign not allowed with integer format "
-                                                   + "specifier 'c'");
+                        + "specifier 'c'");
             }
             if (value instanceof Integer) {
-                int intValue = (Integer) value;
+                int intValue = (Integer)value;
                 if (intValue > 0xffff) {
                     throw new IllegalArgumentException("%c arg not in range(0x10000)");
                 }
-                strValue = Character.toString((char) intValue);
+                strValue = Character.toString((char)intValue);
             } else {
-                BigInteger bigInt = (BigInteger) value;
+                BigInteger bigInt = (BigInteger)value;
                 if (bigInt.intValue() > 0xffff || bigInt.bitCount() > 16) {
                     throw new IllegalArgumentException("%c arg not in range(0x10000)");
                 }
-                strValue = Character.toString((char) bigInt.intValue());
+                strValue = Character.toString((char)bigInt.intValue());
             }
         } else {
             int radix = 10;
@@ -1099,21 +1096,21 @@
             } else if (value instanceof BigInteger) {
                 switch (radix) {
                     case 2:
-                        strValue = toBinString((BigInteger) value);
+                        strValue = toBinString((BigInteger)value);
                         break;
                     case 8:
-                        strValue = toOctString((BigInteger) value);
+                        strValue = toOctString((BigInteger)value);
                         break;
                     case 16:
-                        strValue = toHexString((BigInteger) value);
+                        strValue = toHexString((BigInteger)value);
                         break;
                     default:
                         // General case (v.slow in known implementations up to Java 7).
-                        strValue = ((BigInteger) value).toString(radix);
+                        strValue = ((BigInteger)value).toString(radix);
                         break;
                 }
             } else {
-                strValue = Integer.toString((Integer) value, radix);
+                strValue = Integer.toString((Integer)value, radix);
             }
 
             if (spec.alternate) {
diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java
--- a/src/org/python/core/PyString.java
+++ b/src/org/python/core/PyString.java
@@ -3766,6 +3766,16 @@
         }
     }
 
+    /**
+     * Implements PEP-3101 {}-formatting methods <code>str.format()</code> and
+     * <code>unicode.format()</code>.
+     *
+     * @param value the format string
+     * @param args to be interpolated into the string
+     * @param keywords for the trailing args
+     * @param enclosingIterator when used nested
+     * @return the formatted string based on the arguments
+     */
     protected String buildFormattedString(String value, PyObject[] args, String[] keywords,
             MarkupIterator enclosingIterator) {
         StringBuilder result = new StringBuilder();
@@ -3775,12 +3785,20 @@
             if (chunk == null) {
                 break;
             }
+            // A Chunk encapsulates a literal part ...
             result.append(chunk.literalText);
+            // ... and the parsed form of the replacement field that followed it (if any)
             if (chunk.fieldName.length() > 0) {
+                // The grammar of the replacement field is:
+                // "{" [field_name] ["!" conversion] [":" format_spec] "}"
+
+                // Get the object referred to by the field name (which may be omitted).
                 PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords);
                 if (fieldObj == null) {
                     continue;
                 }
+
+                // The conversion specifier is s = __str__ or r = __repr__.
                 if ("r".equals(chunk.conversion)) {
                     fieldObj = fieldObj.__repr__();
                 } else if ("s".equals(chunk.conversion)) {
@@ -3788,12 +3806,15 @@
                 } else if (chunk.conversion != null) {
                     throw Py.ValueError("Unknown conversion specifier " + chunk.conversion);
                 }
+
+                // The format_spec may be simple, or contained nested replacement fields.
                 String formatSpec = chunk.formatSpec;
                 if (chunk.formatSpecNeedsExpanding) {
                     if (enclosingIterator != null) {
                         // PEP 3101 says only 2 levels
                         throw Py.ValueError("Max string recursion exceeded");
                     }
+                    // Recursively interpolate further args into chunk.formatSpec
                     formatSpec = buildFormattedString(formatSpec, args, keywords, it);
                 }
                 renderField(fieldObj, formatSpec, result);
@@ -3802,6 +3823,15 @@
         return result.toString();
     }
 
+    /**
+     * Return the object referenced by a given field name, interpreted in the context of the given
+     * argument list, containing positional and keyword arguments.
+     *
+     * @param fieldName to interpret.
+     * @param args argument list (positional then keyword arguments).
+     * @param keywords naming the keyword arguments.
+     * @return the object designated or <code>null</code>.
+     */
     private PyObject getFieldObject(String fieldName, PyObject[] args, String[] keywords) {
         FieldNameIterator iterator = new FieldNameIterator(fieldName);
         Object head = iterator.head();
@@ -3814,6 +3844,7 @@
                 throw Py.IndexError("tuple index out of range");
             }
             obj = args[index];
+
         } else {
             for (int i = 0; i < keywords.length; i++) {
                 if (keywords[i].equals(head)) {
@@ -3825,6 +3856,7 @@
                 throw Py.KeyError((String)head);
             }
         }
+
         if (obj != null) {
             while (true) {
                 FieldNameIterator.Chunk chunk = iterator.nextChunk();
@@ -3844,9 +3876,18 @@
                 }
             }
         }
+
         return obj;
     }
 
+    /**
+     * Append to a formatting result, the presentation of one object, according to a given format
+     * specification and the object's <code>__format__</code> method.
+     *
+     * @param fieldObj to format.
+     * @param formatSpec specification to apply.
+     * @param result to which the result will be appended.
+     */
     private void renderField(PyObject fieldObj, String formatSpec, StringBuilder result) {
         PyString formatSpecStr = formatSpec == null ? Py.EmptyString : new PyString(formatSpec);
         result.append(fieldObj.__format__(formatSpecStr).asString());
@@ -3876,10 +3917,12 @@
     }
 
     /**
-     * Internal implementation of str.__format__()
+     * Format the given text according to a parsed PEP 3101 formatting specification, as during
+     * <code>text.__format__(format_spec)</code> or <code>"{:s}".format(text)</code> where
+     * <code>text</code> is a Python string.
      *
-     * @param text the text to format
-     * @param spec the PEP 3101 formatting specification
+     * @param text to format
+     * @param spec the parsed PEP 3101 formatting specification
      * @return the result of the formatting
      */
     public static String formatString(String text, InternalFormatSpec spec) {
@@ -3954,6 +3997,9 @@
 }
 
 
+/**
+ * Interpreter for %-format strings. (Note visible across the core package.)
+ */
 final class StringFormatter {
 
     int index;
@@ -3985,6 +4031,12 @@
         this(format, false);
     }
 
+    /**
+     * Initialise the interpreter with the given format string, ready for {@link #format(PyObject)}.
+     *
+     * @param format string to interpret
+     * @param unicodeCoercion to indicate a <code>PyUnicode</code> result is expected
+     */
     public StringFormatter(String format, boolean unicodeCoercion) {
         index = 0;
         this.format = format;
@@ -3995,16 +4047,11 @@
     PyObject getarg() {
         PyObject ret = null;
         switch (argIndex) {
-        // special index indicating a mapping
-            case -3:
+            case -3: // special index indicating a mapping
                 return args;
-                // special index indicating a single item that has already been
-                // used
-            case -2:
+            case -2: // special index indicating a single item that has already been used
                 break;
-            // special index indicating a single item that has not yet been
-            // used
-            case -1:
+            case -1: // special index indicating a single item that has not yet been used
                 argIndex = -2;
                 return args;
             default:
diff --git a/src/org/python/core/__builtin__.java b/src/org/python/core/__builtin__.java
--- a/src/org/python/core/__builtin__.java
+++ b/src/org/python/core/__builtin__.java
@@ -15,6 +15,7 @@
 import java.util.Map;
 
 import org.python.antlr.base.mod;
+import org.python.core.util.ExtraMath;
 import org.python.core.util.RelativeFile;
 import org.python.core.util.StringUtil;
 import org.python.modules._functools._functools;
@@ -1601,20 +1602,14 @@
         ArgParser ap = new ArgParser("round", args, kwds, new String[] {"number", "ndigits"}, 0);
         PyObject number = ap.getPyObject(0);
         int ndigits = ap.getIndex(1, 0);
-        return round(number.asDouble(), ndigits);
-    }
-
-    private static PyFloat round(double f, int digits) {
-        boolean neg = f < 0;
-        double multiple = Math.pow(10., digits);
-        if (neg) {
-            f = -f;
+        double x = number.asDouble();
+        double r = ExtraMath.round(x, ndigits);
+        if (Double.isInfinite(r) && !Double.isInfinite(x)) {
+            // Rounding caused magnitude to increase beyond representable range
+            throw Py.OverflowError("rounded value too large to represent");
+        } else {
+            return new PyFloat(r);
         }
-        double tmp = Math.floor(f * multiple + 0.5);
-        if (neg) {
-            tmp = -tmp;
-        }
-        return new PyFloat(tmp / multiple);
     }
 }
 
diff --git a/src/org/python/core/stringlib/FloatFormatter.java b/src/org/python/core/stringlib/FloatFormatter.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/stringlib/FloatFormatter.java
@@ -0,0 +1,880 @@
+// Copyright (c) Jython Developers
+package org.python.core.stringlib;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+import org.python.core.stringlib.InternalFormat.Spec;
+
+/**
+ * A class that provides the implementation of floating-point formatting. In a limited way, it acts
+ * like a StringBuilder to which text and one or more numbers may be appended, formatted according
+ * to the format specifier supplied at construction. These are ephemeral objects that are not, on
+ * their own, thread safe.
+ */
+public class FloatFormatter extends InternalFormat.Formatter {
+
+    /** The rounding mode dominant in the formatter. */
+    static final RoundingMode ROUND_PY = RoundingMode.HALF_UP; // Believed to be HALF_EVEN in Py3k
+
+    /** If it contains no decimal point, this length is zero, and 1 otherwise. */
+    private int lenPoint;
+    /** The length of the fractional part, right of the decimal point. */
+    private int lenFraction;
+    /** The length of the exponent marker ("e"), "inf" or "nan", or zero if there isn't one. */
+    private int lenMarker;
+    /** The length of the exponent sign and digits or zero if there isn't one. */
+    private int lenExponent;
+    /** if >=0, minimum digits to follow decimal point (where consulted) */
+    private int minFracDigits;
+
+    /**
+     * Construct the formatter from a specification. A reference is held to this specification, but
+     * it will not be modified by the actions of this class.
+     *
+     * @param spec parsed conversion specification
+     */
+    public FloatFormatter(Spec spec) {
+        // Space for result is based on padded width, or precision, whole part & furniture.
+        this(spec, 1, 0);
+    }
+
+    /**
+     * Construct the formatter from a specification and an explicit initial buffer capacity. A
+     * reference is held to this specification, but it will not be modified by the actions of this
+     * class.
+     *
+     * @param spec parsed conversion specification
+     * @param width expected for the formatted result
+     */
+    public FloatFormatter(Spec spec, int width) {
+        super(spec, width);
+        if (spec.alternate) {
+            // Alternate form means do not trim the zero fractional digits.
+            minFracDigits = -1;
+        } else if (spec.type == 'r' || spec.type == Spec.NONE) {
+            // These formats by default show at least one fractional digit.
+            minFracDigits = 1;
+        } else {
+            /*
+             * Every other format (if it does not ignore the setting) will by default trim off all
+             * the trailing zero fractional digits.
+             */
+            minFracDigits = 0;
+        }
+    }
+
+    /**
+     * Construct the formatter from a specification and two extra hints about the initial buffer
+     * capacity. A reference is held to this specification, but it will not be modified by the
+     * actions of this class.
+     *
+     * @param spec parsed conversion specification
+     * @param count of elements likely to be formatted
+     * @param margin for elements formatted only once
+     */
+    public FloatFormatter(Spec spec, int count, int margin) {
+        /*
+         * Rule of thumb used here: in e format w = (p-1) + len("+1.e+300") = p+7; in f format w = p
+         * + len("1,000,000.") = p+10. If we're wrong, the result will have to grow. No big deal.
+         */
+        this(spec, Math.max(spec.width + 1, count * (spec.precision + 10) + margin));
+    }
+
+    /**
+     * Override the default truncation behaviour for the specification originally supplied. Some
+     * formats remove trailing zero digits, trimming to zero or one. Set member
+     * <code>minFracDigits</code>, to modify this behaviour.
+     *
+     * @param minFracDigits if <0 prevent truncation; if >=0 the minimum number of fractional
+     *            digits; when this is zero, and all fractional digits are zero, the decimal point
+     *            will also be removed.
+     */
+    public void setMinFracDigits(int minFracDigits) {
+        this.minFracDigits = minFracDigits;
+    }
+
+    @Override
+    protected void reset() {
+        // Clear the variables describing the latest number in result.
+        super.reset();
+        lenPoint = lenFraction = lenMarker = lenExponent = 0;
+    }
+
+    @Override
+    protected int[] sectionLengths() {
+        return new int[] {lenSign, lenWhole, lenPoint, lenFraction, lenMarker, lenExponent};
+    }
+
+    /*
+     * Re-implement the text appends so they return the right type.
+     */
+    @Override
+    public FloatFormatter append(char c) {
+        super.append(c);
+        return this;
+    }
+
+    @Override
+    public FloatFormatter append(CharSequence csq) {
+        super.append(csq);
+        return this;
+    }
+
+    @Override
+    public FloatFormatter append(CharSequence csq, int start, int end) //
+            throws IndexOutOfBoundsException {
+        super.append(csq, start, end);
+        return this;
+    }
+
+    /**
+     * Format a floating-point number according to the specification represented by this
+     * <code>FloatFormatter</code>.
+     *
+     * @param value to convert
+     * @return this object
+     */
+    public FloatFormatter format(double value) {
+        return format(value, null);
+    }
+
+    /**
+     * Format a floating-point number according to the specification represented by this
+     * <code>FloatFormatter</code>. The conversion type, precision, and flags for grouping or
+     * percentage are dealt with here. At the point this is used, we know the {@link #spec} is one
+     * of the floating-point types. This entry point allows explicit control of the prefix of
+     * positive numbers, overriding defaults for the format type.
+     *
+     * @param value to convert
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     * @return this object
+     */
+    @SuppressWarnings("fallthrough")
+    public FloatFormatter format(double value, String positivePrefix) {
+
+        // Puts all instance variables back to their starting defaults, and start = result.length().
+        setStart();
+
+        // Precision defaults to 6 (or 12 for none-format)
+        int precision = spec.getPrecision(Spec.specified(spec.type) ? 6 : 12);
+
+        /*
+         * By default, the prefix of a positive number is "", but the format specifier may override
+         * it, and the built-in type complex needs to override the format.
+         */
+        if (positivePrefix == null && Spec.specified(spec.sign) && spec.sign != '-') {
+            positivePrefix = Character.toString(spec.sign);
+        }
+
+        // Different process for each format type, ignoring case for now.
+        switch (Character.toLowerCase(spec.type)) {
+            case 'e':
+                // Exponential case: 1.23e-45
+                format_e(value, positivePrefix, precision);
+                break;
+
+            case 'f':
+                // Fixed case: 123.45
+                format_f(value, positivePrefix, precision);
+                break;
+
+            case 'n':
+                // Locale-sensitive version of g-format should be here. (Désolé de vous decevoir.)
+                // XXX Set a variable here to signal localisation in/after groupDigits?
+            case 'g':
+                // General format: fixed or exponential according to value.
+                format_g(value, positivePrefix, precision, 0);
+                break;
+
+            case Spec.NONE:
+                // None format like g-format but goes exponential at precision-1
+                format_g(value, positivePrefix, precision, -1);
+                break;
+
+            case 'r':
+                // For float.__repr__, very special case, breaks all the rules.
+                format_r(value, positivePrefix);
+                break;
+
+            case '%':
+                // Multiplies by 100 and displays in f-format, followed by a percent sign.
+                format_f(100. * value, positivePrefix, precision);
+                result.append('%');
+                break;
+
+            default:
+                // Should never get here, since this was checked in PyFloat.
+                throw unknownFormat(spec.type, "float");
+        }
+
+        // If the format type is an upper-case letter, convert the result to upper case.
+        if (Character.isUpperCase(spec.type)) {
+            uppercase();
+        }
+
+        // If required to, group the whole-part digits.
+        if (spec.grouping) {
+            groupDigits(3, ',');
+        }
+
+        return this;
+    }
+
+    /**
+     * Convert just the letters in the representation of the current number (in {@link #result}) to
+     * upper case. (That's the exponent marker or the "inf" or "nan".)
+     */
+    @Override
+    protected void uppercase() {
+        int letters = indexOfMarker();
+        int end = letters + lenMarker;
+        for (int i = letters; i < end; i++) {
+            char c = result.charAt(i);
+            result.setCharAt(i, Character.toUpperCase(c));
+        }
+    }
+
+    /**
+     * Common code to deal with the sign, and the special cases "0", "-0", "nan, "inf", or "-inf".
+     * If the method returns <code>false</code>, we have started a non-zero number and the sign is
+     * already in {@link #result}. The client need then only encode <i>abs(value)</i>. If the method
+     * returns <code>true</code>, and {@link #lenMarker}==0, the value was "0" or "-0": the caller
+     * may have to zero-extend this, and/or add an exponent, to match the requested format. If the
+     * method returns <code>true</code>, and {@link #lenMarker}>0, the method has placed "nan, "inf"
+     * in the {@link #result} buffer (preceded by a sign if necessary).
+     *
+     * @param value to convert
+     * @return true if the value was one of "0", "-0", "nan, "inf", or "-inf".
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     */
+    private boolean signAndSpecialNumber(double value, String positivePrefix) {
+
+        // This is easiest via the raw bits
+        long bits = Double.doubleToRawLongBits(value);
+
+        // NaN is always positive
+        if (Double.isNaN(value)) {
+            bits &= ~SIGN_MASK;
+        }
+
+        if ((bits & SIGN_MASK) != 0) {
+            // Negative: encode a minus sign and strip it off bits
+            result.append('-');
+            lenSign = 1;
+            bits &= ~SIGN_MASK;
+
+        } else if (positivePrefix != null) {
+            // Positive, and a prefix is required. Note CPython 2.7 produces "+nan", " nan".
+            result.append(positivePrefix);
+            lenSign = positivePrefix.length();
+        }
+
+        if (bits == 0L) {
+            // All zero means it's zero. (It may have been negative, producing -0.)
+            result.append('0');
+            lenWhole = 1;
+            return true;
+
+        } else if ((bits & EXP_MASK) == EXP_MASK) {
+            // This is characteristic of NaN or Infinity.
+            result.append(((bits & ~EXP_MASK) == 0L) ? "inf" : "nan");
+            lenMarker = 3;
+            return true;
+
+        } else {
+            return false;
+        }
+    }
+
+    private static final long SIGN_MASK = 0x8000000000000000L;
+    private static final long EXP_MASK = 0x7ff0000000000000L;
+
+    /**
+     * The e-format helper function of {@link #format(double, String)} that uses Java's
+     * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
+     * the {@link #result} buffer, and {@link #start} will be set to the index of its first
+     * character.
+     *
+     * @param value to convert
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     * @param precision precision (maximum number of fractional digits)
+     */
+    private void format_e(double value, String positivePrefix, int precision) {
+
+        // Exponent (default value is for 0.0 and -0.0)
+        int exp = 0;
+
+        if (!signAndSpecialNumber(value, positivePrefix)) {
+            // Convert abs(value) to decimal with p+1 digits of accuracy.
+            MathContext mc = new MathContext(precision + 1, ROUND_PY);
+            BigDecimal vv = new BigDecimal(Math.abs(value), mc);
+
+            // Take explicit control in order to get exponential notation out of BigDecimal.
+            String digits = vv.unscaledValue().toString();
+            int digitCount = digits.length();
+            result.append(digits.charAt(0));
+            lenWhole = 1;
+            if (digitCount > 1) {
+                // There is a fractional part
+                result.append('.').append(digits.substring(1));
+                lenPoint = 1;
+                lenFraction = digitCount - 1;
+            }
+            exp = lenFraction - vv.scale();
+        }
+
+        // Finally add zeros, as necessary, and stick on the exponent.
+
+        if (lenMarker == 0) {
+            appendTrailingZeros(precision);
+            appendExponent(exp);
+        }
+    }
+
+    /**
+     * The f-format inner helper function of {@link #format(double, String)} that uses Java's
+     * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to
+     * the {@link #result} buffer, and {@link #start} will be set to the index of its first
+     * character.
+     *
+     * @param value to convert
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     * @param precision precision (maximum number of fractional digits)
+     */
+    private void format_f(double value, String positivePrefix, int precision) {
+
+        if (signAndSpecialNumber(value, positivePrefix)) {
+
+            if (lenMarker == 0) {
+                // May be 0 or -0 so we still need to ...
+                appendTrailingZeros(precision);
+            }
+
+        } else {
+            // Convert value to decimal exactly. (This can be very long.)
+            BigDecimal vLong = new BigDecimal(Math.abs(value));
+
+            // Truncate to the defined number of places to the right of the decimal point).
+            BigDecimal vv = vLong.setScale(precision, ROUND_PY);
+
+            // When converted to text, the number of fractional digits is exactly the scale we set.
+            String raw = vv.toPlainString();
+            result.append(raw);
+            if ((lenFraction = vv.scale()) > 0) {
+                // There is a decimal point and some digits following
+                lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction);
+            } else {
+                lenWhole = result.length() - (start + lenSign);
+            }
+
+        }
+    }
+
+    /**
+     * Implementation of the variants of g-format, that uses Java's {@link BigDecimal} to provide
+     * conversion and rounding. These variants are g-format proper, alternate g-format (available
+     * for "%#g" formatting), n-format (as g but subsequently "internationalised"), and none-format
+     * (type code Spec.NONE).
+     * <p>
+     * None-format is the basis of <code>float.__str__</code>.
+     * <p>
+     * According to the Python documentation for g-format, the precise rules are as follows: suppose
+     * that the result formatted with presentation type <code>'e'</code> and precision <i>p-1</i>
+     * would have exponent exp. Then if <i>-4 <= exp < p</i>, the number is formatted with
+     * presentation type <code>'f'</code> and precision <i>p-1-exp</i>. Otherwise, the number is
+     * formatted with presentation type <code>'e'</code> and precision <i>p-1</i>. In both cases
+     * insignificant trailing zeros are removed from the significand, and the decimal point is also
+     * removed if there are no remaining digits following it.
+     * <p>
+     * The Python documentation says none-format is the same as g-format, but the observed behaviour
+     * differs from this, in that f-format is only used if <i>-4 <= exp < p-1</i> (i.e. one
+     * less), and at least one digit to the right of the decimal point is preserved in the f-format
+     * (but not the e-format). That behaviour is controlled through the following arguments, with
+     * these recommended values:
+     *
+     * <table>
+     * <tr>
+     * <th>type</th>
+     * <th>precision</th>
+     * <th>minFracDigits</th>
+     * <th>expThresholdAdj</th>
+     * <td>expThreshold</td>
+     * </tr>
+     * <tr>
+     * <th>g</th>
+     * <td>p</td>
+     * <td>0</td>
+     * <td>0</td>
+     * <td>p</td>
+     * </tr>
+     * <tr>
+     * <th>#g</th>
+     * <td>p</td>
+     * <td>-</td>
+     * <td>0</td>
+     * <td>p</td>
+     * </tr>
+     * <tr>
+     * <th>\0</th>
+     * <td>p</td>
+     * <td>1</td>
+     * <td>-1</td>
+     * <td>p-1</td>
+     * </tr>
+     * <tr>
+     * <th>__str__</th>
+     * <td>12</td>
+     * <td>1</td>
+     * <td>-1</td>
+     * <td>11</td>
+     * </tr>
+     * </table>
+     *
+     * @param value to convert
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     * @param precision total number of significant digits (precision 0 behaves as 1)
+     * @param expThresholdAdj <code>+precision =</code> the exponent at which to resume using
+     *            exponential notation
+     */
+    private void format_g(double value, String positivePrefix, int precision, int expThresholdAdj) {
+
+        // Precision 0 behaves as 1
+        precision = Math.max(1, precision);
+
+        // Use exponential notation if exponent would be bigger thatn:
+        int expThreshold = precision + expThresholdAdj;
+
+        if (signAndSpecialNumber(value, positivePrefix)) {
+            // Finish formatting if zero result. (This is a no-op for nan or inf.)
+            zeroHelper(precision, expThreshold);
+
+        } else {
+
+            // Convert abs(value) to decimal with p digits of accuracy.
+            MathContext mc = new MathContext(precision, ROUND_PY);
+            BigDecimal vv = new BigDecimal(Math.abs(value), mc);
+
+            // This gives us the digits we need for either fixed or exponential format.
+            String pointlessDigits = vv.unscaledValue().toString();
+
+            // If we were to complete this as e-format, the exponent would be:
+            int exp = pointlessDigits.length() - vv.scale() - 1;
+
+            if (-4 <= exp && exp < expThreshold) {
+                // Finish the job as f-format with variable-precision p-(exp+1).
+                appendFixed(pointlessDigits, exp, precision);
+
+            } else {
+                // Finish the job as e-format.
+                appendExponential(pointlessDigits, exp);
+            }
+        }
+    }
+
+    /**
+     * Implementation of r-format (<code>float.__repr__</code>) that uses Java's
+     * {@link Double#toString(double)} to provide conversion and rounding. That method gives us
+     * almost what we need, but not quite (sometimes it yields 18 digits): here we always round to
+     * 17 significant digits. Much of the formatting after conversion is shared with
+     * {@link #format_g(double, String, int, int, int)}. <code>minFracDigits</code> is consulted
+     * since while <code>float.__repr__</code> truncates to one digit, within
+     * <code>complex.__repr__</code> we truncate fully.
+     *
+     * @param value to convert
+     * @param positivePrefix to use before positive values (e.g. "+") or null to default to ""
+     */
+    private void format_r(double value, String positivePrefix) {
+
+        // Characteristics of repr (precision = 17 and go exponential at 16).
+        int precision = 17;
+        int expThreshold = precision - 1;
+
+        if (signAndSpecialNumber(value, positivePrefix)) {
+            // Finish formatting if zero result. (This is a no-op for nan or inf.)
+            zeroHelper(precision, expThreshold);
+
+        } else {
+
+            // Generate digit sequence (with no decimal point) with custom rounding.
+            StringBuilder pointlessBuffer = new StringBuilder(20);
+            int exp = reprDigits(Math.abs(value), precision, pointlessBuffer);
+
+            if (-4 <= exp && exp < expThreshold) {
+                // Finish the job as f-format with variable-precision p-(exp+1).
+                appendFixed(pointlessBuffer, exp, precision);
+
+            } else {
+                // Finish the job as e-format.
+                appendExponential(pointlessBuffer, exp);
+            }
+        }
+    }
+
+    /**
+     * Common code for g-format, none-format and r-format called when the conversion yields "inf",
+     * "nan" or zero. The method completes formatting of the zero, with the appropriate number of
+     * decimal places or (in particular circumstances) exponential; notation.
+     *
+     * @param precision of conversion (number of significant digits).
+     * @param expThreshold if zero, causes choice of exponential notation for zero.
+     */
+    private void zeroHelper(int precision, int expThreshold) {
+
+        if (lenMarker == 0) {
+            // May be 0 or -0 so we still need to ...
+            if (minFracDigits < 0) {
+                // In "alternate format", we won't economise trailing zeros.
+                appendPointAndTrailingZeros(precision - 1);
+            } else if (lenFraction < minFracDigits) {
+                // Otherwise, it should be at least the stated minimum length.
+                appendTrailingZeros(minFracDigits);
+            }
+
+            // And just occasionally (in none-format) we go exponential even when exp = 0...
+            if (0 >= expThreshold) {
+                appendExponent(0);
+            }
+        }
+    }
+
+    /**
+     * Common code for g-format, none-format and r-format used when the exponent is such that a
+     * fixed-point presentation is chosen. Normally the method removes trailing digits so as to
+     * shorten the presentation without loss of significance. This method respects the minimum
+     * number of fractional digits (digits after the decimal point), in member
+     * <code>minFracDigits</code>, which is 0 for g-format and 1 for none-format and r-format. When
+     * <code>minFracDigits<0</code> this signifies "no truncation" mode, in which trailing zeros
+     * generated in the conversion are not removed. This supports "%#g" format.
+     *
+     * @param digits from converting the value at a given precision.
+     * @param exp would be the exponent (in e-format), used to position the decimal point.
+     * @param precision of conversion (number of significant digits).
+     */
+
+    private void appendFixed(CharSequence digits, int exp, int precision) {
+
+        // Check for "alternate format", where we won't economise trailing zeros.
+        boolean noTruncate = (minFracDigits < 0);
+
+        int digitCount = digits.length();
+
+        if (exp < 0) {
+            // For a negative exponent, we must insert leading zeros 0.000 ...
+            result.append("0.");
+            lenWhole = lenPoint = 1;
+            for (int i = -1; i > exp; --i) {
+                result.append('0');
+            }
+            // Then the generated digits (always enough to satisfy no-truncate mode).
+            result.append(digits);
+            lenFraction = digitCount - exp - 1;
+
+        } else {
+            // For a non-negative exponent, it's a question of placing the decimal point.
+            int w = exp + 1;
+            if (w < digitCount) {
+                // There are w whole-part digits
+                result.append(digits.subSequence(0, w));
+                lenWhole = w;
+                result.append('.').append(digits.subSequence(w, digitCount));
+                lenPoint = 1;
+                lenFraction = digitCount - w;
+            } else {
+                // All the digits are whole-part digits.
+                result.append(digits);
+                // Just occasionally (in r-format) we need more digits than the precision.
+                while (digitCount < w) {
+                    result.append('0');
+                    digitCount += 1;
+                }
+                lenWhole = digitCount;
+            }
+
+            if (noTruncate) {
+                // Extend the fraction as BigDecimal will have economised on zeros.
+                appendPointAndTrailingZeros(precision - digitCount);
+            }
+        }
+
+        // Finally, ensure we have all and only the fractional digits we should.
+        if (!noTruncate) {
+            if (lenFraction < minFracDigits) {
+                // Otherwise, it should be at least the stated minimum length.
+                appendTrailingZeros(minFracDigits);
+            } else {
+                // And no more
+                removeTrailingZeros(minFracDigits);
+            }
+        }
+    }
+
+    /**
+     * Common code for g-format, none-format and r-format used when the exponent is such that an
+     * exponential presentation is chosen. Normally the method removes trailing digits so as to
+     * shorten the presentation without loss of significance. Although no minimum number of
+     * fractional digits is enforced in the exponential presentation, when
+     * <code>minFracDigits<0</code> this signifies "no truncation" mode, in which trailing zeros
+     * generated in the conversion are not removed. This supports "%#g" format.
+     *
+     * @param digits from converting the value at a given precision.
+     * @param exp would be the exponent (in e-format), used to position the decimal point.
+     */
+    private void appendExponential(CharSequence digits, int exp) {
+
+        // The whole-part is the first digit.
+        result.append(digits.charAt(0));
+        lenWhole = 1;
+
+        // And the rest of the digits form the fractional part
+        int digitCount = digits.length();
+        result.append('.').append(digits.subSequence(1, digitCount));
+        lenPoint = 1;
+        lenFraction = digitCount - 1;
+
+        // In no-truncate mode, the fraction is full precision. Otherwise trim it.
+        if (minFracDigits >= 0) {
+            // Note minFracDigits only applies to fixed formats.
+            removeTrailingZeros(0);
+        }
+
+        // Finally, append the exponent as e+nn.
+        appendExponent(exp);
+    }
+
+    /**
+     * Convert a double to digits and an exponent for use in <code>float.__repr__</code> (or
+     * r-format). This method takes advantage of (or assumes) a close correspondence between
+     * {@link Double#toString(double)} and Python <code>float.__repr__</code>. The correspondence
+     * appears to be exact, insofar as the Java method produces the minimal non-zero digit string.
+     * It mostly chooses the same number of digits (and the same digits) as the CPython repr, but in
+     * a few cases <code>Double.toString</code> produces more digits. This method truncates to the
+     * number <code>maxDigits</code>, which in practice is always 17.
+     *
+     * @param value to convert
+     * @param maxDigits maximum number of digits to return in <code>buf</code>.
+     * @param buf for digits of result (recommend size be 20)
+     * @return the exponent
+     */
+    private static int reprDigits(double value, int maxDigits, StringBuilder buf) {
+
+        // Most of the work is done by Double.
+        String s = Double.toString(value);
+
+        // Variables for scanning the string
+        int p = 0, end = s.length(), first = 0, point = end, exp;
+        char c = 0;
+        boolean allZero = true;
+
+        // Scan whole part and fractional part digits
+        while (p < end) {
+            c = s.charAt(p++);
+            if (Character.isDigit(c)) {
+                if (allZero) {
+                    if (c != '0') {
+                        // This is the first non-zero digit.
+                        buf.append(c);
+                        allZero = false;
+                        // p is one *after* the first non-zero digit.
+                        first = p;
+                    }
+                    // Only seen zeros so far: do nothing.
+                } else {
+                    // We've started, so every digit counts.
+                    buf.append(c);
+                }
+
+            } else if (c == '.') {
+                // We remember this location (one *after* '.') to calculate the exponent later.
+                point = p;
+
+            } else {
+                // Something after the mantissa. (c=='E' we hope.)
+                break;
+            }
+        }
+
+        // Possibly followed by an exponent. p has already advanced past the 'E'.
+        if (p < end && c == 'E') {
+            // If there is an exponent, the mantissa must be in standard form: m.mmmm
+            assert point == first + 1;
+            exp = Integer.parseInt(s.substring(p));
+
+        } else {
+            // Exponent is based on relationship of decimal point and first non-zero digit.
+            exp = point - first - 1;
+            // But that's only correct when the point is to the right (or absent).
+            if (exp < 0) {
+                // The point is to the left of the first digit
+                exp += 1; // = -(first-point)
+            }
+        }
+
+        /*
+         * XXX This still does not round in all the cases it could. I think Java stops generating
+         * digits when the residual is <= ulp/2. This is to neglect the possibility that the extra
+         * ulp/2 (before it becomes a different double) could take us to a rounder numeral. To fix
+         * this, we could express ulp/2 as digits in the same scale as those in the buffer, and
+         * consider adding them. But Java's behaviour here is probably a manifestation of bug
+         * JDK-4511638.
+         */
+
+        // Sometimes the result is more digits than we want for repr.
+        if (buf.length() > maxDigits) {
+            // Chop the trailing digits, remembering the most significant lost digit.
+            int d = buf.charAt(maxDigits);
+            buf.setLength(maxDigits);
+            // We round half up. Not absolutely correct since Double has already rounded.
+            if (d >= '5') {
+                // Treat this as a "carry one" into the numeral buf[0:maxDigits].
+                for (p = maxDigits - 1; p >= 0; p--) {
+                    // Each pass of the loop does one carry from buf[p+1] to buf[p].
+                    d = buf.charAt(p) + 1;
+                    if (d <= '9') {
+                        // Carry propagation stops here.
+                        buf.setCharAt(p, (char)d);
+                        break;
+                    } else {
+                        // 9 + 1 -> 0 carry 1. Keep looping.
+                        buf.setCharAt(p, '0');
+                    }
+                }
+                if (p < 0) {
+                    /*
+                     * We fell off the bottom of the buffer with one carry still to propagate. You
+                     * may expect: buf.insert(0, '1') here, but note that every digit in
+                     * buf[0:maxDigits] is currently '0', so all we need is:
+                     */
+                    buf.setCharAt(0, '1');
+                    exp += 1;
+                }
+            }
+        }
+
+        return exp;
+    }
+
+    /**
+     * Append the trailing fractional zeros, as required by certain formats, so that the total
+     * number of fractional digits is no less than specified. If <code>minFracDigits<=0</code>,
+     * the method leaves the {@link #result} buffer unchanged.
+     *
+     * @param minFracDigits smallest number of fractional digits on return
+     */
+    private void appendTrailingZeros(int minFracDigits) {
+
+        int f = lenFraction;
+
+        if (minFracDigits > f) {
+            if (lenPoint == 0) {
+                // First need to add a decimal point. (Implies lenFraction=0.)
+                result.append('.');
+                lenPoint = 1;
+            }
+
+            // Now make up the required number of zeros.
+            for (; f < minFracDigits; f++) {
+                result.append('0');
+            }
+            lenFraction = f;
+        }
+    }
+
+    /**
+     * Append the trailing fractional zeros, as required by certain formats, so that the total
+     * number of fractional digits is no less than specified. If there is no decimal point
+     * originally (and therefore no fractional part), the method will add a decimal point, even if
+     * it adds no zeros.
+     *
+     * @param minFracDigits smallest number of fractional digits on return
+     */
+    private void appendPointAndTrailingZeros(int minFracDigits) {
+
+        if (lenPoint == 0) {
+            // First need to add a decimal point. (Implies lenFraction=0.)
+            result.append('.');
+            lenPoint = 1;
+        }
+
+        // Now make up the required number of zeros.
+        int f;
+        for (f = lenFraction; f < minFracDigits; f++) {
+            result.append('0');
+        }
+        lenFraction = f;
+    }
+
+    /**
+     * Remove trailing zeros from the fractional part, as required by certain formats, leaving at
+     * least the number of fractional digits specified. If the resultant number of fractional digits
+     * is zero, this method will also remove the trailing decimal point (if there is one).
+     *
+     * @param minFracDigits smallest number of fractional digits on return
+     */
+    private void removeTrailingZeros(int minFracDigits) {
+
+        int f = lenFraction;
+
+        if (lenPoint > 0) {
+            // There's a decimal point at least, and there may be some fractional digits.
+            if (minFracDigits == 0 || f > minFracDigits) {
+
+                int fracStart = result.length() - f;
+                for (; f > minFracDigits; --f) {
+                    if (result.charAt(fracStart - 1 + f) != '0') {
+                        // Keeping this one as it isn't a zero
+                        break;
+                    }
+                }
+
+                // f is now the number of fractional digits we wish to retain.
+                if (f == 0 && lenPoint > 0) {
+                    // We will be stripping all the fractional digits. Take the decimal point too.
+                    lenPoint = lenFraction = 0;
+                    f = -1;
+                } else {
+                    lenFraction = f;
+                }
+
+                // Snip the characters we are going to remove (if any).
+                if (fracStart + f < result.length()) {
+                    result.setLength(fracStart + f);
+                }
+            }
+        }
+    }
+
+    /**
+     * Append the current value of {@code exp} in the format <code>"e{:+02d}"</code> (for example
+     * <code>e+05</code>, <code>e-10</code>, <code>e+308</code> , etc.).
+     *
+     * @param exp exponent value to append
+     */
+    private void appendExponent(int exp) {
+
+        int marker = result.length();
+        String e;
+
+        // Deal with sign and leading-zero convention by explicit tests.
+        if (exp < 0) {
+            e = (exp <= -10) ? "e-" : "e-0";
+            exp = -exp;
+        } else {
+            e = (exp < 10) ? "e+0" : "e+";
+        }
+
+        result.append(e).append(exp);
+        lenMarker = 1;
+        lenExponent = result.length() - marker - 1;
+    }
+
+    /**
+     * Return the index in {@link #result} of the first letter. helper for {@link #uppercase()} and
+     * {@link #getExponent()}
+     */
+    private int indexOfMarker() {
+        return start + lenSign + lenWhole + lenPoint + lenFraction;
+    }
+
+}
diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java
deleted file mode 100644
--- a/src/org/python/core/stringlib/Formatter.java
+++ /dev/null
@@ -1,247 +0,0 @@
-package org.python.core.stringlib;
-import org.python.core.*;
-import org.python.core.util.ExtraMath;
-
-import java.math.BigInteger;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-
-
-public class Formatter {
-
-    public static String formatFloat(double value, InternalFormatSpec spec) {
-        InternalFormatter f = new InternalFormatter(spec);
-        String string = f.format(value);
-        return spec.pad(string, '>', 0);
-    }
-
-    public static String formatComplex(double real, double imag, InternalFormatSpec spec) {
-        String string;
-        InternalFormatter f = new InternalFormatter(spec);
-        String r = f.format(real);
-        String i = f.format(imag);
-        if (i.charAt(0) == '-') {
-            string = r + i + "j";
-        } else {
-            string = r + "+" + i + "j";
-        }
-        return spec.pad(string, '>', 0);
-    }
-}
-
-//Adapted from PyString's StringFormatter class.
-final class InternalFormatter {
-    InternalFormatSpec spec;
-    boolean negative;
-    int precision;
-
-    public InternalFormatter(InternalFormatSpec spec) {
-        this.spec = spec;
-        this.precision = spec.precision;
-        if (this.precision == -1)
-            this.precision = 6;
-    }
-
-    private void checkPrecision(String type) {
-        if(precision > 250) {
-            // A magic number. Larger than in CPython.
-            throw Py.OverflowError("formatted " + type + " is too long (precision too long?)");
-        }
-        
-    }
-
-    private String formatExp(long v, int radix) {
-        checkPrecision("integer");
-        if (v < 0) {
-            negative = true;
-            v = -v;
-        }
-        String s = Long.toString(v, radix);
-        while (s.length() < 2) {
-            s = "0"+s;
-        }
-        return s;
-    }
-
-    static class DecimalFormatTemplate {
-        static DecimalFormat template;
-        static {
-            template = new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US));
-            DecimalFormatSymbols symbols = template.getDecimalFormatSymbols();
-            symbols.setNaN("nan");
-            symbols.setInfinity("inf");
-            template.setDecimalFormatSymbols(symbols);
-            template.setGroupingUsed(false);
-        }
-    }
-
-    private static final DecimalFormat getDecimalFormat() {
-        return (DecimalFormat)DecimalFormatTemplate.template.clone();
-    }
-
-    static class PercentageFormatTemplate {
-        static DecimalFormat template;
-        static {
-            template = new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US));
-            DecimalFormatSymbols symbols = template.getDecimalFormatSymbols();
-            symbols.setNaN("nan");
-            symbols.setInfinity("inf");
-            template.setDecimalFormatSymbols(symbols);
-            template.setGroupingUsed(false);
-        }
-    }
-
-    private static final DecimalFormat getPercentageFormat() {
-        return (DecimalFormat)PercentageFormatTemplate.template.clone();
-    }
-
-    private String formatFloatDecimal(double v, boolean truncate) {
-        checkPrecision("decimal");
-        if (v < 0) {
-            v = -v;
-            negative = true;
-        }
-
-        DecimalFormat decimalFormat = getDecimalFormat();
-        decimalFormat.setMaximumFractionDigits(precision);
-        decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision);
-
-        if (spec.thousands_separators) {
-            decimalFormat.setGroupingUsed(true);
-        }
-        String ret = decimalFormat.format(v);
-        return ret;
-    }
-
-    private String formatPercentage(double v, boolean truncate) {
-        checkPrecision("decimal");
-        if (v < 0) {
-            v = -v;
-            negative = true;
-        }
-
-        DecimalFormat decimalFormat = getPercentageFormat();
-        decimalFormat.setMaximumFractionDigits(precision);
-        decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision);
-
-        String ret = decimalFormat.format(v);
-        return ret;
-    }
-
-    private String formatFloatExponential(double v, char e, boolean truncate) {
-        StringBuilder buf = new StringBuilder();
-        boolean isNegative = false;
-        if (v < 0) {
-            v = -v;
-            isNegative = true;
-        }
-        double power = 0.0;
-        if (v > 0)
-            power = ExtraMath.closeFloor(Math.log10(v));
-        String exp = formatExp((long)power, 10);
-        if (negative) {
-            negative = false;
-            exp = '-'+exp;
-        }
-        else {
-            exp = '+' + exp;
-        }
-
-        double base = v/Math.pow(10, power);
-        buf.append(formatFloatDecimal(base, truncate));
-        buf.append(e);
-
-        buf.append(exp);
-        negative = isNegative;
-
-        return buf.toString();
-    }
-
-    @SuppressWarnings("fallthrough")
-    public String format(double value) {
-        String string;
-
-        if (spec.alternate) {
-            throw Py.ValueError("Alternate form (#) not allowed in float format specifier");
-        }
-        int sign = Double.compare(value, 0.0d);
-
-        if (Double.isNaN(value)) {
-            if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') {
-                string = "NAN";
-            } else {
-                string = "nan";
-            }
-        } else if (Double.isInfinite(value)) {
-            if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') {
-                if (value > 0) {
-                    string = "INF";
-                } else {
-                    string = "-INF";
-                }
-            } else {
-                if (value > 0) {
-                    string = "inf";
-                } else {
-                    string = "-inf";
-                }
-            }
-        } else {
-
-            switch(spec.type) {
-            case 'e':
-            case 'E':
-                string = formatFloatExponential(value, spec.type, false);
-                if (spec.type == 'E') {
-                    string = string.toUpperCase();
-                }
-                break;
-            case 'f':
-            case 'F':
-                string = formatFloatDecimal(value, false);
-                if (spec.type == 'F') {
-                    string = string.toUpperCase();
-                }
-                break;
-            case 'g':
-            case 'G':
-                int exponent = (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value)));
-                int origPrecision = precision;
-                if (exponent >= -4 && exponent < precision) {
-                    precision -= exponent + 1;
-                    string = formatFloatDecimal(value, !spec.alternate);
-                } else {
-                    // Exponential precision is the number of digits after the decimal
-                    // point, whereas 'g' precision is the number of significant digits --
-                    // and exponential always provides one significant digit before the
-                    // decimal point
-                    precision--;
-                    string = formatFloatExponential(value, (char)(spec.type-2), !spec.alternate);
-                }
-                if (spec.type == 'G') {
-                    string = string.toUpperCase();
-                }
-                precision = origPrecision;
-                break;
-            case '%':
-                string = formatPercentage(value, false);
-                break;
-            default:
-                //Should never get here, since this was checked in PyFloat.
-                throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'",
-                                    spec.type));
-            }
-        }
-        if (sign >= 0) {
-            if (spec.sign == '+') {
-                string = "+" + string;
-            } else if (spec.sign == ' ') {
-                string = " " + string;
-            }
-        }
-        if (sign < 0 && string.charAt(0) != '-') {
-            string = "-" + string;
-        }
-        return string;
-    }
-}
diff --git a/src/org/python/core/stringlib/InternalFormat.java b/src/org/python/core/stringlib/InternalFormat.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/stringlib/InternalFormat.java
@@ -0,0 +1,824 @@
+// Copyright (c) Jython Developers
+package org.python.core.stringlib;
+
+import org.python.core.Py;
+import org.python.core.PyException;
+
+public class InternalFormat {
+
+    /**
+     * Create a {@link Spec} object by parsing a format specification.
+     *
+     * @param text to parse
+     * @return parsed equivalent to text
+     */
+    public static Spec fromText(String text) {
+        Parser parser = new Parser(text);
+        return parser.parse();
+    }
+
+    /**
+     * A class that provides the base for implementations of type-specific formatting. In a limited
+     * way, it acts like a StringBuilder to which text and one or more numbers may be appended,
+     * formatted according to the format specifier supplied at construction. These are ephemeral
+     * objects that are not, on their own, thread safe.
+     */
+    public static class Formatter implements Appendable {
+
+        /** The specification according to which we format any number supplied to the method. */
+        protected final Spec spec;
+        /** The (partial) result. */
+        protected StringBuilder result;
+
+        /** The number we are working on floats at the end of the result, and starts here. */
+        protected int start;
+        /** If it contains no sign, this length is zero, and 1 otherwise. */
+        protected int lenSign;
+        /** The length of the whole part (to left of the decimal point or exponent) */
+        protected int lenWhole;
+
+        /**
+         * Construct the formatter from a specification and initial buffer capacity. A reference is
+         * held to this specification, but it will not be modified by the actions of this class.
+         *
+         * @param spec parsed conversion specification
+         * @param width of buffer initially
+         */
+        public Formatter(Spec spec, int width) {
+            this.spec = spec;
+            result = new StringBuilder(width);
+        }
+
+        /**
+         * Current (possibly final) result of the formatting, as a <code>String</code>.
+         *
+         * @return formatted result
+         */
+        public String getResult() {
+            return result.toString();
+        }
+
+        /*
+         * Implement Appendable interface by delegation to the result buffer.
+         *
+         * @see java.lang.Appendable#append(char)
+         */
+        @Override
+        public Formatter append(char c) {
+            result.append(c);
+            return this;
+        }
+
+        @Override
+        public Formatter append(CharSequence csq) {
+            result.append(csq);
+            return this;
+        }
+
+        @Override
+        public Formatter append(CharSequence csq, int start, int end) //
+                throws IndexOutOfBoundsException {
+            result.append(csq, start, end);
+            return this;
+        }
+
+        /**
+         * Clear the instance variables describing the latest object in {@link #result}, ready to
+         * receive a new number
+         */
+        public void setStart() {
+            // Mark the end of the buffer as the start of the current object and reset all.
+            start = result.length();
+            // Clear the variable describing the latest number in result.
+            reset();
+        }
+
+        /**
+         * Clear the instance variables describing the latest object in {@link #result}, ready to
+         * receive a new one.
+         */
+        protected void reset() {
+            // Clear the variable describing the latest object in result.
+            lenSign = lenWhole = 0;
+        }
+
+        /**
+         * Supports {@link #toString()} by returning the lengths of the successive sections in the
+         * result buffer, used for navigation relative to {@link #start}. The <code>toString</code>
+         * method shows a '|' character between each section when it prints out the buffer. Override
+         * this when you define more lengths in the subclass.
+         *
+         * @return
+         */
+        protected int[] sectionLengths() {
+            return new int[] {lenSign, lenWhole};
+        }
+
+        /**
+         * {@inheritDoc}
+         * <p>
+         * Overridden to provide a debugging view in which the actual text is shown divided up by
+         * the <code>len*</code> member variables. If the dividers don't look right, those variables
+         * have not remained consistent with the text.
+         */
+        @Override
+        public String toString() {
+            if (result == null) {
+                return ("[]");
+            } else {
+                StringBuilder buf = new StringBuilder(result.length() + 20);
+                buf.append(result);
+                try {
+                    int p = start;
+                    buf.insert(p++, '[');
+                    for (int len : sectionLengths()) {
+                        p += len;
+                        buf.insert(p++, '|');
+                    }
+                    buf.setCharAt(p - 1, ']');
+                } catch (IndexOutOfBoundsException e) {
+                    // Some length took us beyond the end of the result buffer. Pass.
+                }
+                return buf.toString();
+            }
+        }
+
+        /**
+         * Insert grouping characters (conventionally commas) into the whole part of the number.
+         * {@link #lenWhole} will increase correspondingly.
+         *
+         * @param groupSize normally 3.
+         * @param comma or some other character to use as a separator.
+         */
+        protected void groupDigits(int groupSize, char comma) {
+
+            // Work out how many commas (or whatever) it takes to group the whole-number part.
+            int commasNeeded = (lenWhole - 1) / groupSize;
+
+            if (commasNeeded > 0) {
+                // Index *just after* the current last digit of the whole part of the number.
+                int from = start + lenSign + lenWhole;
+                // Open a space into which the whole part will expand.
+                makeSpaceAt(from, commasNeeded);
+                // Index *just after* the end of that space.
+                int to = from + commasNeeded;
+                // The whole part will be longer by the number of commas to be inserted.
+                lenWhole += commasNeeded;
+
+                /*
+                 * Now working from high to low, copy all the digits that have to move. Each pass
+                 * copies one group and inserts a comma, which makes the to-pointer move one place
+                 * extra. The to-pointer descends upon the from-pointer from the right.
+                 */
+                while (to > from) {
+                    // Copy a group
+                    for (int i = 0; i < groupSize; i++) {
+                        result.setCharAt(--to, result.charAt(--from));
+                    }
+                    // Write the comma that precedes it.
+                    result.setCharAt(--to, comma);
+                }
+            }
+        }
+
+        /**
+         * Make a space in {@link #result} of a certain size and position. On return, the segment
+         * lengths are likely to be invalid until the caller adjusts them corresponding to the
+         * insertion. There is no guarantee what the opened space contains.
+         *
+         * @param pos at which to make the space
+         * @param size of the space
+         */
+        protected void makeSpaceAt(int pos, int size) {
+            int n = result.length();
+            if (pos < n) {
+                // Space is not at the end: must copy what's to the right of pos.
+                String tail = result.substring(pos);
+                result.setLength(n + size);
+                result.replace(pos + size, n + size, tail);
+            } else {
+                // Space is at the end.
+                result.setLength(n + size);
+            }
+        }
+
+        /**
+         * Convert letters in the representation of the current number (in {@link #result}) to upper
+         * case.
+         */
+        protected void uppercase() {
+            int end = result.length();
+            for (int i = start; i < end; i++) {
+                char c = result.charAt(i);
+                result.setCharAt(i, Character.toUpperCase(c));
+            }
+        }
+
+        /**
+         * Pad the result so far (defined as the entire contents of {@link #result}) using the
+         * alignment, target width and fill character defined in {@link #spec}. The action of
+         * padding will increase the overall length of the result to the target width, if that is
+         * greater than the current length.
+         * <p>
+         * When the padding method has decided that that it needs to add n padding characters, it
+         * will affect {@link #start} or {@link #lenSign} as follows.
+         * <table border style>
+         * <tr>
+         * <th>align</th>
+         * <th>meaning</th>
+         * <th>start</th>
+         * <th>lenSign</th>
+         * <th>result.length()</th>
+         * </tr>
+         * <tr>
+         * <th><</th>
+         * <td>left-aligned</td>
+         * <td>+0</td>
+         * <td>+0</td>
+         * <td>+n</td>
+         * </tr>
+         * <tr>
+         * <th>></th>
+         * <td>right-aligned</td>
+         * <td>+n</td>
+         * <td>+0</td>
+         * <td>+n</td>
+         * </tr>
+         * <tr>
+         * <th>^</th>
+         * <td>centred</td>
+         * <td>+(n/2)</td>
+         * <td>+0</td>
+         * <td>+n</td>
+         * </tr>
+         * <tr>
+         * <th>=</th>
+         * <td>pad after sign</td>
+         * <td>+0</td>
+         * <td>+n</td>
+         * <td>+n</td>
+         * </tr>
+         * </table>
+         * Note that we may have converted more than one value into the result buffer (for example
+         * when formatting a complex number). The pointer <code>start</code> is at the start of the
+         * last number converted. Padding with zeros, and the "pad after sign" mode, will produce a
+         * result you probably don't want. It is up to the client to disallow this (which
+         * <code>complex</code> does).
+         *
+         * @param value to pad
+         * @return this object
+         */
+        public Formatter pad() {
+
+            // We'll need this many pad characters (if>0). Note Spec.UNDEFINED<0.
+            int n = spec.width - result.length();
+            if (n > 0) {
+
+                char align = spec.getAlign('>'); // Right for numbers (wrong for strings)
+                char fill = spec.getFill(' ');
+
+                // Start by assuming padding is all leading ('>' case or '=')
+                int leading = n;
+
+                // Split the total padding according to the alignment
+                if (align == '^') {
+                    // Half the padding before
+                    leading = n / 2;
+                } else if (align == '<') {
+                    // All the padding after
+                    leading = 0;
+                }
+
+                // All padding that is not leading is trailing
+                int trailing = n - leading;
+
+                // Insert the leading space
+                if (leading > 0) {
+                    int pos;
+                    if (align == '=') {
+                        // Incorporate into the (latest) whole part
+                        pos = start + lenSign;
+                        lenWhole += leading;
+                    } else {
+                        // Insert at the very beginning (not start) by default.
+                        pos = 0;
+                        start += leading;
+                    }
+                    makeSpaceAt(pos, leading);
+                    for (int i = 0; i < leading; i++) {
+                        result.setCharAt(pos + i, fill);
+                    }
+                }
+
+                // Append the trailing space
+                for (int i = 0; i < trailing; i++) {
+                    result.append(fill);
+                }
+
+                // Check for special case
+                if (align == '=' && fill == '0' && spec.grouping) {
+                    // We must extend the grouping separator into the padding
+                    zeroPadAfterSignWithGroupingFixup(3, ',');
+                }
+            }
+
+            return this;
+        }
+
+        /**
+         * Fix-up the zero-padding of the last formatted number in {@link #result()} in the special
+         * case where a sign-aware padding (<code>{@link #spec}.align='='</code>) was requested, the
+         * fill character is <code>'0'</code>, and the digits are to be grouped. In these exact
+         * circumstances, the grouping, which must already have been applied to the (whole part)
+         * number itself, has to be extended into the zero-padding.
+         *
+         * <pre>
+         * >>> format(-12e8, " =30,.3f")
+         * '-            1,200,000,000.000'
+         * >>> format(-12e8, "*=30,.3f")
+         * '-************1,200,000,000.000'
+         * >>> format(-12e8, "*>30,.3f")
+         * '************-1,200,000,000.000'
+         * >>> format(-12e8, "0>30,.3f")
+         * '000000000000-1,200,000,000.000'
+         * >>> format(-12e8, "0=30,.3f")
+         * '-0,000,000,001,200,000,000.000'
+         * </pre>
+         *
+         * The padding has increased the overall length of the result to the target width. About one
+         * in three call to this method adds one to the width, because the whole part cannot start
+         * with a comma.
+         *
+         * <pre>
+         * >>> format(-12e8, " =30,.4f")
+         * '-           1,200,000,000.0000'
+         * >>> format(-12e8, "0=30,.4f")
+         * '-<b>0</b>,000,000,001,200,000,000.0000'
+         * </pre>
+         *
+         * Insert grouping characters (conventionally commas) into the whole part of the number.
+         * {@link #lenWhole} will increase correspondingly.
+         *
+         * @param groupSize normally 3.
+         * @param comma or some other character to use as a separator.
+         */
+        protected void zeroPadAfterSignWithGroupingFixup(int groupSize, char comma) {
+            /*
+             * Suppose the format call was format(-12e8, "0=30,.3f"). At this point, we have
+             * something like this in result: .. [-|0000000000001,200,000,000|.|000||]
+             *
+             * All we need do is over-write some of the zeros with the separator comma, in the
+             * portion marked as the whole-part: [-|0,000,000,001,200,000,000|.|000||]
+             */
+
+            // First digit of the whole-part.
+            int firstZero = start + lenSign;
+            // One beyond last digit of the whole-part.
+            int p = firstZero + lenWhole;
+            // Step back down the result array visiting the commas. (Easiest to do all of them.)
+            int step = groupSize + 1;
+            for (p = p - step; p >= firstZero; p -= step) {
+                result.setCharAt(p, comma);
+            }
+
+            // Sometimes the last write was exactly at the first padding zero.
+            if (p + step == firstZero) {
+                /*
+                 * Suppose the format call was format(-12e8, "0=30,.4f"). At the beginning, we had
+                 * something like this in result: . [-|000000000001,200,000,000|.|0000||]
+                 *
+                 * And now, result looks like this: [-|0000,000,001,200,000,000|.|0000||] in which
+                 * the first zero is wrong as it stands, nor can it just be over-written with a
+                 * comma. We have to insert another zero, even though this makes the result longer
+                 * than we were given.
+                 */
+                result.insert(firstZero, '0');
+                lenWhole += 1;
+            }
+        }
+
+        /**
+         * Convenience method returning a {@link Py#ValueError} reporting:
+         * <p>
+         * <code>"Unknown format code '"+code+"' for object of type '"+forType+"'"</code>
+         *
+         * @param code the presentation type
+         * @param forType the type it was found applied to
+         * @return exception to throw
+         */
+        public static PyException unknownFormat(char code, String forType) {
+            String msg = "Unknown format code '" + code + "' for object of type '" + forType + "'";
+            return Py.ValueError(msg);
+        }
+
+        /**
+         * Convenience method returning a {@link Py#ValueError} reporting that alternate form is not
+         * allowed in a format specifier for the named type.
+         *
+         * @param forType the type it was found applied to
+         * @return exception to throw
+         */
+        public static PyException alternateFormNotAllowed(String forType) {
+            return notAllowed("Alternate form (#)", forType);
+        }
+
+        /**
+         * Convenience method returning a {@link Py#ValueError} reporting that the given alignment
+         * flag is not allowed in a format specifier for the named type.
+         *
+         * @param align type of alignment
+         * @param forType the type it was found applied to
+         * @return exception to throw
+         */
+        public static PyException alignmentNotAllowed(char align, String forType) {
+            return notAllowed("'" + align + "' alignment flag", forType);
+        }
+
+        /**
+         * Convenience method returning a {@link Py#ValueError} reporting that zero padding is not
+         * allowed in a format specifier for the named type.
+         *
+         * @param forType the type it was found applied to
+         * @return exception to throw
+         */
+        public static PyException zeroPaddingNotAllowed(String forType) {
+            return notAllowed("Zero padding", forType);
+        }
+
+        /**
+         * Convenience method returning a {@link Py#ValueError} reporting that some format specifier
+         * feature is not allowed for the named type.
+         *
+         * @param particularOutrage committed in the present case
+         * @param forType the type it where it is an outrage
+         * @return exception to throw
+         */
+        protected static PyException notAllowed(String particularOutrage, String forType) {
+            String msg = particularOutrage + " is not allowed in " + forType + " format specifier";
+            return Py.ValueError(msg);
+        }
+
+    }
+
+    /**
+     * Parsed PEP-3101 format specification of a single field, encapsulating the format for use by
+     * formatting methods. This class holds the several attributes that might be decoded from a
+     * format specifier. Each attribute has a reserved value used to indicate "unspecified".
+     * <code>Spec</code> objects may be merged such that one <code>Spec</code> provides values,
+     * during the construction of a new <code>Spec</code>, for attributes that are unspecified in a
+     * primary source.
+     * <p>
+     * This structure is returned by factory method {@link #fromText(CharSequence)}, and having
+     * public final members is freely accessed by formatters such as {@link FloatBuilder}, and the
+     * __format__ methods of client object types.
+     * <p>
+     * The fields correspond to the elements of a format specification. The grammar of a format
+     * specification is:
+     *
+     * <pre>
+     * [[fill]align][sign][#][0][width][,][.precision][type]
+     * </pre>
+     *
+     * A typical idiom is:
+     *
+     * <pre>
+     *     private static final InternalFormatSpec FLOAT_DEFAULT = InternalFormatSpec.from(">");
+     *     ...
+     *         InternalFormatSpec spec = InternalFormatSpec.from(specString, FLOAT_DEFAULT);
+     *         ... // Validation of spec.type, and other attributes, for this type.
+     *         FloatBuilder buf = new FloatBuilder(spec);
+     *         buf.format(value);
+     *         String result = buf.getResult();
+     *
+     * </pre>
+     */
+    public static class Spec {
+
+        /** The fill character specified, or '\uffff' if unspecified. */
+        public final char fill;
+        /**
+         * Alignment indicator is one of {<code>'<', '^', '>', '='</code>, or '\uffff' if
+         * unspecified.
+         */
+        public final char align;
+        /**
+         * Sign-handling flag, one of <code>'+'</code>, <code>'-'</code>, or <code>' '</code>, or
+         * '\uffff' if unspecified.
+         */
+        public final char sign;
+        /** The alternative format flag '#' was given. */
+        public final boolean alternate;
+        /** Width to which to pad the result, or -1 if unspecified. */
+        public final int width;
+        /** Insert the grouping separator (which in Python always indicates a group-size of 3). */
+        public final boolean grouping;
+        /** Precision decoded from the format, or -1 if unspecified. */
+        public final int precision;
+        /** Type key from the format, or '\uffff' if unspecified. */
+        public final char type;
+
+        /** Non-character code point used to represent "no value" in <code>char</code> attributes. */
+        public static final char NONE = '\uffff';
+        /** Negative value used to represent "no value" in <code>int</code> attributes. */
+        public static final int UNSPECIFIED = -1;
+
+        /**
+         * Test to see if an attribute has been specified.
+         *
+         * @param c attribute
+         * @return true only if the attribute is not equal to {@link #NONE}
+         */
+        public static final boolean specified(char c) {
+            return c != NONE;
+        }
+
+        /**
+         * Test to see if an attribute has been specified.
+         *
+         * @param value of attribute
+         * @return true only if the attribute is >=0 (meaning that it has been specified).
+         */
+        public static final boolean specified(int value) {
+            return value >= 0;
+        }
+
+        /**
+         * Constructor to set all the fields in the format specifier.
+         *
+         * <pre>
+         * [[fill]align][sign][#][0][width][,][.precision][type]
+         * </pre>
+         *
+         * @param fill fill character (or {@link #NONE}
+         * @param align alignment indicator, one of {<code>'<', '^', '>', '='</code>
+         * @param sign policy, one of <code>'+'</code>, <code>'-'</code>, or <code>' '</code>.
+         * @param alternate true to request alternate formatting mode (<code>'#'</code> flag).
+         * @param width of field after padding or -1 to default
+         * @param grouping true to request comma-separated groups
+         * @param precision (e.g. decimal places) or -1 to default
+         * @param type indicator character
+         */
+        public Spec(char fill, char align, char sign, boolean alternate, int width,
+                boolean grouping, int precision, char type) {
+            this.fill = fill;
+            this.align = align;
+            this.sign = sign;
+            this.alternate = alternate;
+            this.width = width;
+            this.grouping = grouping;
+            this.precision = precision;
+            this.type = type;
+        }
+
+        /**
+         * Return a format specifier (text) equivalent to the value of this Spec.
+         */
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder();
+            if (specified(fill)) {
+                buf.append(fill);
+            }
+            if (specified(align)) {
+                buf.append(align);
+            }
+            if (specified(sign)) {
+                buf.append(sign);
+            }
+            if (alternate) {
+                buf.append('#');
+            }
+            if (specified(width)) {
+                buf.append(width);
+            }
+            if (grouping) {
+                buf.append(',');
+            }
+            if (specified(precision)) {
+                buf.append('.').append(precision);
+            }
+            if (specified(type)) {
+                buf.append(type);
+            }
+            return buf.toString();
+        }
+
+        /**
+         * Return a merged <code>Spec</code> object, in which any attribute of this object, that is
+         * specified (or <code>true</code>) has the same value in the result, and any attribute of
+         * this object that is unspecified (or <code>false</code>) has the value that attribute
+         * takes in the other object. This the second object supplies default values. (These
+         * defaults may also be unspecified.) The use of this method is to allow a <code>Spec</code>
+         * constructed from text to record exactly, and only, what was in the textual specification,
+         * while the __format__ method of a client object supplies its type-specific defaults. Thus
+         * "20" means "<20s" to a <code>str</code>, ">20.12" to a <code>float</code> and ">20.12g"
+         * to a <code>complex</code>.
+         *
+         * @param defaults to merge where this object does not specify the attribute.
+         * @return a new Spec object.
+         */
+        public Spec withDefaults(Spec other) {
+            return new Spec(//
+                    specified(fill) ? fill : other.fill, //
+                    specified(align) ? align : other.align, //
+                    specified(sign) ? sign : other.sign, //
+                    alternate || other.alternate, //
+                    specified(width) ? width : other.width, //
+                    grouping || other.grouping, //
+                    specified(precision) ? precision : other.precision, //
+                    specified(type) ? type : other.type //
+            );
+        }
+
+        /**
+         * Defaults applicable to most numeric types. Equivalent to " >"
+         */
+        public static final Spec NUMERIC = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED,
+                false, Spec.UNSPECIFIED, Spec.NONE);
+
+        /**
+         * Constructor offering just precision and type.
+         *
+         * <pre>
+         * [.precision][type]
+         * </pre>
+         *
+         * @param precision (e.g. decimal places)
+         * @param type indicator character
+         */
+        public Spec(int width, int precision, char type) {
+            this(' ', '>', Spec.NONE, false, UNSPECIFIED, false, precision, type);
+        }
+
+        /** The alignment from the parsed format specification, or default. */
+        public char getFill(char defaultFill) {
+            return specified(fill) ? fill : defaultFill;
+        }
+
+        /** The alignment from the parsed format specification, or default. */
+        public char getAlign(char defaultAlign) {
+            return specified(align) ? align : defaultAlign;
+        }
+
+        /** The precision from the parsed format specification, or default. */
+        public int getPrecision(int defaultPrecision) {
+            return specified(precision) ? precision : defaultPrecision;
+        }
+
+        /** The type code from the parsed format specification, or default supplied. */
+        public char getType(char defaultType) {
+            return specified(type) ? type : defaultType;
+        }
+
+    }
+
+    /**
+     * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()}
+     * method that translates the format specification into an <code>Spec</code> object.
+     */
+    private static class Parser {
+
+        private String spec;
+        private int ptr;
+
+        /**
+         * Constructor simply holds the specification string ahead of the {@link #parse()}
+         * operation.
+         *
+         * @param spec format specifier to parse (e.g. "<+12.3f")
+         */
+        Parser(String spec) {
+            this.spec = spec;
+            this.ptr = 0;
+        }
+
+        /**
+         * Parse the specification with which this object was initialised into an {@link Spec},
+         * which is an object encapsulating the format for use by formatting methods. This parser
+         * deals only with the format specifiers themselves, as accepted by the
+         * <code>__format__</code> method of a type, or the <code>format()</code> built-in, not
+         * format strings in general as accepted by <code>str.format()</code>.
+         *
+         * @return the <code>Spec</code> equivalent to the string given.
+         */
+        /*
+         * This method is the equivalent of CPython's parse_internal_render_format_spec() in
+         * ~/Objects/stringlib/formatter.h, but we deal with defaults another way.
+         */
+        Spec parse() {
+
+            char fill = Spec.NONE, align = Spec.NONE;
+            char sign = Spec.NONE, type = Spec.NONE;
+            boolean alternate = false, grouping = false;
+            int width = Spec.UNSPECIFIED, precision = Spec.UNSPECIFIED;
+
+            // Scan [[fill]align] ...
+            if (isAlign()) {
+                // First is alignment. fill not specified.
+                align = spec.charAt(ptr++);
+            } else {
+                // Peek ahead
+                ptr += 1;
+                if (isAlign()) {
+                    // Second character is alignment, so first is fill
+                    fill = spec.charAt(0);
+                    align = spec.charAt(ptr++);
+                } else {
+                    // Second character is not alignment. We are still at square zero.
+                    ptr = 0;
+                }
+            }
+
+            // Scan [sign] ...
+            if (isAt("+- ")) {
+                sign = spec.charAt(ptr++);
+            }
+
+            // Scan [#] ...
+            alternate = scanPast('#');
+
+            // Scan [0] ...
+            if (scanPast('0')) {
+                // Accept 0 here as equivalent to zero-fill but only not set already.
+                if (!Spec.specified(fill)) {
+                    fill = '0';
+                    if (!Spec.specified(align)) {
+                        // Also accept it as equivalent to "=" aligment but only not set already.
+                        align = '=';
+                    }
+                }
+            }
+
+            // Scan [width]
+            if (isDigit()) {
+                width = scanInteger();
+            }
+
+            // Scan [,][.precision][type]
+            grouping = scanPast(',');
+
+            // Scan [.precision]
+            if (scanPast('.')) {
+                if (isDigit()) {
+                    precision = scanInteger();
+                } else {
+                    throw new IllegalArgumentException("Format specifier missing precision");
+                }
+            }
+
+            // Scan [type]
+            if (ptr < spec.length()) {
+                type = spec.charAt(ptr++);
+            }
+
+            // If we haven't reached the end, something is wrong
+            if (ptr != spec.length()) {
+                throw new IllegalArgumentException("Invalid conversion specification");
+            }
+
+            // Restrict grouping to known formats. (Mirrors CPython, but misplaced?)
+            if (grouping && "defgEG%F\0".indexOf(type) == -1) {
+                throw new IllegalArgumentException("Cannot specify ',' with '" + type + "'.");
+            }
+
+            // Create a specification
+            return new Spec(fill, align, sign, alternate, width, grouping, precision, type);
+        }
+
+        /** Test that the next character is exactly the one specified, and advance past it if it is. */
+        private boolean scanPast(char c) {
+            if (ptr < spec.length() && spec.charAt(ptr) == c) {
+                ptr++;
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /** Test that the next character is one of a specified set. */
+        private boolean isAt(String chars) {
+            return ptr < spec.length() && (chars.indexOf(spec.charAt(ptr)) >= 0);
+        }
+
+        /** Test that the next character is one of the alignment characters. */
+        private boolean isAlign() {
+            return ptr < spec.length() && ("<^>=".indexOf(spec.charAt(ptr)) >= 0);
+        }
+
+        /** Test that the next character is a digit. */
+        private boolean isDigit() {
+            return ptr < spec.length() && Character.isDigit(spec.charAt(ptr));
+        }
+
+        /** The current character is a digit (maybe a sign). Scan the integer, */
+        private int scanInteger() {
+            int p = ptr++;
+            while (isDigit()) {
+                ptr++;
+            }
+            return Integer.parseInt(spec.substring(p, ptr));
+        }
+
+    }
+
+}
diff --git a/src/org/python/core/stringlib/InternalFormatSpec.java b/src/org/python/core/stringlib/InternalFormatSpec.java
--- a/src/org/python/core/stringlib/InternalFormatSpec.java
+++ b/src/org/python/core/stringlib/InternalFormatSpec.java
@@ -1,42 +1,88 @@
 package org.python.core.stringlib;
 
 /**
- * Parsed PEP-3101 format specification of a single field.
+ * Parsed PEP-3101 format specification of a single field. This class holds the several attributes
+ * that might be decoded from a format specifier. It provides a method
+ * {@link #pad(String, char, int)} for adjusting a string using those attributes related to padding
+ * to a string assumed to be the result of formatting to the given precision.
+ * <p>
+ * This structure is returned by {@link InternalFormatSpecParser#parse()} and having public members
+ * is freely used by {@link InternalFormatSpecParser}, {@link Formatter} and the __format__ methods
+ * of client object types.
+ * <p>
+ * The fields correspond to the elements of a format specification. The grammar of a format
+ * specification is:
+ *
+ * <pre>
+ * [[fill]align][sign][#][0][width][,][.precision][type]
+ * </pre>
  */
 public final class InternalFormatSpec {
+
+    /** The fill specified in the grammar. */
     public char fill_char;
+    /** Alignment indicator is 0, or one of {<code>'<', '^', '>', '='</code> . */
     public char align;
+    /** The alternative format flag '#' was given. */
     public boolean alternate;
+    /** Sign-handling flag, one of <code>'+'</code>, <code>'-'</code>, or <code>' '</code>. */
     public char sign;
+    /** Width to which to pad the resault in {@link #pad(String, char, int)}. */
     public int width = -1;
+    /** Insert the grouping separator (which in Python always indicates a group-size of 3). */
     public boolean thousands_separators;
+    /** Precision decoded from the format. */
     public int precision = -1;
+    /** Type key from the format. */
     public char type;
 
+    /**
+     * Pad value, using {@link #fill_char} (or <code>' '</code>) before and after, to {@link #width}
+     * <code>-leaveWidth</code>, aligned according to {@link #align} (or according to
+     * <code>defaultAlign</code>).
+     *
+     * @param value to pad
+     * @param defaultAlign to use if <code>this.align</code>=0 (one of <code>'<'</code>,
+     *            <code>'^'</code>, <code>'>'</code>, or <code>'='</code>).
+     * @param leaveWidth to reduce effective <code>this.width</code> by
+     * @return
+     */
     public String pad(String value, char defaultAlign, int leaveWidth) {
+
+        // We'll need this many pad characters (if>0)
         int remaining = width - value.length() - leaveWidth;
         if (remaining <= 0) {
             return value;
         }
-        StringBuilder result = new StringBuilder();
-        int leading = remaining;
+
+        // Use this.align or defaultAlign
         int useAlign = align;
         if (useAlign == 0) {
             useAlign = defaultAlign;
         }
+
+        // By default all padding is leading padding ('<' case or '=')
+        int leading = remaining;
         if (useAlign == '^') {
-            leading = remaining/2;
+            // Half the padding before
+            leading = remaining / 2;
         } else if (useAlign == '<') {
+            // All the padding after
             leading = 0;
         }
+
+        // Now build the result
+        StringBuilder result = new StringBuilder();
         char fill = fill_char != 0 ? fill_char : ' ';
-        for (int i = 0; i < leading; i++) {
+
+        for (int i = 0; i < leading; i++) { // before
             result.append(fill);
         }
         result.append(value);
-        for (int i = 0; i < remaining - leading; i++) {
+        for (int i = 0; i < remaining - leading; i++) { // after
             result.append(fill);
         }
+
         return result.toString();
     }
 }
diff --git a/src/org/python/core/stringlib/InternalFormatSpecParser.java b/src/org/python/core/stringlib/InternalFormatSpecParser.java
--- a/src/org/python/core/stringlib/InternalFormatSpecParser.java
+++ b/src/org/python/core/stringlib/InternalFormatSpecParser.java
@@ -1,19 +1,26 @@
 package org.python.core.stringlib;
 
 /**
- * Parser for PEP-3101 field format specifications.
+ * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} method
+ * that translates the format specification into an <code>InternalFormatSpec</code> object.
  */
 public class InternalFormatSpecParser {
+
     private String spec;
     private int index;
 
+    /**
+     * Constructor simply holds the specification streang ahead of the {@link #parse()} operation.
+     *
+     * @param spec format specifier to parse (e.g. "<+12.3f")
+     */
     public InternalFormatSpecParser(String spec) {
         this.spec = spec;
         this.index = 0;
     }
 
     private static boolean isAlign(char c) {
-        switch(c) {
+        switch (c) {
             case '<':
             case '>':
             case '=':
@@ -24,6 +31,24 @@
         }
     }
 
+    /**
+     * Parse the specification with which this object was initialised into an
+     * {@link InternalFormatSpec}, which is an object encapsulating the format for use by formatting
+     * methods. This parser deals only with the format specifiers themselves, as accepted by the
+     * <code>__format__</code> method of a type, or the <code>format()</code> built-in, not format
+     * strings in general as accepted by <code>str.format()</code>. A typical idiom is:
+     *
+     * <pre>
+     * InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
+     * </pre>
+     *
+     * @return the <code>InternalFormatSpec</code> equivalent to the constructor argument
+     */
+    /*
+     * This method is the equivalent of CPython's parse_internal_render_format_spec() in
+     * ~/Objects/stringlib/formatter.h.
+     */
+    // XXX Better encapsulated as a constructor of InternalFormatSpec?
     public InternalFormatSpec parse() {
         InternalFormatSpec result = new InternalFormatSpec();
         if (spec.length() >= 1 && isAlign(spec.charAt(0))) {
diff --git a/src/org/python/core/util/ExtraMath.java b/src/org/python/core/util/ExtraMath.java
--- a/src/org/python/core/util/ExtraMath.java
+++ b/src/org/python/core/util/ExtraMath.java
@@ -1,6 +1,9 @@
 // Copyright (c) Corporation for National Research Initiatives
 package org.python.core.util;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
 /**
  * A static utility class with two additional math functions.
  */
@@ -26,11 +29,58 @@
     }
 
     /**
-     * Returns floor(v) except when v is very close to the next number, when it
-     * returns ceil(v);
+     * Returns floor(v) except when v is very close to the next number, when it returns ceil(v);
      */
     public static double closeFloor(double v) {
         double floor = Math.floor(v);
         return close(v, floor + 1.0) ? floor + 1.0 : floor;
     }
+
+    /**
+     * Round the argument x to n decimal places. (Rounding is half-up in Python 2.) The method uses
+     * BigDecimal, to compute <i>r(x*10<sup>n</sup>)*10<sup>-n</sup></i>, where <i>r()</i> round to
+     * the nearest integer. It takes some short-cuts for extreme values.
+     * <p>
+     * For sufficiently small <i>x*10<sup>n</sup></i>, the rounding is to zero, and the return value
+     * is a signed zero (same sign as x). Suppose <i>x = a*2<sup>b</sup></i>, where the significand
+     * we must have <i>a<2</i>. Sufficiently small means such that <i>n log<sub>2</sub>10 <
+     * -(b+2)</i>.
+     * <p>
+     * For sufficiently large <i>x*10<sup>n</sup></i>, the adjustment of rounding is too small to
+     * affect the least significant bit. That is <i>a*2<sup>b</sup></i> represents an amount greater
+     * than one, and rounding no longer affects the value, and the return is x. Since the matissa
+     * has 52 fractional bits, sufficiently large means such that <i>n log<sub>2</sub>10 > 52-b</i>.
+     *
+     * @param x to round
+     * @param n decimal places
+     * @return x rounded.
+     */
+    public static double round(double x, int n) {
+
+        if (Double.isNaN(x) || Double.isInfinite(x) || x == 0.0) {
+            // nans, infinities and zeros round to themselves
+            return x;
+
+        } else {
+
+            // (Slightly less than) n*log2(10).
+            float nlog2_10 = 3.3219f * n;
+
+            // x = a * 2^b and a<2.
+            int b = Math.getExponent(x);
+
+            if (nlog2_10 > 52 - b) {
+                // When n*log2(10) > nmax, the lsb of abs(x) is >1, so x rounds to itself.
+                return x;
+            } else if (nlog2_10 < -(b + 2)) {
+                // When n*log2(10) < -(b+2), abs(x)<0.5*10^n so x rounds to (signed) zero.
+                return Math.copySign(0.0, x);
+            } else {
+                // We have to work it out properly.
+                BigDecimal xx = new BigDecimal(x);
+                BigDecimal rr = xx.setScale(n, RoundingMode.HALF_UP);
+                return rr.doubleValue();
+            }
+        }
+    }
 }

-- 
Repository URL: http://hg.python.org/jython


More information about the Jython-checkins mailing list