Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…up_id=5470.

This was a misleading bug -- the true "bug" was that hash(x) gave an error
return when x is an infinity.  Fixed that.  Added new Py_IS_INFINITY macro to
pyport.h.  Rearranged code to reduce growing duplication in hashing of float and
complex numbers, pushing Trent's earlier stab at that to a logical conclusion.
Fixed exceedingly rare bug where hashing of floats could return -1 even if there
wasn't an error (didn't waste time trying to construct a test case, it was simply
obvious from the code that it *could* happen).  Improved complex hash so that
hash(complex(x, y)) doesn't systematically equal hash(complex(y, x)) anymore.
  • Loading branch information
tim-one committed Aug 15, 2000
1 parent 7aced17 commit 39dce29
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 101 deletions.
10 changes: 9 additions & 1 deletion Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ extern "C" {
#define Py_ARITHMETIC_RIGHT_SHIFT(TYPE, I, J) ((I) >> (J))
#endif

/* Py_FORCE_EXPANSION
/* Py_FORCE_EXPANSION(X)
* "Simply" returns its argument. However, macro expansions within the
* argument are evaluated. This unfortunate trickery is needed to get
* token-pasting to work as desired in some cases.
Expand All @@ -123,6 +123,14 @@ extern "C" {
#define Py_SAFE_DOWNCAST(VALUE, WIDE, NARROW) (NARROW)(VALUE)
#endif

/* Py_IS_INFINITY(X)
* Return 1 if float or double arg is an infinity, else 0.
* Caution:
* X is evaluated more than once.
* This implementation may set the underflow flag if |X| is very small;
* it really can't be implemented correctly (& easily) before C99.
*/
#define Py_IS_INFINITY(X) ((X) && (X)*0.5 == (X))

/**************************************************************************
Prototypes that are missing from the standard include files on some systems
Expand Down
63 changes: 17 additions & 46 deletions Objects/complexobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,52 +242,23 @@ complex_compare(PyComplexObject *v, PyComplexObject *w)
static long
complex_hash(PyComplexObject *v)
{
double intpart, fractpart;
long x;
/* This is designed so that Python numbers with the same
value hash to the same value, otherwise comparisons
of mapping keys will turn out weird */

#ifdef MPW /* MPW C modf expects pointer to extended as second argument */
{
extended e;
fractpart = modf(v->cval.real, &e);
intpart = e;
}
#else
fractpart = modf(v->cval.real, &intpart);
#endif

if (fractpart == 0.0 && v->cval.imag == 0.0) {
if (intpart > LONG_MAX || -intpart > LONG_MAX) {
/* Convert to long int and use its hash... */
PyObject *w = PyLong_FromDouble(v->cval.real);
if (w == NULL)
return -1;
x = PyObject_Hash(w);
Py_DECREF(w);
return x;
}
x = (long)intpart;
}
else {
x = _Py_HashDouble(v->cval.real);
if (x == -1)
return -1;

if (v->cval.imag != 0.0) { /* Hash the imaginary part */
/* XXX Note that this hashes complex(x, y)
to the same value as complex(y, x).
Still better than it used to be :-) */
long y = _Py_HashDouble(v->cval.imag);
if (y == -1)
return -1;
x += y;
}
}
if (x == -1)
x = -2;
return x;
long hashreal, hashimag, combined;
hashreal = _Py_HashDouble(v->cval.real);
if (hashreal == -1)
return -1;
hashimag = _Py_HashDouble(v->cval.imag);
if (hashimag == -1)
return -1;
/* Note: if the imaginary part is 0, hashimag is 0 now,
* so the following returns hashreal unchanged. This is
* important because numbers of different types that
* compare equal must have the same hash value, so that
* hash(x + 0*j) must equal hash(x).
*/
combined = hashreal + 1000003 * hashimag;
if (combined == -1)
combined = -2;
return combined;
}

static PyObject *
Expand Down
39 changes: 1 addition & 38 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,44 +326,7 @@ float_compare(PyFloatObject *v, PyFloatObject *w)
static long
float_hash(PyFloatObject *v)
{
double intpart, fractpart;
long x;
/* This is designed so that Python numbers with the same
value hash to the same value, otherwise comparisons
of mapping keys will turn out weird */

#ifdef MPW /* MPW C modf expects pointer to extended as second argument */
{
extended e;
fractpart = modf(v->ob_fval, &e);
intpart = e;
}
#else
fractpart = modf(v->ob_fval, &intpart);
#endif

if (fractpart == 0.0) {
if (intpart > LONG_MAX || -intpart > LONG_MAX) {
/* Convert to long int and use its hash... */
PyObject *w = PyLong_FromDouble(v->ob_fval);
if (w == NULL)
return -1;
x = PyObject_Hash(w);
Py_DECREF(w);
return x;
}
x = (long)intpart;
}
else {
/* Note -- if you change this code, also change the copy
in complexobject.c */
x = _Py_HashDouble(v->ob_fval);
if (x == -1)
return -1;
}
if (x == -1)
x = -2;
return x;
return _Py_HashDouble(v->ob_fval);
}

static PyObject *
Expand Down
2 changes: 1 addition & 1 deletion Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ PyLong_FromDouble(double dval)
double frac;
int i, ndig, expo, neg;
neg = 0;
if (dval && dval * 0.5 == dval) {
if (Py_IS_INFINITY(dval)) {
PyErr_SetString(PyExc_OverflowError,
"cannot convert float infinity to long");
return NULL;
Expand Down
70 changes: 55 additions & 15 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -532,25 +532,65 @@ PyObject_Compare(PyObject *v, PyObject *w)
long
_Py_HashDouble(double v)
{
/* Use frexp to get at the bits in the double.
double intpart, fractpart;
int expo;
long hipart;
long x; /* the final hash value */
/* This is designed so that Python numbers of different types
* that compare equal hash to the same value; otherwise comparisons
* of mapping keys will turn out weird.
*/

#ifdef MPW /* MPW C modf expects pointer to extended as second argument */
{
extended e;
fractpart = modf(v, &e);
intpart = e;
}
#else
fractpart = modf(v, &intpart);
#endif
if (fractpart == 0.0) {
/* This must return the same hash as an equal int or long. */
if (intpart > LONG_MAX || -intpart > LONG_MAX) {
/* Convert to long and use its hash. */
PyObject *plong; /* converted to Python long */
if (Py_IS_INFINITY(intpart))
/* can't convert to long int -- arbitrary */
v = v < 0 ? -271828.0 : 314159.0;
plong = PyLong_FromDouble(v);
if (plong == NULL)
return -1;
x = PyObject_Hash(plong);
Py_DECREF(plong);
return x;
}
/* Fits in a C long == a Python int, so is its own hash. */
x = (long)intpart;
if (x == -1)
x = -2;
return x;
}
/* The fractional part is non-zero, so we don't have to worry about
* making this match the hash of some other type.
* Use frexp to get at the bits in the double.
* Since the VAX D double format has 56 mantissa bits, which is the
* most of any double format in use, each of these parts may have as
* many as (but no more than) 56 significant bits.
* So, assuming sizeof(long) >= 4, each part can be broken into two longs;
* frexp and multiplication are used to do that.
* Also, since the Cray double format has 15 exponent bits, which is the
* most of any double format in use, shifting the exponent field left by
* 15 won't overflow a long (again assuming sizeof(long) >= 4).
* So, assuming sizeof(long) >= 4, each part can be broken into two
* longs; frexp and multiplication are used to do that.
* Also, since the Cray double format has 15 exponent bits, which is
* the most of any double format in use, shifting the exponent field
* left by 15 won't overflow a long (again assuming sizeof(long) >= 4).
*/
int expo;
long hipart;

v = frexp(v, &expo);
v = v * 2147483648.0; /* 2**31 */
hipart = (long)v; /* Take the top 32 bits */
v = (v - (double)hipart) * 2147483648.0; /* Get the next 32 bits */

return hipart + (long)v + (expo << 15); /* Combine everything */
v = frexp(v, &expo);
v *= 2147483648.0; /* 2**31 */
hipart = (long)v; /* take the top 32 bits */
v = (v - (double)hipart) * 2147483648.0; /* get the next 32 bits */
x = hipart + (long)v + (expo << 15);
if (x == -1)
x = -2;
return x;
}

long
Expand Down

0 comments on commit 39dce29

Please sign in to comment.