Issue899109
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.
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) | 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) * | 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) | 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) | 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) * | 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) | 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) | 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) * | 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) | 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) | 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:02 | admin | set | github: 39945 |
2004-02-17 21:47:00 | arman0 | create |