[Jython-checkins] jython: Fixes for %-formatting floats.
jeff.allen
jython-checkins at python.org
Mon May 19 00:49:36 CEST 2014
http://hg.python.org/jython/rev/60838278e668
changeset: 7259:60838278e668
user: Jeff Allen <ja.py at farowl.co.uk>
date: Thu May 08 21:23:39 2014 +0100
summary:
Fixes for %-formatting floats.
Replaces the float rendering in PyString StringFormatter with that used for float.__format__(). Skips removed from test_float. Some change to FloatFormatter to support alternative mode in %#e and %#f.
files:
Lib/test/test_float.py | 40 --
Lib/test/test_float_jy.py | 5 +-
src/org/python/core/PyString.java | 149 +--------
src/org/python/core/stringlib/FloatFormatter.java | 91 ++++-
4 files changed, 83 insertions(+), 202 deletions(-)
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
@@ -617,8 +617,6 @@
self.assertEqual('{0:f}'.format(NAN), 'nan')
self.assertEqual('{0:F}'.format(NAN), 'NAN')
- @unittest.skipIf(test_support.is_jython,
- "FIXME: not working on Jython")
@requires_IEEE_754
def test_format_testfile(self):
with open(format_testfile) as testfile:
@@ -842,8 +840,6 @@
self.assertAlmostEqual(round(1.5e22, -22), 2e22)
- @unittest.skipIf(test_support.is_jython,
- "FIXME: %-formatting specials imperfect in Jython")
@requires_IEEE_754
def test_format_specials(self):
# Test formatting of nans and infs.
@@ -878,42 +874,6 @@
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
@@ -40,11 +40,8 @@
def test_float_str_formatting(self):
self.assertEqual('%.13g' % 12345678.00005, '12345678.00005')
- self.assertEqual('%.12g' % 12345678.00005,
- jython and '12345678' or '12345678.0001')
+ self.assertEqual('%.12g' % 12345678.00005, '12345678.0001')
self.assertEqual('%.11g' % 12345678.00005, '12345678')
- # XXX: The exponential formatter isn't totally correct, e.g. our
- # output here is really .13g
self.assertEqual('%.12g' % math.pi**-100, '1.92758141606e-50')
self.assertEqual('%.5g' % 123.005, '123')
self.assertEqual('%#.5g' % 123.005, '123.00')
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
@@ -4,17 +4,15 @@
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.math.BigInteger;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-
-import org.python.core.StringFormatter.DecimalFormatTemplate;
+
import org.python.core.buffer.BaseBuffer;
import org.python.core.buffer.SimpleStringBuffer;
import org.python.core.stringlib.FieldNameIterator;
+import org.python.core.stringlib.FloatFormatter;
+import org.python.core.stringlib.InternalFormat.Spec;
import org.python.core.stringlib.InternalFormatSpec;
import org.python.core.stringlib.InternalFormatSpecParser;
import org.python.core.stringlib.MarkupIterator;
-import org.python.core.util.ExtraMath;
import org.python.core.util.StringUtil;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
@@ -4246,7 +4244,6 @@
// safe to call __int__:
argAsInt = arg.__int__();
} else {
- // Same case noted on formatFloatDecimal:
// We can't simply call arg.__int__() because PyString implements
// it without exposing it to python (i.e, str instances has no
// __int__ attribute). So, we would support strings as arguments
@@ -4255,7 +4252,7 @@
try {
argAsInt = arg.__getattr__("__int__").__call__();
} catch (PyException e) {
- // XXX: Swallow customs AttributeError throws from __float__ methods
+ // XXX: Swallow custom AttributeError throws from __int__ methods
// No better alternative for the moment
if (e.match(Py.AttributeError)) {
throw Py.TypeError("int argument required");
@@ -4317,79 +4314,6 @@
}
}
- 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();
- }
-
- private String formatFloatDecimal(double v, boolean truncate) {
- checkPrecision("decimal");
- int prec = precision;
- if (prec == -1) {
- prec = 6;
- }
- if (v < 0) {
- v = -v;
- negative = true;
- }
-
- DecimalFormat decimalFormat = getDecimalFormat();
- decimalFormat.setMaximumFractionDigits(prec);
- decimalFormat.setMinimumFractionDigits(truncate ? 0 : prec);
-
- String ret = decimalFormat.format(v);
- return ret;
- }
-
- private String formatFloatExponential(PyObject arg, char e, boolean truncate) {
- StringBuilder buf = new StringBuilder();
- double v = asDouble(arg);
- boolean isNegative = false;
- if (v < 0) {
- v = -v;
- isNegative = true;
- }
- double power = 0.0;
- if (v > 0) {
- power = ExtraMath.closeFloor(Math.log10(v));
- }
- // System.err.println("formatExp: "+v+", "+power);
- int savePrecision = precision;
- precision = 2;
-
- String exp = formatInteger((long)power, 10, false);
- if (negative) {
- negative = false;
- exp = '-' + exp;
- } else {
- exp = '+' + exp;
- }
-
- precision = savePrecision;
-
- double base = v / Math.pow(10, power);
- buf.append(formatFloatDecimal(base, truncate));
- buf.append(e);
-
- buf.append(exp);
- negative = isNegative;
-
- return buf.toString();
- }
-
/**
* Main service of this class: format one or more arguments with the format string supplied at
* construction.
@@ -4655,61 +4579,26 @@
case 'e':
case 'E':
- // Floating point exponential format (+case).
- string = formatFloatExponential(arg, c, false);
- if (c == 'E') {
- string = string.toUpperCase();
- }
- break;
-
case 'f':
case 'F':
- // Floating point decimal format (+case). Note ints accepted.
- string = formatFloatDecimal(asDouble(arg), false);
- if (c == 'F') {
- string = string.toUpperCase();
- }
- break;
-
case 'g':
case 'G':
- // Value-adaptive floating point format (+case). Note ints accepted.
- int origPrecision = precision;
- if (precision == -1) {
- precision = 6;
- }
-
+ // All floating point formats (+case).
+
+ // Convert the flags (local variables) to the form needed in the Spec object.
+ char align = ljustFlag ? '<' : '>';
+ char sign = signFlag ? '+' : (blankFlag ? ' ' : Spec.NONE);
+ int w = Spec.UNSPECIFIED;
+ Spec spec = new Spec(fill, align, sign, altFlag, w, false, precision, c);
+
+ // Format using this Spec the double form of the argument.
+ FloatFormatter f = new FloatFormatter(spec);
double v = asDouble(arg);
- int exponent = (int)ExtraMath.closeFloor(Math.log10(Math.abs(v == 0 ? 1 : v)));
- if (v == Double.POSITIVE_INFINITY) {
- string = "inf";
- } else if (v == Double.NEGATIVE_INFINITY) {
- string = "-inf";
- } else if (exponent >= -4 && exponent < precision) {
- precision -= exponent + 1;
- string = formatFloatDecimal(v, !altFlag);
-
- // XXX: this block may be unnecessary now
- if (altFlag && string.indexOf('.') == -1) {
- int zpad = origPrecision - string.length();
- string += '.';
- if (zpad > 0) {
- char zeros[] = new char[zpad];
- for (int ci = 0; ci < zpad; zeros[ci++] = '0') {}
- string += new String(zeros);
- }
- }
- } 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(arg, (char)(c - 2), !altFlag);
- }
- if (c == 'G') {
- string = string.toUpperCase();
- }
+ f.format(v);
+ string = f.getResult();
+
+ // Suppress subsequent attempts to insert a correct sign, done already.
+ signFlag = blankFlag = negative = false;
break;
case 'c':
diff --git a/src/org/python/core/stringlib/FloatFormatter.java b/src/org/python/core/stringlib/FloatFormatter.java
--- a/src/org/python/core/stringlib/FloatFormatter.java
+++ b/src/org/python/core/stringlib/FloatFormatter.java
@@ -16,7 +16,7 @@
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
+ static final RoundingMode ROUND_PY = RoundingMode.HALF_EVEN;
/** If it contains no decimal point, this length is zero, and 1 otherwise. */
private int lenPoint;
@@ -325,10 +325,9 @@
exp = lenFraction - vv.scale();
}
- // Finally add zeros, as necessary, and stick on the exponent.
-
+ // If the result is not already complete, add point and zeros as necessary, and exponent.
if (lenMarker == 0) {
- appendTrailingZeros(precision);
+ ensurePointAndTrailingZeros(precision);
appendExponent(exp);
}
}
@@ -345,14 +344,7 @@
*/
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 {
+ if (!signAndSpecialNumber(value, positivePrefix)) {
// Convert value to decimal exactly. (This can be very long.)
BigDecimal vLong = new BigDecimal(Math.abs(value));
@@ -366,9 +358,53 @@
// There is a decimal point and some digits following
lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction);
} else {
+ // There are no fractional digits and so no decimal point
lenWhole = result.length() - (start + lenSign);
}
+ }
+ // Finally, ensure we have all the fractional digits we should.
+ if (lenMarker == 0) {
+ ensurePointAndTrailingZeros(precision);
+ }
+ }
+
+ /**
+ * Append a decimal point and trailing fractional zeros if necessary for 'e' and 'f' format.
+ * This should not be called if the result is not numeric ("inf" for example). This method deals
+ * with the following complexities: on return there will be at least the number of fractional
+ * digits specified in the argument <code>n</code>, and at least {@link #minFracDigits};
+ * further, if <code>minFracDigits<0</code>, signifying the "alternate mode" of certain
+ * formats, the method will ensure there is a decimal point, even if there are no fractional
+ * digits to follow.
+ *
+ * @param n smallest number of fractional digits on return
+ */
+ private void ensurePointAndTrailingZeros(int n) {
+
+ // Set n to the number of fractional digits we should have.
+ if (n < minFracDigits) {
+ n = minFracDigits;
+ }
+
+ // Do we have a decimal point already?
+ if (lenPoint == 0) {
+ // No decimal point: add one if there will be any fractional digits or
+ if (n > 0 || minFracDigits < 0) {
+ // First need to add a decimal point.
+ result.append('.');
+ lenPoint = 1;
+ }
+ }
+
+ // Do we have enough fractional digits already?
+ int f = lenFraction;
+ if (n > f) {
+ // Make up the required number of zeros.
+ for (; f < n; f++) {
+ result.append('0');
+ }
+ lenFraction = f;
}
}
@@ -635,7 +671,7 @@
// In no-truncate mode, the fraction is full precision. Otherwise trim it.
if (minFracDigits >= 0) {
- // Note minFracDigits only applies to fixed formats.
+ // Note positive minFracDigits only applies to fixed formats.
removeTrailingZeros(0);
}
@@ -757,16 +793,16 @@
/**
* 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.
+ * number of fractional digits is no less than specified. If <code>n<=0</code>, the method
+ * leaves the {@link #result} buffer unchanged.
*
- * @param minFracDigits smallest number of fractional digits on return
+ * @param n smallest number of fractional digits on return
*/
- private void appendTrailingZeros(int minFracDigits) {
+ private void appendTrailingZeros(int n) {
int f = lenFraction;
- if (minFracDigits > f) {
+ if (n > f) {
if (lenPoint == 0) {
// First need to add a decimal point. (Implies lenFraction=0.)
result.append('.');
@@ -774,7 +810,7 @@
}
// Now make up the required number of zeros.
- for (; f < minFracDigits; f++) {
+ for (; f < n; f++) {
result.append('0');
}
lenFraction = f;
@@ -787,9 +823,9 @@
* 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
+ * @param n smallest number of fractional digits on return
*/
- private void appendPointAndTrailingZeros(int minFracDigits) {
+ private void appendPointAndTrailingZeros(int n) {
if (lenPoint == 0) {
// First need to add a decimal point. (Implies lenFraction=0.)
@@ -799,7 +835,7 @@
// Now make up the required number of zeros.
int f;
- for (f = lenFraction; f < minFracDigits; f++) {
+ for (f = lenFraction; f < n; f++) {
result.append('0');
}
lenFraction = f;
@@ -810,18 +846,17 @@
* 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
+ * @param n smallest number of fractional digits on return
*/
- private void removeTrailingZeros(int minFracDigits) {
-
- int f = lenFraction;
+ private void removeTrailingZeros(int n) {
if (lenPoint > 0) {
// There's a decimal point at least, and there may be some fractional digits.
- if (minFracDigits == 0 || f > minFracDigits) {
+ int f = lenFraction;
+ if (n == 0 || f > n) {
int fracStart = result.length() - f;
- for (; f > minFracDigits; --f) {
+ for (; f > n; --f) {
if (result.charAt(fracStart - 1 + f) != '0') {
// Keeping this one as it isn't a zero
break;
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list