Douglas Denhartog
Douglas Denhartog

Reputation: 2054

Python3 Infinity/NaN: Decimal vs. float

Given (Python3):

>>> float('inf') == Decimal('inf')
True 

>>> float('-inf') <= float('nan') <= float('inf')
False

>>> float('-inf') <= Decimal(1) <= float('inf')
True

Why are the following invalid? I have read Special values.

Invalid

>>> Decimal('-inf') <= Decimal('nan') <= Decimal('inf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

>>> Decimal('-inf') <= float('nan') <= Decimal('inf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

>>> float('-inf') <= Decimal('nan') <= float('inf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

Upvotes: 4

Views: 5057

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121744

From the decimal.py source code:

# Note: The Decimal standard doesn't cover rich comparisons for
# Decimals.  In particular, the specification is silent on the
# subject of what should happen for a comparison involving a NaN.
# We take the following approach:
#
#   == 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.
#
# This behavior is designed to conform as closely as possible to
# that specified by IEEE 754.

And from the Special values section you say you read:

An attempt to compare two Decimals using any of the <, <=, > or >= operators will raise the InvalidOperation signal if either operand is a NaN, and return False if this signal is not trapped.

Note that IEEE 754 uses NaN as a floating point exception value; e.g. you did something that cannot be computed and you got an exception instead. It is a signal value and should be seen as an error, not something to compare other floats against, which is why in the IEEE 754 standard it is unequal to anything else.

Moreover, the Special values section mentions:

Note that the General Decimal Arithmetic specification does not specify the behavior of direct comparisons; these rules for comparisons involving a NaN were taken from the IEEE 854 standard (see Table 3 in section 5.7).

and looking at IEEE 854 section 5.7 we find:

In addition to the true/false response, an invalid operation exception (see 7.1) shall be signaled when, as indicated in the last column of Table 3, “unordered” operands are compared using one of the predicates involving “<” or “>” but not “?.” (Here the symbol “?” signifies “unordered.” )

with comparisons with NaN classified as unordered.

By default InvalidOperation is trapped, so a Python exception is raised when using <= and >= against Decimal('NaN'). This is a logical extension; Python has actual exceptions so if you compare against the NaN exception value, you can expect an exception being raised.

You could disable trapping by using a Decimal.localcontext():

>>> from decimal import localcontext, Decimal, InvalidOperation
>>> with localcontext() as ctx:
...     ctx.traps[InvalidOperation] = 0
...     Decimal('-inf') <= Decimal('nan') <= Decimal('inf')
... 
False

Upvotes: 4

Related Questions