[pypy-commit] pypy default: add a half even rounding mode to round_double for py3k

pjenvey noreply at buildbot.pypy.org
Sat Dec 15 01:05:10 CET 2012


Author: Philip Jenvey <pjenvey at underboss.org>
Branch: 
Changeset: r59425:1fce20d13b3c
Date: 2012-12-14 16:03 -0800
http://bitbucket.org/pypy/pypy/changeset/1fce20d13b3c/

Log:	add a half even rounding mode to round_double for py3k

diff --git a/pypy/rlib/rfloat.py b/pypy/rlib/rfloat.py
--- a/pypy/rlib/rfloat.py
+++ b/pypy/rlib/rfloat.py
@@ -170,13 +170,17 @@
     result = formatd(value, tp, precision, flags)
     return result, special
 
-def round_double(value, ndigits):
+def round_double(value, ndigits, half_even=False):
+    """Round a float half away from zero.
+
+    Specify half_even=True to round half even instead.
+    """
     if USE_SHORT_FLOAT_REPR:
-        return round_double_short_repr(value, ndigits)
+        return round_double_short_repr(value, ndigits, half_even)
     else:
-        return round_double_fallback_repr(value, ndigits)
+        return round_double_fallback_repr(value, ndigits, half_even)
 
-def round_double_short_repr(value, ndigits):
+def round_double_short_repr(value, ndigits, half_even):
     # The basic idea is very simple: convert and round the double to
     # a decimal string using _Py_dg_dtoa, then convert that decimal
     # string back to a double with _Py_dg_strtod.  There's one minor
@@ -209,7 +213,7 @@
 
     # determine whether this is a halfway case.
     halfway_case = 0
-    if expo == -ndigits - 1:
+    if not half_even and expo == -ndigits - 1:
         if ndigits >= 0:
             halfway_case = 1
         elif ndigits >= -22:
@@ -224,7 +228,7 @@
     # round to a decimal string; use an extra place for halfway case
     strvalue = formatd(value, 'f', ndigits + halfway_case)
 
-    if halfway_case:
+    if not half_even and halfway_case:
         buf = [c for c in strvalue]
         if ndigits >= 0:
             endpos = len(buf) - 1
@@ -263,7 +267,7 @@
 
 # fallback version, to be used when correctly rounded
 # binary<->decimal conversions aren't available
-def round_double_fallback_repr(value, ndigits):
+def round_double_fallback_repr(value, ndigits, half_even):
     if ndigits >= 0:
         if ndigits > 22:
             # pow1 and pow2 are each safe from overflow, but
@@ -284,12 +288,17 @@
         pow2 = 1.0 # unused; for translation
         y = value / pow1
 
-    if y >= 0.0:
-        z = math.floor(y + 0.5)
+    if half_even:
+        z = round_away(y)
+        if math.fabs(y - z) == 0.5:
+            z = 2.0 * round_away(y / 2.0)
     else:
-        z = math.ceil(y - 0.5)
-    if math.fabs(y-z) == 1.0:   # obscure case, see the test
-        z = y
+        if y >= 0.0:
+            z = math.floor(y + 0.5)
+        else:
+            z = math.ceil(y - 0.5)
+        if math.fabs(y - z) == 1.0:   # obscure case, see the test
+            z = y
 
     if ndigits >= 0:
         z = (z / pow2) / pow1
diff --git a/pypy/rpython/test/test_rfloat.py b/pypy/rpython/test/test_rfloat.py
--- a/pypy/rpython/test/test_rfloat.py
+++ b/pypy/rpython/test/test_rfloat.py
@@ -374,6 +374,15 @@
         almost_equal(round_double(0.5e22, -22), 1e22)
         almost_equal(round_double(1.5e22, -22), 2e22)
 
+    def test_round_half_even(self):
+        from pypy.rlib import rfloat
+        for func in (rfloat.round_double_short_repr,
+                     rfloat.round_double_fallback_repr):
+            # 2.x behavior
+            assert func(2.5, 0, False) == 3.0
+            # 3.x behavior
+            assert func(2.5, 0, True) == 2.0
+
 
 class TestLLtype(BaseTestRfloat, LLRtypeMixin):
 


More information about the pypy-commit mailing list