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

This commit is contained in:
Mark Dickinson 2010-04-02 10:17:07 +00:00
parent ea2d389474
commit e096e82e82
3 changed files with 60 additions and 14 deletions

View File

@ -845,8 +845,11 @@ class Decimal(object):
# subject of what should happen for a comparison involving a NaN. # subject of what should happen for a comparison involving a NaN.
# We take the following approach: # We take the following approach:
# #
# == comparisons involving a NaN always return False # == comparisons involving a quiet NaN always return False
# != comparisons involving a NaN always return True # != 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) # <, >, <= and >= comparisons involving a (quiet or signaling)
# NaN signal InvalidOperation, and return False if the # NaN signal InvalidOperation, and return False if the
# InvalidOperation is not trapped. # InvalidOperation is not trapped.
@ -854,19 +857,19 @@ class Decimal(object):
# This behavior is designed to conform as closely as possible to # This behavior is designed to conform as closely as possible to
# that specified by IEEE 754. # that specified by IEEE 754.
def __eq__(self, other): def __eq__(self, other, context=None):
other = _convert_other(other, allow_float=True) other = _convert_other(other, allow_float=True)
if other is NotImplemented: if other is NotImplemented:
return other return other
if self.is_nan() or other.is_nan(): if self._check_nans(other, context):
return False return False
return self._cmp(other) == 0 return self._cmp(other) == 0
def __ne__(self, other): def __ne__(self, other, context=None):
other = _convert_other(other, allow_float=True) other = _convert_other(other, allow_float=True)
if other is NotImplemented: if other is NotImplemented:
return other return other
if self.is_nan() or other.is_nan(): if self._check_nans(other, context):
return True return True
return self._cmp(other) != 0 return self._cmp(other) != 0

View File

@ -26,6 +26,7 @@ with the corresponding argument.
import math import math
import os, sys import os, sys
import operator
import pickle, copy import pickle, copy
import unittest import unittest
from decimal import * from decimal import *
@ -1080,18 +1081,56 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
self.assertEqual(abs(Decimal(45)), abs(Decimal(-45))) # abs self.assertEqual(abs(Decimal(45)), abs(Decimal(-45))) # abs
def test_nan_comparisons(self): 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') n = Decimal('NaN')
s = Decimal('sNaN') s = Decimal('sNaN')
i = Decimal('Inf') i = Decimal('Inf')
f = Decimal('2') 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)]: qnan_pairs = (n, n), (n, i), (i, n), (n, f), (f, n)
self.assertTrue(x != y) snan_pairs = (s, n), (n, s), (s, i), (i, s), (s, f), (f, s), (s, s)
self.assertTrue(not (x == y)) order_ops = operator.lt, operator.le, operator.gt, operator.ge
self.assertTrue(not (x < y)) equality_ops = operator.eq, operator.ne
self.assertTrue(not (x <= y))
self.assertTrue(not (x > y)) # results when InvalidOperation is not trapped
self.assertTrue(not (x >= y)) 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): def test_copy_sign(self):
d = Decimal(1).copy_sign(Decimal(-2)) d = Decimal(1).copy_sign(Decimal(-2))

View File

@ -35,6 +35,10 @@ Core and Builtins
Library 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 - Issue #2531: Comparison operations between floats and Decimal
instances now return a result based on the numeric values of the instances now return a result based on the numeric values of the
operands; previously they returned an arbitrary result based on operands; previously they returned an arbitrary result based on