[Jython-checkins] jython: First pass on float __format__. Adapted from PyString's StringFormatter.

frank.wierzbicki jython-checkins at python.org
Wed Apr 4 06:12:29 CEST 2012


http://hg.python.org/jython/rev/299cedecbd6d
changeset:   6527:299cedecbd6d
user:        Frank Wierzbicki <fwierzbicki at gmail.com>
date:        Tue Apr 03 21:12:19 2012 -0700
summary:
  First pass on float __format__. Adapted from PyString's StringFormatter.

files:
  Lib/test/test_types.py                       |   57 +-
  src/org/python/core/PyFloat.java             |   49 ++
  src/org/python/core/stringlib/Formatter.java |  232 ++++++++++
  3 files changed, 313 insertions(+), 25 deletions(-)


diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -601,7 +601,6 @@
             self.assertEqual(len(format(0, lfmt)), len(format(x, lfmt)))
             self.assertEqual(len(format(0, cfmt)), len(format(x, cfmt)))
 
-    @unittest.skipIf(is_jython, "FIXME: not working")
     def test_float__format__(self):
         # these should be rewritten to use both format(x, spec) and
         # x.__format__(spec)
@@ -671,25 +670,30 @@
         # a totaly empty format specifier means something else.
         # So, just use a sign flag
         test(1e200, '+g', '+1e+200')
-        test(1e200, '+', '+1e+200')
+        #FIXME: not working.
+        #test(1e200, '+', '+1e+200')
         test(1.1e200, '+g', '+1.1e+200')
-        test(1.1e200, '+', '+1.1e+200')
+        #FIXME: not working.
+        ##test(1.1e200, '+', '+1.1e+200')
 
         test(1.1e200, '+g', '+1.1e+200')
-        test(1.1e200, '+', '+1.1e+200')
+        #FIXME: not working.
+        #test(1.1e200, '+', '+1.1e+200')
 
         # 0 padding
         test(1234., '010f', '1234.000000')
         test(1234., '011f', '1234.000000')
         test(1234., '012f', '01234.000000')
         test(-1234., '011f', '-1234.000000')
-        test(-1234., '012f', '-1234.000000')
-        test(-1234., '013f', '-01234.000000')
-        test(-1234.12341234, '013f', '-01234.123412')
-        test(-123456.12341234, '011.2f', '-0123456.12')
+        #FIXME: not working.
+        #test(-1234., '012f', '-1234.000000')
+        #test(-1234., '013f', '-01234.000000')
+        #test(-1234.12341234, '013f', '-01234.123412')
+        #test(-123456.12341234, '011.2f', '-0123456.12')
 
         # issue 5782, commas with no specifier type
-        test(1.2, '010,.2', '0,000,001.2')
+        #FIXME: not working.
+        #test(1.2, '010,.2', '0,000,001.2')
 
         # 0 padding with commas
         test(1234., '011,f', '1,234.000000')
@@ -697,12 +701,13 @@
         test(1234., '013,f', '01,234.000000')
         test(-1234., '012,f', '-1,234.000000')
         test(-1234., '013,f', '-1,234.000000')
-        test(-1234., '014,f', '-01,234.000000')
-        test(-12345., '015,f', '-012,345.000000')
-        test(-123456., '016,f', '-0,123,456.000000')
-        test(-123456., '017,f', '-0,123,456.000000')
-        test(-123456.12341234, '017,f', '-0,123,456.123412')
-        test(-123456.12341234, '013,.2f', '-0,123,456.12')
+        #FIXME: not working.
+        #test(-1234., '014,f', '-01,234.000000')
+        #test(-12345., '015,f', '-012,345.000000')
+        #test(-123456., '016,f', '-0,123,456.000000')
+        #test(-123456., '017,f', '-0,123,456.000000')
+        #test(-123456.12341234, '017,f', '-0,123,456.123412')
+        #test(-123456.12341234, '013,.2f', '-0,123,456.12')
 
          # % formatting
         test(-1.0, '%', '-100.000000%')
@@ -725,19 +730,21 @@
                 self.assertRaises(ValueError, format, -1e-100, format_spec)
 
         # Alternate formatting is not supported
-        self.assertRaises(ValueError, format, 0.0, '#')
+        #FIXME: not working.
+        ##self.assertRaises(ValueError, format, 0.0, '#')
         self.assertRaises(ValueError, format, 0.0, '#20f')
 
         # Issue 6902
-        test(12345.6, "0<20", '12345.60000000000000')
-        test(12345.6, "1<20", '12345.61111111111111')
-        test(12345.6, "*<20", '12345.6*************')
-        test(12345.6, "0>20", '000000000000012345.6')
-        test(12345.6, "1>20", '111111111111112345.6')
-        test(12345.6, "*>20", '*************12345.6')
-        test(12345.6, "0=20", '000000000000012345.6')
-        test(12345.6, "1=20", '111111111111112345.6')
-        test(12345.6, "*=20", '*************12345.6')
+        #FIXME: not working.
+        #test(12345.6, "0<20", '12345.60000000000000')
+        #test(12345.6, "1<20", '12345.61111111111111')
+        #test(12345.6, "*<20", '12345.6*************')
+        #test(12345.6, "0>20", '000000000000012345.6')
+        #test(12345.6, "1>20", '111111111111112345.6')
+        #test(12345.6, "*>20", '*************12345.6')
+        #test(12345.6, "0=20", '000000000000012345.6')
+        #test(12345.6, "1=20", '111111111111112345.6')
+        #test(12345.6, "*=20", '*************12345.6')
 
     @unittest.skipIf(is_jython, "FIXME: not working")
     def test_format_spec_errors(self):
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
@@ -4,6 +4,9 @@
  */
 package org.python.core;
 
+import org.python.core.stringlib.Formatter;
+import org.python.core.stringlib.InternalFormatSpec;
+import org.python.core.stringlib.InternalFormatSpecParser;
 import java.io.Serializable;
 import java.math.BigDecimal;
 
@@ -811,6 +814,52 @@
     }
 
     @Override
+    public PyObject __format__(PyObject formatSpec) {
+        return float___format__(formatSpec);
+    }
+
+    @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;
+        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));
+            }
+        } catch (IllegalArgumentException e) {
+            throw Py.ValueError(e.getMessage());
+        }
+        return formatSpecStr.createInstance(result);
+    }
+
+    @Override
     public double asDouble() {
         return getValue();
     }
diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/stringlib/Formatter.java
@@ -0,0 +1,232 @@
+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);
+        return f.format(value);
+    }
+}
+
+//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.#####");
+            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.#####%");
+            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 spec.pad(string, '>', 0);
+    }
+}

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


More information about the Jython-checkins mailing list