]> granicus.if.org Git - python/commitdiff
Issue #7279: Make comparisons involving a Decimal sNaN signal InvalidOperation.
authorMark Dickinson <dickinsm@gmail.com>
Fri, 2 Apr 2010 10:17:07 +0000 (10:17 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Fri, 2 Apr 2010 10:17:07 +0000 (10:17 +0000)
Lib/decimal.py
Lib/test/test_decimal.py
Misc/NEWS

index 159669c3f3ceec0cbc95ae2b14fd2da9336284e4..52ac7a8b8be6e68f33558a453c260b9d9fd398c4 100644 (file)
@@ -845,8 +845,11 @@ class Decimal(object):
     # 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 @@ class Decimal(object):
     # 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
 
index 4071eff8db9c619ecaae9c93c51b9aa5be2b74dc..c2a6b0e0074c1935cd4fbda8bdec004477c63f82 100644 (file)
@@ -26,6 +26,7 @@ with the corresponding argument.
 
 import math
 import os, sys
+import operator
 import pickle, copy
 import unittest
 from decimal import *
@@ -1080,18 +1081,56 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
         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))
index 4b2255d6d7f02d7aff9ef258f8aeec77309db0fd..e9b39b7d7dec74b082f7c63ea04484cb800c5156 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -35,6 +35,10 @@ Core and Builtins
 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