[Python-checkins] r79588 - in python/trunk: Lib/decimal.py Lib/test/test_decimal.py Misc/NEWS

mark.dickinson python-checkins at python.org
Fri Apr 2 12:17:07 CEST 2010

Author: mark.dickinson
Date: Fri Apr  2 12:17:07 2010
New Revision: 79588

Log:
Issue #7279:  Make comparisons involving a Decimal sNaN signal InvalidOperation.

Modified:
python/trunk/Lib/decimal.py
python/trunk/Lib/test/test_decimal.py
python/trunk/Misc/NEWS

Modified: python/trunk/Lib/decimal.py
==============================================================================
--- python/trunk/Lib/decimal.py	(original)
+++ python/trunk/Lib/decimal.py	Fri Apr  2 12:17:07 2010
@@ -845,8 +845,11 @@
# subject of what should happen for a comparison involving a NaN.
# We take the following approach:
#
-    #   == comparisons involving a NaN always return False
-    #   != comparisons involving a NaN always return True
+    #   == comparisons involving a quiet NaN always return False
+    #   != comparisons involving a quiet NaN always return True
+    #   == or != comparisons involving a signaling NaN signal
+    #      InvalidOperation, and return False or True as above if the
+    #      InvalidOperation is not trapped.
#   <, >, <= and >= comparisons involving a (quiet or signaling)
#      NaN signal InvalidOperation, and return False if the
#      InvalidOperation is not trapped.
@@ -854,19 +857,19 @@
# This behavior is designed to conform as closely as possible to
# that specified by IEEE 754.

-    def __eq__(self, other):
+    def __eq__(self, other, context=None):
other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
-        if self.is_nan() or other.is_nan():
+        if self._check_nans(other, context):
return False
return self._cmp(other) == 0

-    def __ne__(self, other):
+    def __ne__(self, other, context=None):
other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
-        if self.is_nan() or other.is_nan():
+        if self._check_nans(other, context):
return True
return self._cmp(other) != 0

Modified: python/trunk/Lib/test/test_decimal.py
==============================================================================
--- python/trunk/Lib/test/test_decimal.py	(original)
+++ python/trunk/Lib/test/test_decimal.py	Fri Apr  2 12:17:07 2010
@@ -26,6 +26,7 @@

import math
import os, sys
+import operator
import pickle, copy
import unittest
from decimal import *
@@ -1080,18 +1081,56 @@
self.assertEqual(abs(Decimal(45)), abs(Decimal(-45)))  # abs

def test_nan_comparisons(self):
+        # comparisons involving signaling nans signal InvalidOperation
+
+        # order comparisons (<, <=, >, >=) involving only quiet nans
+        # also signal InvalidOperation
+
+        # equality comparisons (==, !=) involving only quiet nans
+        # don't signal, but return False or True respectively.
+
n = Decimal('NaN')
s = Decimal('sNaN')
i = Decimal('Inf')
f = Decimal('2')
-        for x, y in [(n, n), (n, i), (i, n), (n, f), (f, n),
-                     (s, n), (n, s), (s, i), (i, s), (s, f), (f, s), (s, s)]:
-            self.assertTrue(x != y)
-            self.assertTrue(not (x == y))
-            self.assertTrue(not (x < y))
-            self.assertTrue(not (x <= y))
-            self.assertTrue(not (x > y))
-            self.assertTrue(not (x >= y))
+
+        qnan_pairs = (n, n), (n, i), (i, n), (n, f), (f, n)
+        snan_pairs = (s, n), (n, s), (s, i), (i, s), (s, f), (f, s), (s, s)
+        order_ops = operator.lt, operator.le, operator.gt, operator.ge
+        equality_ops = operator.eq, operator.ne
+
+        # results when InvalidOperation is not trapped
+        for x, y in qnan_pairs + snan_pairs:
+            for op in order_ops + equality_ops:
+                got = op(x, y)
+                expected = True if op is operator.ne else False
+                self.assertIs(expected, got,
+                              "expected {0!r} for operator.{1}({2!r}, {3!r}); "
+                              "got {4!r}".format(
+                        expected, op.__name__, x, y, got))
+
+        # repeat the above, but this time trap the InvalidOperation
+        with localcontext() as ctx:
+            ctx.traps[InvalidOperation] = 1
+
+            for x, y in qnan_pairs:
+                for op in equality_ops:
+                    got = op(x, y)
+                    expected = True if op is operator.ne else False
+                    self.assertIs(expected, got,
+                                  "expected {0!r} for "
+                                  "operator.{1}({2!r}, {3!r}); "
+                                  "got {4!r}".format(
+                            expected, op.__name__, x, y, got))
+
+            for x, y in snan_pairs:
+                for op in equality_ops:
+                    self.assertRaises(InvalidOperation, operator.eq, x, y)
+                    self.assertRaises(InvalidOperation, operator.ne, x, y)
+
+            for x, y in qnan_pairs + snan_pairs:
+                for op in order_ops:
+                    self.assertRaises(InvalidOperation, op, x, y)

def test_copy_sign(self):
d = Decimal(1).copy_sign(Decimal(-2))

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Fri Apr  2 12:17:07 2010
@@ -35,6 +35,10 @@
Library
-------

+- Issue #7279: Comparisons involving a Decimal signaling NaN now
+  signal InvalidOperation instead of returning False.  (Comparisons
+  involving a quiet NaN are unchanged.)
+
- Issue #2531: Comparison operations between floats and Decimal
instances now return a result based on the numeric values of the
operands;  previously they returned an arbitrary result based on