This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: 1==float('nan')
Type: Stage:
Components: Interpreter Core Versions: Python 2.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mwh Nosy List: arman0, eggert, lemburg, mwh, tim.peters
Priority: low Keywords:

Created on 2004-02-17 21:47 by arman0, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
richcmp-for-floats.diff mwh, 2004-02-19 19:01 mwh's patch #1
Messages (14)
msg20024 - (view) Author: Arman Bostani (arman0) Date: 2004-02-17 21:47
In python 2.3.1 on Linux/x86, X==float('nan') evaluates
to True given any number value for X.  I admit to not
knowing much about IEEE arithmetic.  But, is this Kosher?

-arman
 
msg20025 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-02-18 17:46
Logged In: YES 
user_id=6656

Man, that's strange (I see the same behaviour on my redhat 9
box with 2.3.3).

I don't have time to dig right now, but if you want to,
playing with equivalent C programs would be a very good start.
msg20026 - (view) Author: Paul Eggert (eggert) Date: 2004-02-19 06:39
Logged In: YES 
user_id=17848

There's an easy fix.  Python has long mishandled IEEE
special values
(see bugs 445484 and 737648, and see PEP 42 "Non-accidental
IEEE-754
support" as well as PEP 754), and solving the problem in
general will
be some work.  However, solving the NaN-comparison problem
is easy:
simply sort NaNs consistently before all numbers.  This is
the same
algorithm that GNU "sort -g" has used for quite some time.

===================================================================
RCS file: Include/pyport.h,v
retrieving revision 2.3.3.0
diff -pu -r2.3.3.0 Include/pyport.h
--- Include/pyport.h	2003/09/30 14:56:50	2.3.3.0
+++ Include/pyport.h	2004/02/19 06:06:10
@@ -221,6 +221,13 @@ extern "C" {
 #define Py_SAFE_DOWNCAST(VALUE, WIDE, NARROW) (NARROW)(VALUE)
 #endif
 
+/* Py_IS_NAN(X)
+ * Return 1 if float or double arg is not a number (NaN),
else 0.
+ * Caution:
+ *    X is evaluated more than once.
+ */
+#define Py_IS_NAN(X) ((X) != (X))
+
 /* Py_IS_INFINITY(X)
  * Return 1 if float or double arg is an infinity, else 0.
  * Caution:
===================================================================
RCS file: Objects/floatobject.c,v
retrieving revision 2.3.3.0
diff -pu -r2.3.3.0 Objects/floatobject.c
--- Objects/floatobject.c	2003/06/28 20:04:24	2.3.3.0
+++ Objects/floatobject.c	2004/02/19 06:03:37
@@ -367,7 +367,21 @@ float_compare(PyFloatObject *v, PyFloatO
 {
 	double i = v->ob_fval;
 	double j = w->ob_fval;
-	return (i < j) ? -1 : (i > j) ? 1 : 0;
+	int c;
+	/* Because of NaNs IEEE arithmetic is not a total order.
+	 * Python works better with total orders, so use the same
+	 * total order that GNU sort does: NaNs sort before numbers,
+	 * and NaNs are sorted by internal bit-pattern.
+	 */
+	return ((i < j) ? -1
+		: (i > j) ? 1
+		: (i == j) ? 0
+		: !Py_IS_NAN(j) ? -1 /* i is NaN, j is not */
+		: !Py_IS_NAN(i) ? 1  /* j is NAN, i is not */
+		: /* i and j are both NaNs; compare them bitwise */
+		  ((c = memcmp(&v->ob_fval, &w->ob_fval, sizeof(v->ob_fval)))
+		   < 0) ? -1
+		: (c > 0));
 }
 
 static long
msg20027 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2004-02-19 17:34
Logged In: YES 
user_id=31435

Sorry, the patch can't work:  C89 says nothing about what 
happens when NaNs are compared, and it's in fact not the 
case across platforms that "x != x" returns 1 when x is a NaN.

The most important counter-example for Python is that the 
following prints

1.#INF
-1.#IND
1

under MSVC 6.

#include <stdio.h>

void main()
{
    double x = 1e300 * 1e300;
    printf("%g\n", x);
    x -= x;
    printf("%g\n", x);
    printf("%d\n", x == x);
}
msg20028 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-02-19 19:01
Logged In: YES 
user_id=6656

Consider the attached.  It implements rich comparisons for
floats, which makes the behaviour on linux at least very
much less silly.  All tests except for the
considered-hopeless-on-this box audio tests and the new
test_tcl pass.  Even test_bsddb passed! Twice!  (I'm not
sure I can claim credit for this :-)
msg20029 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-02-19 19:39
Logged In: YES 
user_id=6656

Tim said yes.

Objects/floatobject.c revision 2.127
Misc/NEWS revision 1.936
msg20030 - (view) Author: Paul Eggert (eggert) Date: 2004-02-19 19:55
Logged In: YES 
user_id=17848

C99 does say what happens when NaNs are
compared.  See, for example, section 7.12.14,
which says that NaNs are unordered with respect
to other floating point values, and section
7.12.14.1, which says that x>y is equivalent to
isgreater(x,y) and therefore returns false if
either x or y is a NaN (unless the "invalid"
floating-point exception occurs, which isn't
happening here and even if it did happen the
original code would be broken anyway).

Your argument is that any code that uses NaNs
returns garbage and so we should give up.  But
then you have no logical reason to either favor
or oppose this patch, as it doesn't alter
Python's behavior on non-NaN arguments.

It's clear the the patch does fix the comparison
problem on most existing platforms.  It actually
works on Solaris sparc, and I'm sure that it
will work on a wide variety of other important
platforms.

The fact that you've found an incompatibility
between MSVC 6 and C99 on one example does not
mean that the proposed patch won't work: it
merely means that MSVC 6 won't work on your
example (which is deliberately constructed to
confuse pre-C99 compilers).  The proposed patch
doesn't contain confusing code like that, so it
should work even on pre-C99 compilers.

I'm not speaking just from theory here.  Similar
code has been in GNU sort since 1999, and it
works fine in practice.

Since the patch fixes the bug on many (perhaps
all) real platforms, and it can't introduce a
bug on any platform, the patch is a win.

Michael Hudson said "Consider the attached."
but nothing was attached.  I gather from his
comments that he added rich comparisons for
floats.  This will work if the code never
invokes the 2.3.3 float_compare -- so can I take
it that he removed float_compare?
msg20031 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2004-02-19 20:22
Logged In: YES 
user_id=31435

I specifically said C89.  Python doesn't require C99 support, 
and can't yet, because the majority of compilers used to 
compile Python aren't yet C99 compilers.  Even in C99, that x!
=x is 1 for NaN is optional behavior, depending on whether the 
implementation chooses to define __STDC_IEC_559__.

The point of my example was solely the result of x==x.  It 
doesn't matter how a NaN got put into x, MSVC 6 generates 
code that returns true regardless.  It wasn't constructed to 
confuse anything -- to the contrary, it shows about the most 
straightforward way to *get* a NaN under MSVC 6, short of 
playing tricks with bit representation.

Michael's patch is a more efficient approach regardless.  If 
Python is to grow an isnan macro (which his patch doesn't 
need), then it shoiuld grow an isnan() macro that works on all 
platforms with 754 HW (else it appears to promise thing it 
can't deliver).

Going on to call NaN "less than" everything else goes beyond 
anything C99 says, and is a debate of its own.

No, he didn't remove float_compare().  He could, but only cmp
() will invoke it now.
msg20032 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-02-20 11:09
Logged In: YES 
user_id=6656

Paul, I appreciate your attention to detail, but your last
comment is essentially a rant that puts words into Tim's and
my mouth.

My patch (which is what got checked in) *is* attached to
this report, or you can see it on the python-checkins list.
 There was some ancillary discussion on python-dev (under
the admittedly inscrutable title of "Weekly Python Bug/Patch
Summary") about this, where it was uncovered that MSVC 7.1
(which is what Python 2.4 will be compiled with) adheres to
the C99 rules for comparisons involving NaNs.  So with my
patch, on all major platforms, float comparisons are likely
to follow C99 rules in Python 2.4.  We're still not going to
promise anything, though.

I admit to not doing much research on whether I should/could
remove float_compare.  It seems that perhaps I should.
msg20033 - (view) Author: Paul Eggert (eggert) Date: 2004-02-20 21:10
Logged In: YES 
user_id=17848

Sorry, I didn't intend a rant, and I apologize if any
offense was given.  I'll try to use a milder tone
henceforth.

To answer Michael's question: yes, now that your patch is
installed, float_compare is incompatible with rich
comparison; for example cmp(NaN,1.0) returns 0 even though
NaN==1.0 returns False.

Another problem: cmp() initially uses pointer comparison to
compare objects, so if x is a NaN then cmp(x,x) returns 0.

I don't think that removing float_compare will fix the
problem, as it would simply cause cmp to return an incorrect
value when given a NaN.

If you prefer IEEE semantics to sorting NaNs before numbers,
another possibility is for cmp to raise an exception when
given a NaN, since in that case cmp can't return any value
that is compatible with IEEE floating point.

Here's a patch to implement this proposal.  It does not rely
on isnan or a substitute, so I hope this answers Tim's
objection.  Note that PyObject_Compare needs a minor change,
too; since user-defined types may have the same problem that
Float does, it's simplest just to omit the optimization
there.

===================================================================
RCS file: Objects/floatobject.c,v
retrieving revision 2.3.3.2
retrieving revision 2.3.3.3
diff -pu -r2.3.3.2 -r2.3.3.3
--- Objects/floatobject.c	2004/02/20 19:45:01	2.3.3.2
+++ Objects/floatobject.c	2004/02/20 21:04:20	2.3.3.3
@@ -367,7 +367,15 @@ float_compare(PyFloatObject *v, PyFloatO
 {
 	double i = v->ob_fval;
 	double j = w->ob_fval;
-	return (i < j) ? -1 : (i > j) ? 1 : 0;
+	if (i < j)
+		return -1;
+	if (i > j)
+		return 1;
+	if (i == j)
+		return 0;
+	PyErr_SetString(PyExc_FloatingPointError,
+			"cannot compare NaNs using cmp");
+	return -2;
 }
 
 static PyObject*
===================================================================
RCS file: Objects/object.c,v
retrieving revision 2.3.3.0
retrieving revision 2.3.3.1
diff -pu -r2.3.3.0 -r2.3.3.1
--- Objects/object.c	2003/04/18 00:45:58	2.3.3.0
+++ Objects/object.c	2004/02/20 21:04:20	2.3.3.1
@@ -877,8 +877,6 @@ PyObject_Compare(PyObject *v, PyObject *
 		PyErr_BadInternalCall();
 		return -1;
 	}
-	if (v == w)
-		return 0;
 	vtp = v->ob_type;
 	compare_nesting++;
 	if (compare_nesting > NESTING_LIMIT &&
msg20034 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-02-26 12:51
Logged In: YES 
user_id=6656

Hmm.

I agree the situation wrt. float_compare as in CVS is a
problem.  Not sure I have the courage to have float_compare
starting to set exceptions, though.  Just removing
float_compare seems to be a conservative change (yes, it can
give silly answers, but this is nothing new).

I certainly don't have the courage to change
PyObject_Compare unless someone like Tim or Guido says I
should :-)
msg20035 - (view) Author: Marc-Andre Lemburg (lemburg) * (Python committer) Date: 2004-02-26 13:09
Logged In: YES 
user_id=38388

Michael,

please be more careful about these changes: we certainly do
not want to lose a significant number of cycles just to
remedy this rather uncommon error situation.

The cleanest solution seems to be to just raise an exception
in the compare logic (which is perfectly OK). Removing the
tp_compare slot will result in PyObject_Compare() to take a
different path and is likely going to cause a performance hit.

Whatever you chose to do about this, please make sure that
we don't lose performance in float compares. pybench has a
special test for float comparisons which you could use as
basis for this.

Thanks.
msg20036 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-03-01 13:48
Logged In: YES 
user_id=6656

If I'm reading the pybench tea leaves correctly, you don't
have anything to worry about here.  CompareFloatsIntegers is
significantly faster (~30%), everything else is in the noise.

Removing float_compare speeds up CompareFloatsIntegers even
more (~40%, now) and has little impact on anything else.

I don't believe PyObject_Compare() is a performance hotspot
when the types involved implement rich comparisons.
msg20037 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-05-26 17:36
Logged In: YES 
user_id=6656

I removed float_compare as discussed below.  Closing.

Objects/floatobject.c revision 2.130
History
Date User Action Args
2022-04-11 14:56:02adminsetgithub: 39945
2004-02-17 21:47:00arman0create