[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