# Is there any difference between `Decimal("-nan")` and `Decimal("nan")`?

I stumbled on strange `nan` behavior.

``````from decimal import Decimal

nan = Decimal("nan")
mnan = Decimal("-nan")

print(nan)
# NaN

print(mnan)
# -NaN

print(mnan == nan)
# False

print(mnan is nan)
# False
``````

So it looks like `Decimal("-nan")` might be different than `Decimal("nan")`. But we also have

``````print(nan.is_nan())
# True

print(mnan.is_nan())
# True
``````

They both pass `is_nan()` checks, unsurprisingly.

But then we must recall that `Decimal("nan")` has strange comparison behavior.

``````nan2 = Decimal("nan")

print(nan == nan2)
# False

print(nan is nan2)
# False
``````

So `mnan` compares to `nan` the same as `nan2` compares to `nan`. From this perspective it seems like `Decimal("-nan")` behaves exactly like `Decimal("-nan")` except for the `__repr__` which includes the minus sign for `Decimal("-nan")`.

Does `Decimal("-nan")` matter? I have a number formatting library and I’m wondering if it could ever matter that I preserve the minus sign on `Decimal("-nan")`. I know `nan` is defined in some technical specifications. Is `-nan` specified?

Note that

``````print(float("-nan"))
# nan
``````

So for floats the `-nan` seems to be cast to just `nan`.

Here are some other ways that `Decimal("nan")` and `Decimal("-nan")` are distinguishable besides their `__repr__`:

``````>>> Decimal("nan").is_signed()
False
>>> Decimal("-nan").is_signed()
True

>>> Decimal(2).copy_sign(Decimal("nan"))
Decimal('2')
>>> Decimal(2).copy_sign(Decimal("-nan"))
Decimal('-2')

>>> Decimal("nan").copy_negate()
Decimal('-NaN')
>>> Decimal("-nan").copy_negate()
Decimal('NaN')

>>> Decimal("nan").compare_total(Decimal("0"))
Decimal('1')
>>> Decimal("-nan").compare_total(Decimal("0"))
Decimal('-1')

>>> Decimal("nan").compare_total(Decimal("-nan"))
Decimal('1')
>>> Decimal("-nan").compare_total(Decimal("nan"))
Decimal('-1')
``````

I suppose it depends. From the specs:

[…] the sign of a NaN has no meaning, although it may be considered part of the diagnostic information.

It seems that `float('-nan')` does construct a signed NaN.

``````>>> from math import copysign
>>> copysign(222, float('-nan'))
-222.0
>>> copysign(222, float('nan'))
222.0
``````
1 Like

At least in IEEE 754 (which, for most purposes, is the only floating-point format you need to worry about), there is an entire family of NaN values. They are distinguished by have all of the exponent bits set to 1, and at least one fraction bit set to 1. (All-1 exponent and all-0 fraction is infinity.) So half of all NaNs are “positive” (sign bit 0), and the other half are “negative” (sign bit 1). Things like `float("nan")` and `float("-nan")` return a particular representative signed NaN.

``````>>> import struct
>>> struct.pack("!d", float("nan"))
b'\x7f\xf8\x00\x00\x00\x00\x00\x00'
>>> struct.pack("!d", float("-nan"))
b'\xff\xf8\x00\x00\x00\x00\x00\x00'
``````
2 Likes