float("nan") in set or as key

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Jun 6 00:59:11 EDT 2011


On Mon, 06 Jun 2011 14:11:03 +1000, Chris Angelico wrote:

> On Mon, Jun 6, 2011 at 11:21 AM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> The intended behaviour is operations on "quiet NANs" should return
>> NANs, but operations on "signalling NANs" should cause a trap, which
>> can either be ignored, and converted into a quiet NAN, or treated as an
>> exception.
>>
>> E.g. in Decimal: [snip]
> 
> So does this mean that:
> 
> a = 0.0/0.0
> b = a + 1
> 
> (with signalling NANs) should trap on the second line but not the first?
> That's the first "operation on a nan".

Sort of.

Firstly, in order for a = 0.0/0.0 to not trap (not raise an exception), 
you have to tell it not to trap InvalidOperation (and DivideByZero I 
think?). So using Decimal:

>>> import decimal
>>> decimal.getcontext()
Context(prec=9, rounding=ROUND_HALF_UP, Emin=-999999999, Emax=999999999, 
capitals=1, flags=[], traps=[Underflow, Clamped, DivisionByZero, 
Overflow, InvalidOperation])


If we call Decimal(0)/Decimal(0), it will be trapped, which is treated as 
an exception in Python. To get a NAN:


>>> decimal.setcontext(decimal.ExtendedContext)
>>> decimal.getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, 
Emax=999999999, capitals=1, flags=[], traps=[])
>>>
>>> D = decimal.Decimal
>>> a = D(0)/D(0)
>>> a
Decimal('NaN')


Note that a flag is set, so you can tell that an exceptional event has 
occurred:


>>> decimal.getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, 
Emax=999999999, capitals=1, flags=[InvalidOperation], traps=[])


But the NAN given is a quiet NAN. Doing further operations on it doesn't 
trap:


>>> decimal.getcontext().traps[decimal.InvalidOperation] = 1
>>> decimal.getcontext().flags.clear()
>>> b = a + 1
>>> decimal.getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, 
Emax=999999999, capitals=1, flags=[], traps=[InvalidOperation])


However, if you use a signalling NAN, the situation is different. As far 
as I can tell, the only way to get a signalling NAN is to create one 
yourself:


>>> c = D('sNAN')
>>> d = c + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.6/decimal.py", line 1064, in __add__
    ans = self._check_nans(other, context)
  File "/usr/local/lib/python2.6/decimal.py", line 703, in _check_nans
    self)
  File "/usr/local/lib/python2.6/decimal.py", line 3778, in _raise_error
    raise error(explanation)
decimal.InvalidOperation: sNaN


I don't think that there's any way to tell IEEE-754 for operations on 
NANs to return signalling NANs. As I understand it, the idea is:


- if you want exceptions to signal, set the appropriate traps;
- if you want NANs that propagate through your calculation, clear the 
traps and you'll get propagating NANs;
- if you need to detect the presence of a NAN in your calculation, you 
can inspect the flags at any time and take whatever action you want;
- and if you want a signalling NAN, you have to inject it yourself into 
your calculation, and then avoid using it.


I'm lead to believe that signalling NANs were added to satisfy politics, 
but apart from being slightly useful for marking uninitialised memory 
before use, nobody actually uses them in practice.

Wanna see something cool? You can check for inexact arithmetic:

>>> decimal.getcontext().flags
{<class 'decimal.InvalidOperation'>: 1}
>>> D(1)/D(7)
Decimal('0.142857143')
>>> decimal.getcontext().flags
{<class 'decimal.Inexact'>: 1, <class 'decimal.InvalidOperation'>: 1, 
<class 'decimal.Rounded'>: 1}


and trap on it:


>>> decimal.getcontext().traps[decimal.Inexact] = 1
>>> D(1)/D(7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.6/decimal.py", line 1275, in __truediv__
    return ans._fix(context)
  File "/usr/local/lib/python2.6/decimal.py", line 1632, in _fix
    context._raise_error(Inexact)
  File "/usr/local/lib/python2.6/decimal.py", line 3778, in _raise_error
    raise error(explanation)
decimal.Inexact: None


Not surprisingly, by default that's turned off :)



-- 
Steven



More information about the Python-list mailing list