Issue #7279: Make comparisons involving a Decimal sNaN signal InvalidOperation.
This commit is contained in:
parent
ea2d389474
commit
e096e82e82
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue