classification
Title: add inf and nan to math module
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: eric.smith, ethan.furman, gvanrossum, haypo, lemburg, mark.dickinson, pitrou, python-dev, rhettinger, serhiy.storchaka, stutzbach
Priority: normal Keywords: easy, patch

Created on 2015-01-07 16:33 by ethan.furman, last changed 2015-01-13 09:37 by mark.dickinson. This issue is now closed.

Files
File name Uploaded Description Edit
math_inf_nan.patch mark.dickinson, 2015-01-07 18:42 review
math_inf_nan2.patch mark.dickinson, 2015-01-07 19:27 review
math_inf_nan3.patch mark.dickinson, 2015-01-07 20:02 review
math_inf_nan4.patch mark.dickinson, 2015-01-08 17:40 review
Messages (26)
msg233580 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2015-01-07 16:33
Proposal:

  math.nan = float('nan')
  math.inf = float('inf')


Guido's approval:

  https://mail.python.org/pipermail/python-ideas/2015-January/030775.html


Followup question:

  Do we add a math.neginf, or somesuch, for float('-inf')?  Or just use -math.inf?
msg233581 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-07 16:42
>   Do we add a math.neginf, or somesuch, for float('-inf')?  Or just use -math.inf?

I would prefer to only add inf.

It looks like float("-inf") and -float("inf") have exactly the same IEEE 754 representation (at least on my x86_64 CPU):

>>> struct.pack("d", float("-inf")) == struct.pack("d", -float("inf"))
True
msg233582 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 16:57
Sounds good to me.

> Do we add a math.neginf

IMO no: -inf should be fine.
msg233583 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 16:58
> float("-inf") and -float("inf") have exactly the same IEEE 754 representation

Indeed: there's only one negative infinity (and only one positive infinity) in IEEE 754 binary64 format.
msg233585 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 17:07
Implementation suggestion: if possible, use the _Py_dg_stdnan and _Py_dg_infinity functions from Python/dtoa.c.  These are a little safer than the Py_NAN and Py_HUGE_VAL macros, and will give results consistent with the float("inf") and float("nan") constructions.
msg233588 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-07 17:25
Oh, NaN can be signed?

>>> struct.pack("d", float("nan"))
b'\x00\x00\x00\x00\x00\x00\xf8\x7f'
>>> struct.pack("d", float("-nan"))
b'\x00\x00\x00\x00\x00\x00\xf8\xff'
>>> struct.pack("d", -float("nan"))
b'\x00\x00\x00\x00\x00\x00\xf8\xff'
>>> struct.pack("d", -float("-nan"))
b'\x00\x00\x00\x00\x00\x00\xf8\x7f'

Why does Python return the same representation for positive and negative NaN?

>>> float("nan")
nan
>>> float("-nan")
nan
msg233591 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 18:18
> Why does Python return the same representation for positive and negative NaN?

History, perhaps?  In any case, the sign of a NaN isn't useful information in the same way that the sign of an infinity is.  The IEEE 754 standard explicitly refuses to attach any meaning to the sign bit of a NaN.  And if we were aiming for a full and faithful representation of NaNs, we'd want to output the payload, too (which is just about as meaningless / meaningful as the sign bit).
msg233593 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-01-07 18:25
There are several different NaNs.

>>> x = struct.unpack('d', b'\x00\x00\x00\x00\x00\x00\xf8\x7f')[0]
>>> x
nan
>>> x == x
False
>>> struct.pack('d', x)
b'\x00\x00\x00\x00\x00\x00\xf8\x7f'
>>> x = struct.unpack('d', b'\x00\x00\x00\x00\x00\x00\xf9\x7f')[0]
>>> x
nan
>>> x == x
False
>>> struct.pack('d', x)
b'\x00\x00\x00\x00\x00\x00\xf9\x7f'

Interesting, but 0*inf and inf-inf return values with the same representation as float('-nan'), not float('nan').

>>> inf = float("inf")
>>> struct.pack('d', 0*inf)
b'\x00\x00\x00\x00\x00\x00\xf8\xff'
>>> struct.pack('d', inf-inf)
b'\x00\x00\x00\x00\x00\x00\xf8\xff'
>>> struct.pack('d', float('nan'))
b'\x00\x00\x00\x00\x00\x00\xf8\x7f'
>>> struct.pack('d', float('-nan'))
b'\x00\x00\x00\x00\x00\x00\xf8\xff'
msg233594 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2015-01-07 18:27
By tweaking the grammar we can have math.-inf.
msg233595 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 18:27
> but 0*inf and inf-inf return values with the same representation as float('-nan'), not float('nan')

Right: that's because Intel's "default" NaN (i.e., the float it produces as a result of any invalid operation) has its sign bit set.
msg233596 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 18:27
> By tweaking the grammar we can have math.-inf.

AAAARRGH!
msg233598 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 18:42
Here's a patch.
msg233601 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 19:27
Thanks for the review comments.  Here's an updated patch taking the review comments into account (and fixing the spelling of PY_NAN, which should have been Py_NAN).
msg233602 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-07 20:02
One more patch, fixing a misplaced period.
msg233607 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-07 22:09
"History, perhaps?  In any case, the sign of a NaN isn't useful information in the same way that the sign of an infinity is.  The IEEE 754 standard explicitly refuses to attach any meaning to the sign bit of a NaN.  And if we were aiming for a full and faithful representation of NaNs, we'd want to output the payload, too (which is just about as meaningless / meaningful as the sign bit)."

So I understand that adding a math.neg_nan would be useless. As adding one constant per possible "NaN" value :-) If I recall correctly the IEEE 754 standard, there is not single NaN value, but a range of NaN.

"Two kinds of NaN: a quiet NaN (qNaN) and a signaling NaN (sNaN). A NaN may carry a payload that is intended for diagnostic information indicating the source of the NaN. The sign of a NaN has no meaning, but it may be predictable in some circumstances." says Wikipedia.

Well, the current definition of math.nan makes sense, it's the same value than float("nan").

Note: On python-ideas, I asked if math.nan and math.inf should be singleton (as it was requested for float("0.0") in issue #4024). The answer is no.
msg233630 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-08 08:58
I have an updated patch taking into account the most recent review comments (for which thanks!), but it's at home; I'll upload it this evening (UTC+00:00).
msg233674 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-08 17:40
New patch, addressing review comments.
msg233692 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-08 22:41
Except of my small suggestion on the doc (see the review), math_inf_nan4.patch looks good to me.
msg233720 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-01-09 07:16
May be make math.inf and math.nan special objects so that for all x (except inf and nan):

x < math.inf
x > -math.inf
not (x < math.nan)
not (x > math.nan)
msg233737 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-09 08:57
2015-01-09 8:16 GMT+01:00 Serhiy Storchaka <report@bugs.python.org>:
> May be make math.inf and math.nan special objects so that for all x (except inf and nan):

What do you mean? Implement a subtype of float and override some methods?

> x < math.inf
> x > -math.inf

It's already the case for int, float and decimal.Decimal.

> not (x < math.nan)
> not (x > math.nan)

Comparison to nan always return False.

I would be better to raise an error when nan is compared to other numbers (I mean operations like a>b, not a==b), but Python was not designed like that (nor the IEEE 754?).

>>> sorted((nan, 1, nan, 2))
[nan, 1, nan, 2]

Sorting with NaN is a common issue :-/ See for example:
https://stackoverflow.com/questions/4240050/python-sort-function-breaks-in-the-presence-of-nan

Anyway, changing NaN behaviour is out of the scope of this issue!
msg233842 - (view) Author: Roundup Robot (python-dev) Date: 2015-01-11 11:55
New changeset cf4bf577749c by Mark Dickinson in branch 'default':
Issue #23185: add math.inf and math.nan constants.
https://hg.python.org/cpython/rev/cf4bf577749c
msg233843 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-11 11:56
Committed.  Thanks for all the helpful review comments!
msg233894 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2015-01-12 22:41
Should inf and nan be added to cmath too?  It has e and pi and isnan() and isinf()...

Also complex(0, math.nan) a value that is printed as "nanj" and complex("nanj") parses and returns such a value, so the point could be made that there should be a constant named complex.nanj.
msg233912 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2015-01-13 09:07
Guido van Rossum added the comment:
> Should inf and nan be added to cmath too?  It has e and pi and isnan() and isinf()...
>
> Also complex(0, math.nan) a value that is printed as "nanj" and complex("nanj") parses and returns such a value, so the point could be made that there should be a constant named complex.nanj.

Since it's a different module and we are talking about more and
different constants, I suggest to open a new issue.
msg233917 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-13 09:35
> Should inf and nan be added to cmath too?

Hmm; probably, yes.  I'll open an issue.

> so the point could be made that there should be a constant named complex.nanj

Yes, I suppose it could (along with infj, of course).  I don't like it much, and I suspect it would get almost no uses.  complex(0, inf) and complex(0, nan) seem like good enough spellings.
msg233920 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-01-13 09:37
Opened issue #23229.
History
Date User Action Args
2015-01-13 09:37:33mark.dickinsonsetmessages: + msg233920
2015-01-13 09:35:45mark.dickinsonsetmessages: + msg233917
2015-01-13 09:07:12hayposetmessages: + msg233912
2015-01-12 22:41:31gvanrossumsetnosy: + gvanrossum
messages: + msg233894
2015-01-11 11:56:31mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg233843

stage: needs patch -> resolved
2015-01-11 11:55:40python-devsetnosy: + python-dev
messages: + msg233842
2015-01-11 00:05:27ethan.furmansetnosy: + lemburg, rhettinger, eric.smith, stutzbach
2015-01-09 08:57:16hayposetmessages: + msg233737
2015-01-09 07:16:11serhiy.storchakasetmessages: + msg233720
2015-01-08 22:41:09hayposetmessages: + msg233692
2015-01-08 17:40:20mark.dickinsonsetfiles: + math_inf_nan4.patch

messages: + msg233674
2015-01-08 08:58:49mark.dickinsonsetmessages: + msg233630
2015-01-07 22:09:05hayposetmessages: + msg233607
2015-01-07 20:02:37mark.dickinsonsetfiles: + math_inf_nan3.patch

messages: + msg233602
2015-01-07 19:27:27mark.dickinsonsetfiles: + math_inf_nan2.patch

messages: + msg233601
2015-01-07 18:42:47mark.dickinsonsetfiles: + math_inf_nan.patch
assignee: mark.dickinson
messages: + msg233598

keywords: + patch
2015-01-07 18:27:45mark.dickinsonsetmessages: + msg233596
2015-01-07 18:27:27mark.dickinsonsetmessages: + msg233595
2015-01-07 18:27:06pitrousetnosy: + pitrou
messages: + msg233594
2015-01-07 18:25:27serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg233593
2015-01-07 18:18:08mark.dickinsonsetmessages: + msg233591
2015-01-07 17:25:57hayposetmessages: + msg233588
2015-01-07 17:07:00mark.dickinsonsetmessages: + msg233585
2015-01-07 16:58:50mark.dickinsonsetmessages: + msg233583
2015-01-07 16:57:53mark.dickinsonsetnosy: + mark.dickinson
messages: + msg233582
2015-01-07 16:55:21berker.peksagsetcomponents: + Library (Lib)
stage: needs patch
2015-01-07 16:42:15hayposetnosy: + haypo
messages: + msg233581
2015-01-07 16:33:44ethan.furmancreate