From e096e82e827f6092706c7349fd4944c275382eb5 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Fri, 2 Apr 2010 10:17:07 +0000 Subject: [PATCH] Issue #7279: Make comparisons involving a Decimal sNaN signal InvalidOperation. --- Lib/decimal.py | 15 ++++++----- Lib/test/test_decimal.py | 55 ++++++++++++++++++++++++++++++++++------ Misc/NEWS | 4 +++ 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/Lib/decimal.py b/Lib/decimal.py index 159669c3f3c..52ac7a8b8be 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -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 diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 4071eff8db9..c2a6b0e0074 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -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)) diff --git a/Misc/NEWS b/Misc/NEWS index 4b2255d6d7f..e9b39b7d7de 100644 --- 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