Index: Python/sysmodule.c =================================================================== --- Python/sysmodule.c (revision 79745) +++ Python/sysmodule.c (working copy) @@ -570,6 +570,57 @@ return Py_None; } +static PyTypeObject Hash_InfoType; + +PyDoc_STRVAR(hash_info_doc, +"hash_info\n\ +\n\ +A struct sequence providing parameters used for computing\n\ +numeric hashes. The attributes are read only."); + +static PyStructSequence_Field hash_info_fields[] = { + {"bits", "hash width"}, + {"modulus", "prime number giving the modulus on which the hash " + "function is based"}, + {"inf", "value to be used for hash of a positive or unsigned infinity"}, + {"ninf", "value to be used for hash of a negative infinity"}, + {"nan", "value to be used for hash of a nan"}, + {NULL, NULL} +}; + +static PyStructSequence_Desc hash_info_desc = { + "sys.hash_info", + hash_info_doc, + hash_info_fields, + 5, +}; + +PyObject * +get_hash_info(void) +{ + PyObject *hash_info; + int field = 0; + hash_info = PyStructSequence_New(&Hash_InfoType); + if (hash_info == NULL) + return NULL; + PyStructSequence_SET_ITEM(hash_info, field++, + PyLong_FromLong(_PyHASH_BITS)); + PyStructSequence_SET_ITEM(hash_info, field++, + PyLong_FromLong(_PyHASH_MASK)); + PyStructSequence_SET_ITEM(hash_info, field++, + PyLong_FromLong(_PyHASH_INF)); + PyStructSequence_SET_ITEM(hash_info, field++, + PyLong_FromLong(_PyHASH_NINF)); + PyStructSequence_SET_ITEM(hash_info, field++, + PyLong_FromLong(_PyHASH_NAN)); + if (PyErr_Occurred()) { + Py_CLEAR(hash_info); + return NULL; + } + return hash_info; +} + + PyDoc_STRVAR(setrecursionlimit_doc, "setrecursionlimit(n)\n\ \n\ @@ -1477,6 +1528,11 @@ PyFloat_GetInfo()); SET_SYS_FROM_STRING("int_info", PyLong_GetInfo()); + /* initialize hash_info */ + if (Hash_InfoType.tp_name == 0) + PyStructSequence_InitType(&Hash_InfoType, &hash_info_desc); + SET_SYS_FROM_STRING("hash_info", + get_hash_info()); SET_SYS_FROM_STRING("maxunicode", PyLong_FromLong(PyUnicode_GetMax())); SET_SYS_FROM_STRING("builtin_module_names", Index: Include/pyport.h =================================================================== --- Include/pyport.h (revision 79745) +++ Include/pyport.h (working copy) @@ -126,6 +126,20 @@ #endif #endif +/* Parameters used for the numeric hash implementation. See notes for + _PyHash_Double in Objects/object.c. Numeric hashes are based on + reduction modulo the prime 2**_PyHASH_BITS - 1. */ + +#if SIZEOF_LONG >= 8 +#define _PyHASH_BITS 61 +#else +#define _PyHASH_BITS 31 +#endif +#define _PyHASH_MASK ((1UL << _PyHASH_BITS) - 1) +#define _PyHASH_INF 314159 +#define _PyHASH_NINF -271828 +#define _PyHASH_NAN 15858 + /* uintptr_t is the C9X name for an unsigned integral type such that a * legitimate void* can be cast to uintptr_t and then back to void* again * without loss of information. Similarly for intptr_t, wrt a signed Index: Objects/object.c =================================================================== --- Objects/object.c (revision 79745) +++ Objects/object.c (working copy) @@ -644,60 +644,100 @@ All the utility functions (_Py_Hash*()) return "-1" to signify an error. */ +/* For numeric types, the hash of a number x is based on the reduction + of x modulo the prime P = 2**_PyHASH_BITS - 1. It's designed so that + hash(x) == hash(y) whenever x and y are numerically equal, even if + x and y have different types. + + A quick summary of the hashing strategy: + + (1) First define the 'reduction of x modulo P' for any rational + number x; this is a standard extension of the usual notion of + reduction modulo P for integers. If x == p/q (written in lowest + terms), the reduction is interpreted as the reduction of p times + the inverse of the reduction of q, all modulo P; if q is exactly + divisible by P then define the reduction to be infinity. So we've + got a well-defined map + + reduce : { rational numbers } -> { 0, 1, 2, ..., P-1, infinity }. + + (2) Now for a rational number x, define hash(x) by: + + reduce(x) if x >= 0 + -reduce(-x) if x < 0 + + If the result of the reduction is infinity (this is impossible for + integers, floats and Decimals) then use the predefined hash value + _PyHASH_INF instead. _PyHASH_INF, _PyHASH_NINF and _PyHASH_NAN are also + used for the hashes of float and Decimal infinities and nans. + + A selling point for the above strategy is that it makes it possible + to compute hashes of decimal and binary floating-point numbers + efficiently, even if the exponent of the binary or decimal number + is large. The key point is that + + reduce(x * y) == reduce(x) * reduce(y) (modulo _PyHASH_MASK) + + provided that {reduce(x), reduce(y)} != {0, infinity}. The reduction of a + binary or decimal float is never infinity, since the denominator is a power + of 2 (for binary) or a divisor of a power of 10 (for decimal). So we have, + for nonnegative x, + + reduce(x * 2**e) == reduce(x) * reduce(2**e) % _PyHASH_MASK + + reduce(x * 10**e) == reduce(x) * reduce(10**e) % _PyHASH_MASK + + and reduce(10**e) can be computed efficiently by the usual modular + exponentiation algorithm. For reduce(2**e) it's even better: since + P is of the form 2**n-1, reduce(2**e) is 2**(e mod n), and multiplication + by 2**(e mod n) modulo 2**n-1 just amounts to a rotation of bits. + + */ + long _Py_HashDouble(double v) { - 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. - */ + int e, sign; + double m; + unsigned long x, y; - fractpart = modf(v, &intpart); - if (fractpart == 0.0) { - /* This must return the same hash as an equal int or long. */ - if (intpart > LONG_MAX/2 || -intpart > LONG_MAX/2) { - /* 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; + if (!Py_IS_FINITE(v)) { + if (Py_IS_INFINITY(v)) + return v > 0 ? _PyHASH_INF : _PyHASH_NINF; + else + return _PyHASH_NAN; } - /* 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). - */ - 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; + + m = frexp(v, &e); + + sign = 1; + if (m < 0) { + sign = -1; + m = -m; + } + + /* process 28 bits at a time; this should work well both for binary + and hexadecimal floating point. */ + x = 0; + while (m) { + x = ((x << 28) & _PyHASH_MASK) | x >> (_PyHASH_BITS - 28); + m *= 268435456.0; /* 2**28 */ + e -= 28; + y = (unsigned long)m; /* pull out integer part */ + m -= y; + x += y; + if (x >= _PyHASH_MASK) + x -= _PyHASH_MASK; + } + + /* adjust for the exponent; first reduce it modulo _PyHASH_BITS */ + e = e >= 0 ? e % _PyHASH_BITS : _PyHASH_BITS-1-((-1-e) % _PyHASH_BITS); + x = ((x << e) & _PyHASH_MASK) | x >> (_PyHASH_BITS - e); + + x = x * sign; + if (x == (unsigned long)-1) + x = (unsigned long)-2; + return (long)x; } long Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 79745) +++ Objects/typeobject.c (working copy) @@ -4911,30 +4911,44 @@ PyObject *func, *res; static PyObject *hash_str; long h; + int overflow; func = lookup_method(self, "__hash__", &hash_str); - if (func == Py_None) { + if (func == Py_None) { Py_DECREF(func); func = NULL; } if (func == NULL) { return PyObject_HashNotImplemented(self); - } + } res = PyEval_CallObject(func, NULL); Py_DECREF(func); if (res == NULL) return -1; - if (PyLong_Check(res)) + + if (!PyLong_Check(res)) { + PyErr_SetString(PyExc_TypeError, + "__hash__ method should return an integer"); + return -1; + } + /* Transform the PyLong `res` to a C long `h`. For an existing + hashable Python object x, hash(x) will always lie within the range + of a C long. Therefore our transformation must preserve values + that already lie within this range, to ensure that if x.__hash__() + returns hash(y) then hash(x) == hash(y). */ + h = PyLong_AsLongAndOverflow(res, &overflow); + if (overflow) + /* res was not within the range of a C long, so we're free to + use any sufficiently bit-mixing transformation; + long.__hash__ will do nicely. */ h = PyLong_Type.tp_hash(res); - else - h = PyLong_AsLong(res); Py_DECREF(res); - if (h == -1 && !PyErr_Occurred()) - h = -2; - return h; + if (h == -1 && !PyErr_Occurred()) + h = -2; + return h; } static PyObject * Index: Objects/longobject.c =================================================================== --- Objects/longobject.c (revision 79745) +++ Objects/longobject.c (working copy) @@ -2572,18 +2572,36 @@ sign = -1; i = -(i); } - /* The following loop produces a C unsigned long x such that x is - congruent to the absolute value of v modulo ULONG_MAX. The - resulting x is nonzero if and only if v is. */ while (--i >= 0) { - /* Force a native long #-bits (32 or 64) circular shift */ - x = (x >> (8*SIZEOF_LONG-PyLong_SHIFT)) | (x << PyLong_SHIFT); + /* Here x is a quantity in the range [0, _PyHASH_MASK); we + want to compute x * 2**PyLong_SHIFT + v->ob_digit[i] modulo + _PyHASH_MASK. + + The computation of x * 2**PyLong_SHIFT % _PyHASH_MASK + amounts to a rotation of the bits of x. To see this, write + + x * 2**PyLong_SHIFT = y * 2**_PyHASH_BITS + z + + where y = x >> (_PyHASH_BITS - PyLong_SHIFT) gives the top + PyLong_SHIFT bits of x (those that are shifted out of the + original _PyHASH_BITS bits, and z = (x << PyLong_SHIFT) & + _PyHASH_MASK gives the bottom _PyHASH_BITS - PyLong_SHIFT + bits of x, shifted up. Then since 2**_PyHASH_BITS is + congruent to 1 modulo _PyHASH_MASK, y*2**_PyHASH_BITS is + congruent to y modulo _PyHASH_MASK. So + + x * 2**PyLong_SHIFT = y + z (mod _PyHASH_MASK). + + The right-hand side is just the result of rotating the + _PyHASH_BITS bits of x left by PyLong_SHIFT places; since + not all _PyHASH_BITS bits of x are 1s, the same is true + after rotation, so 0 <= y+z < _PyHASH_MASK and y + z is the + reduction of x*2**PyLong_SHIFT modulo _PyHASH_MASK. */ + x = ((x << PyLong_SHIFT) & _PyHASH_MASK) | + (x >> (_PyHASH_BITS - PyLong_SHIFT)); x += v->ob_digit[i]; - /* If the addition above overflowed we compensate by - incrementing. This preserves the value modulo - ULONG_MAX. */ - if (x < v->ob_digit[i]) - x++; + if (x >= _PyHASH_MASK) + x -= _PyHASH_MASK; } x = x * sign; if (x == (unsigned long)-1) Index: Doc/library/decimal.rst =================================================================== --- Doc/library/decimal.rst (revision 79745) +++ Doc/library/decimal.rst (working copy) @@ -358,23 +358,17 @@ compared, sorted, and coerced to another type (such as :class:`float` or :class:`int`). - Decimal objects cannot generally be combined with floats in - arithmetic operations: an attempt to add a :class:`Decimal` to a - :class:`float`, for example, will raise a :exc:`TypeError`. - There's one exception to this rule: it's possible to use Python's - comparison operators to compare a :class:`float` instance ``x`` - with a :class:`Decimal` instance ``y``. Without this exception, - comparisons between :class:`Decimal` and :class:`float` instances - would follow the general rules for comparing objects of different - types described in the :ref:`expressions` section of the reference - manual, leading to confusing results. + Decimal objects cannot generally be combined with floats or + instances of :class:`fractions.Fraction` in arithmetic operations: + an attempt to add a :class:`Decimal` to a :class:`float`, for + example, will raise a :exc:`TypeError`. However, it is possible to + use Python's comparison operators to compare a :class:`Decimal` + instance ``x`` with another number ``y``. This avoids confusing results + when doing equality comparisons between numbers of different types. .. versionchanged:: 3.2 - A comparison between a :class:`float` instance ``x`` and a - :class:`Decimal` instance ``y`` now returns a result based on - the values of ``x`` and ``y``. In earlier versions ``x < y`` - returned the same (arbitrary) result for any :class:`Decimal` - instance ``x`` and any :class:`float` instance ``y``. + Mixed-type comparisons between :class:`Decimal` instances and + other numeric types are now fully supported. In addition to the standard numeric properties, decimal floating point objects also have a number of specialized methods: Index: Doc/library/sys.rst =================================================================== --- Doc/library/sys.rst (revision 79745) +++ Doc/library/sys.rst (working copy) @@ -442,6 +442,29 @@ Changed to a named tuple and added *service_pack_minor*, *service_pack_major*, *suite_mask*, and *product_type*. + +.. data:: hash_info + + A structseq giving parameters of the numeric hash implementation. For + more details about hashing of numeric types, see :ref:`numeric-hash`. + + +---------------------+--------------------------------------------------+ + | attribute | explanation | + +=====================+==================================================+ + | :const:`bits` | exponent of the Mersenne prime P | + +---------------------+--------------------------------------------------+ + | :const:`modulus` | modulus P, equal to ``2**hash_info.bits - 1`` | + +---------------------+--------------------------------------------------+ + | :const:`inf` | hash value returned for a positive infinity | + +---------------------+--------------------------------------------------+ + | :const:`ninf` | hash value returned for a negative infinity | + +---------------------+--------------------------------------------------+ + | :const:`nan` | hash value returned for a nan | + +---------------------+--------------------------------------------------+ + + .. versionadded:: 3.2 + + .. data:: hexversion The version number encoded as a single integer. This is guaranteed to increase Index: Doc/library/stdtypes.rst =================================================================== --- Doc/library/stdtypes.rst (revision 79745) +++ Doc/library/stdtypes.rst (working copy) @@ -595,6 +595,86 @@ '0x1.d380000000000p+11' +.. _numeric-hash: + +Hashing of numeric types +------------------------ + +If ``x`` and ``y`` are two numbers, possibly of different types, then it's a +requirement that ``hash(x) == hash(y)`` whenever ``x == y`` (see the +:meth:`__hash__` method documentation for more details). For ease of +implementation and efficiency across a variety of numeric types (:class:`int`, +:class:`float`, :class:`decimal.Decimal`, :class:`fractions.Fraction`) Python's +hash for numeric types is based on a single mathematical function that's defined +for any rational number, and hence applies to all instances of :class:`int` and +:class:`fraction.Fraction`, and all finite instances of :class:`float` and +:class:`decimal.Decimal`. Essentially, this function is given by reduction +modulo ``P`` for a fixed Mersenne prime ``P``; currently, the prime used is ``P += 2**31 - 1`` on 32-bit machines and ``P = 2**61 - 1`` on 64-bit machines. The +value of ``P`` is made available to Python as the :attr:`modulus` attribute of +:data:`sys.hash_info`. + +Here are the rules in detail: + + - If ``x = m / n`` is a nonnegative rational number and ``n`` is not divisible + by ``P``, define ``hash(x)`` as ``m * invmod(n, P) % P``, where ``invmod(n, + P)`` gives the inverse of ``n`` modulo ``P``. + + - If ``x = m / n`` is a negative rational number and ``n`` is not divisible by + ``P`` then define ``hash(x)`` as ``-hash(-x)``. + + - If ``x = m / n ``, and ``n`` is divisible by ``P`` (but ``m`` is not) then + ``n`` has no inverse modulo ``P`` and the rules above don't apply; in this + case ``hash(x)`` is defined to be the constant value ``sys.hash_info.inf``, + regardless of whether ``x`` is positive or negative. + + - If after following the above rules the resulting hash is ``-1``, it's + replaced with ``-2``. + + - The particular values ``sys.hash_info.inf``, ``sys.hash_info.ninf`` and + ``sys.hash_info.nan`` are used as hash values for positive infinity, negative + infinity, or nans (respectively). (All hashable nans have the same hash + value.) + +Here's some example Python code giving an implementation of hash for rational +numbers:: + + def invmod(n, P): + """Inverse of n modulo the prime P. + + Given a prime number P and an integer n that's not divisible by P, + return the unique integer x such that 0 < x < P and x * n % P == + 1. (Will silently return a wrong result if P is not prime or n is + divisible by P.) + + """ + # Fermat's Little Theorem: n**(P-1) is congruent to 1 modulo P, so + # n**(P-2) is congruent to the inverse of n modulo P. + return pow(n, P - 2, P) + + def hash_fraction(m, n): + """Compute the hash of a rational number m / n. + + Assumes m and n are integers, with n positive. + Equivalent to hash(fractions.Fraction(m, n)). + + """ + import sys + P = sys.hash_info.modulus + # Remove common factors of P. (Unnecessary if m and n already coprime.) + while m % P == n % P == 0: + m, n = m // P, n // P + + if n % P == 0: + return sys.hash_info.inf + hash_ = (abs(m) % P) * invmod(n, P) % P + if m < 0: + hash_ = -hash_ + if hash_ == -1: + hash = -2 + return hash_ + + .. _typeiter: Iterator Types Index: Lib/fractions.py =================================================================== --- Lib/fractions.py (revision 79745) +++ Lib/fractions.py (working copy) @@ -8,6 +8,7 @@ import numbers import operator import re +import sys __all__ = ['Fraction', 'gcd'] @@ -23,6 +24,12 @@ a, b = b, a%b return a +# Constants related to the hash implementation; hash(x) is based +# on the reduction of x modulo the prime _PyHASH_MASK. +_PyHASH_MASK = sys.hash_info.modulus +# Value to be used for rationals that reduce to infinity modulo +# _PyHASH_MASK. +_PyHASH_INF = sys.hash_info.inf _RATIONAL_FORMAT = re.compile(r""" \A\s* # optional whitespace at the start, then @@ -528,17 +535,16 @@ """ # XXX since this method is expensive, consider caching the result - if self._denominator == 1: - # Get integers right. - return hash(self._numerator) - # Expensive check, but definitely correct. - if self == float(self): - return hash(float(self)) - else: - # Use tuple's hash to avoid a high collision rate on - # simple fractions. - return hash((self._numerator, self._denominator)) + # dinv is the inverse of self._denominator modulo the prime + # _PyHASH_MASK, or 0 if self._denominator is divisible by + # _PyHASH_MASK. + dinv = pow(self._denominator, _PyHASH_MASK - 2, _PyHASH_MASK) + if not dinv: + return _PyHASH_INF + hash_ = abs(self._numerator) * dinv % _PyHASH_MASK + return hash_ if self >= 0 else -hash_ + def __eq__(a, b): """a == b""" if isinstance(b, numbers.Rational): Index: Lib/decimal.py =================================================================== --- Lib/decimal.py (revision 79745) +++ Lib/decimal.py (working copy) @@ -862,7 +862,7 @@ # that specified by IEEE 754. def __eq__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other, equality_op=True) if other is NotImplemented: return other if self._check_nans(other, context): @@ -870,7 +870,7 @@ return self._cmp(other) == 0 def __ne__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other, equality_op=True) if other is NotImplemented: return other if self._check_nans(other, context): @@ -879,7 +879,7 @@ def __lt__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other) if other is NotImplemented: return other ans = self._compare_check_nans(other, context) @@ -888,7 +888,7 @@ return self._cmp(other) < 0 def __le__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other) if other is NotImplemented: return other ans = self._compare_check_nans(other, context) @@ -897,7 +897,7 @@ return self._cmp(other) <= 0 def __gt__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other) if other is NotImplemented: return other ans = self._compare_check_nans(other, context) @@ -906,7 +906,7 @@ return self._cmp(other) > 0 def __ge__(self, other, context=None): - other = _convert_other(other, allow_float=True) + self, other = _convert_for_comparison(self, other) if other is NotImplemented: return other ans = self._compare_check_nans(other, context) @@ -935,56 +935,24 @@ def __hash__(self): """x.__hash__() <==> hash(x)""" - # Decimal integers must hash the same as the ints - # - # The hash of a nonspecial noninteger Decimal must depend only - # on the value of that Decimal, and not on its representation. - # For example: hash(Decimal('100E-1')) == hash(Decimal('10')). - - # Equality comparisons involving signaling nans can raise an - # exception; since equality checks are implicitly and - # unpredictably used when checking set and dict membership, we - # prevent signaling nans from being used as set elements or - # dict keys by making __hash__ raise an exception. if self._is_special: if self.is_snan(): raise TypeError('Cannot hash a signaling NaN value.') elif self.is_nan(): - # 0 to match hash(float('nan')) - return 0 + return _PyHASH_NAN else: - # values chosen to match hash(float('inf')) and - # hash(float('-inf')). if self._sign: - return -271828 + return _PyHASH_NINF else: - return 314159 + return _PyHASH_INF - # In Python 2.7, we're allowing comparisons (but not - # arithmetic operations) between floats and Decimals; so if - # a Decimal instance is exactly representable as a float then - # its hash should match that of the float. - self_as_float = float(self) - if Decimal.from_float(self_as_float) == self: - return hash(self_as_float) + if self._exp >= 0: + exp_hash = pow(10, self._exp, _PyHASH_MASK) + else: + exp_hash = pow(_PyHASH_10INV, -self._exp, _PyHASH_MASK) + hash_ = int(self._int) * exp_hash % _PyHASH_MASK + return hash_ if self >= 0 else -hash_ - if self._isinteger(): - op = _WorkRep(self.to_integral_value()) - # to make computation feasible for Decimals with large - # exponent, we use the fact that hash(n) == hash(m) for - # any two nonzero integers n and m such that (i) n and m - # have the same sign, and (ii) n is congruent to m modulo - # 2**64-1. So we can replace hash((-1)**s*c*10**e) with - # hash((-1)**s*c*pow(10, e, 2**64-1). - return hash((-1)**op.sign*op.int*pow(10, op.exp, 2**64-1)) - # The value of a nonzero nonspecial Decimal instance is - # faithfully represented by the triple consisting of its sign, - # its adjusted exponent, and its coefficient with trailing - # zeros removed. - return hash((self._sign, - self._exp+len(self._int), - self._int.rstrip('0'))) - def as_tuple(self): """Represents the number as a triple tuple. @@ -5824,6 +5792,37 @@ raise TypeError("Unable to convert %s to Decimal" % other) return NotImplemented +def _convert_for_comparison(self, other, equality_op=False): + """Given a Decimal instance self and a Python object other, return + an pair (s, o) of Decimal instances such that "s op o" is + equivalent to "self op other" for any of the 6 comparison + operators "op". + + """ + if isinstance(other, Decimal): + return self, other + + # Comparison with a Rational instance (also includes integers): + # self op n/d <=> self*d op n (for n and d integers, d positive). + # A NaN or infinity can be left unchanged without affecting the + # comparison result. + if isinstance(other, _numbers.Rational): + if not self._is_special: + self = _dec_from_triple(self._sign, + str(int(self._int) * other.denominator), + self._exp) + return self, Decimal(other.numerator) + + # Comparisons with float and complex types. == and != comparisons + # with complex numbers should succeed, returning either True or False + # as appropriate. Other comparisons return NotImplemented. + if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0: + other = other.real + if isinstance(other, float): + return self, Decimal.from_float(other) + return self, NotImplemented + + ##### Setup Specific Contexts ############################################ # The default context prototype used by Context() @@ -6154,8 +6153,20 @@ # _SignedInfinity[sign] is infinity w/ that sign _SignedInfinity = (_Infinity, _NegativeInfinity) +# Constants related to the hash implementation; hash(x) is based +# on the reduction of x modulo _PyHASH_MASK +import sys +_PyHASH_MASK = sys.hash_info.modulus +# hash values to use for positive and negative infinities, and nans +_PyHASH_INF = sys.hash_info.inf +_PyHASH_NINF = sys.hash_info.ninf +_PyHASH_NAN = sys.hash_info.nan +del sys +# _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MASK +_PyHASH_10INV = pow(10, _PyHASH_MASK - 2, _PyHASH_MASK) + if __name__ == '__main__': import doctest, sys doctest.testmod(sys.modules[__name__]) Index: Lib/test/test_fractions.py =================================================================== --- Lib/test/test_fractions.py (revision 79745) +++ Lib/test/test_fractions.py (working copy) @@ -395,12 +395,11 @@ self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** F(1, 10)) def testMixingWithDecimal(self): - # Decimal refuses mixed comparisons. + # Decimal refuses mixed arithmetic (but not mixed comparisons) self.assertRaisesMessage( TypeError, "unsupported operand type(s) for +: 'Fraction' and 'Decimal'", operator.add, F(3,11), Decimal('3.1415926')) - self.assertNotEquals(F(5, 2), Decimal('2.5')) def testComparisons(self): self.assertTrue(F(1, 2) < F(2, 3)) Index: Lib/test/test_numeric_tower.py =================================================================== --- Lib/test/test_numeric_tower.py (revision 0) +++ Lib/test/test_numeric_tower.py (revision 0) @@ -0,0 +1,206 @@ +# test interactions betwen int, float, Decimal and Fraction + +import unittest +import random +import math +import sys +import operator +from test.support import run_unittest + +from decimal import Decimal as D +from fractions import Fraction as F + +# Constants related to the hash implementation; hash(x) is based +# on the reduction of x modulo the prime _PyHASH_MASK. +_PyHASH_MASK = sys.hash_info.modulus + +class HashTest(unittest.TestCase): + def check_equal_hash(self, x, y): + # check both that x and y are equal and that their hashes are equal + self.assertEqual(hash(x), hash(y), + "got different hashes for {!r} and {!r}".format(x, y)) + self.assertEqual(x, y) + + def test_bools(self): + self.check_equal_hash(False, 0) + self.check_equal_hash(True, 1) + + def test_integers(self): + # check that equal values hash equal + + # exact integers + for i in range(-1000, 1000): + self.check_equal_hash(i, float(i)) + self.check_equal_hash(i, D(i)) + self.check_equal_hash(i, F(i)) + + # the current hash is based on reduction modulo 2**n-1 for some + # n, so pay special attention to numbers of the form 2**n and 2**n-1. + for i in range(100): + n = 2**i - 1 + if n == int(float(n)): + self.check_equal_hash(n, float(n)) + self.check_equal_hash(-n, -float(n)) + self.check_equal_hash(n, D(n)) + self.check_equal_hash(n, F(n)) + self.check_equal_hash(-n, D(-n)) + self.check_equal_hash(-n, F(-n)) + + n = 2**i + self.check_equal_hash(n, float(n)) + self.check_equal_hash(-n, -float(n)) + self.check_equal_hash(n, D(n)) + self.check_equal_hash(n, F(n)) + self.check_equal_hash(-n, D(-n)) + self.check_equal_hash(-n, F(-n)) + + # random values of various sizes + for _ in range(1000): + e = random.randrange(300) + n = random.randrange(-10**e, 10**e) + self.check_equal_hash(n, D(n)) + self.check_equal_hash(n, F(n)) + if n == int(float(n)): + self.check_equal_hash(n, float(n)) + + def test_binary_floats(self): + # check that floats hash equal to corresponding Fractions and Decimals + + # floats that are distinct but numerically equal should hash the same + self.check_equal_hash(0.0, -0.0) + + # zeros + self.check_equal_hash(0.0, D(0)) + self.check_equal_hash(-0.0, D(0)) + self.check_equal_hash(-0.0, D('-0.0')) + self.check_equal_hash(0.0, F(0)) + + # infinities and nans + self.check_equal_hash(float('inf'), D('inf')) + self.check_equal_hash(float('-inf'), D('-inf')) + + for _ in range(1000): + x = random.random() * math.exp(random.random()*200.0 - 100.0) + self.check_equal_hash(x, D.from_float(x)) + self.check_equal_hash(x, F.from_float(x)) + + def test_complex(self): + # complex numbers with zero imaginary part should hash equal to + # the corresponding float + + test_values = [0.0, -0.0, 1.0, -1.0, 0.40625, -5136.5, + float('inf'), float('-inf')] + + for zero in -0.0, 0.0: + for value in test_values: + self.check_equal_hash(value, complex(value, zero)) + + def test_decimals(self): + # check that Decimal instances that have different representations + # but equal values give the same hash + zeros = ['0', '-0', '0.0', '-0.0e10', '000e-10'] + for zero in zeros: + self.check_equal_hash(D(zero), D(0)) + + self.check_equal_hash(D('1.00'), D(1)) + self.check_equal_hash(D('1.00000'), D(1)) + self.check_equal_hash(D('-1.00'), D(-1)) + self.check_equal_hash(D('-1.00000'), D(-1)) + self.check_equal_hash(D('123e2'), D(12300)) + self.check_equal_hash(D('1230e1'), D(12300)) + self.check_equal_hash(D('12300'), D(12300)) + self.check_equal_hash(D('12300.0'), D(12300)) + self.check_equal_hash(D('12300.00'), D(12300)) + self.check_equal_hash(D('12300.000'), D(12300)) + + def test_fractions(self): + # hashes are based on reduction modulo _PyHASH_MASK, so just check + # that we can still compute a hash without error for Fractions + # whose reduction modulo _PyHASH_MASK is infinite. + hash(F(1, _PyHASH_MASK)) + hash(F(-12345, _PyHASH_MASK)) + hash(F(_PyHASH_MASK, 5*_PyHASH_MASK)) + + def test_hash_normalization(self): + # Test for a bug encountered while changing long_hash. + # + # Given objects x and y, it should be possible for y's + # __hash__ method to return hash(x) in order to ensure that + # hash(x) == hash(y). But hash(x) is not exactly equal to the + # result of x.__hash__(): there's some internal normalization + # to make sure that the result fits in a C long, and is not + # equal to the invalid hash value -1. This internal + # normalization must therefore not change the result of + # hash(x) for any x. + + class HalibutProxy: + def __hash__(self): + return hash('halibut') + def __eq__(self, other): + return other == 'halibut' + + x = {'halibut', HalibutProxy()} + self.assertEqual(len(x), 1) + +class ComparisonTest(unittest.TestCase): + def test_mixed_comparisons(self): + + # ordered list of distinct test values of various types: + # int, float, Fraction, Decimal + test_values = [ + float('-inf'), + D('-1e999999999'), + -1e308, + F(-22, 7), + -3.14, + -2, + 0.0, + 1e-320, + True, + F('1.2'), + D('1.3'), + float('1.4'), + F(275807, 195025), + D('1.414213562373095048801688724'), + F(114243, 80782), + F(473596569, 84615), + 7e200, + D('infinity'), + ] + for i, first in enumerate(test_values): + for second in test_values[i+1:]: + self.assertLess(first, second) + self.assertLessEqual(first, second) + self.assertGreater(second, first) + self.assertGreaterEqual(second, first) + + def test_complex(self): + # comparisons with complex are special: equality and inequality + # comparisons should always succeed, but order comparisons should + # raise TypeError. + z = 1.0 + 0j + w = -3.14 + 2.7j + + for v in 1, 1.0, F(1), D(1), complex(1): + self.assertEqual(z, v) + self.assertEqual(v, z) + + for v in 2, 2.0, F(2), D(2), complex(2): + self.assertNotEqual(z, v) + self.assertNotEqual(v, z) + self.assertNotEqual(w, v) + self.assertNotEqual(v, w) + + for v in (1, 1.0, F(1), D(1), complex(1), + 2, 2.0, F(2), D(2), complex(2), w): + for op in operator.le, operator.lt, operator.ge, operator.gt: + self.assertRaises(TypeError, op, z, v) + self.assertRaises(TypeError, op, v, z) + + + +def test_main(): + run_unittest(HashTest, ComparisonTest) + +if __name__ == '__main__': + test_main() Index: Lib/test/test_sys.py =================================================================== --- Lib/test/test_sys.py (revision 79745) +++ Lib/test/test_sys.py (working copy) @@ -392,6 +392,22 @@ self.assertEqual(type(sys.int_info.bits_per_digit), int) self.assertEqual(type(sys.int_info.sizeof_digit), int) self.assertIsInstance(sys.hexversion, int) + + self.assertEqual(len(sys.hash_info), 5) + self.assertEqual(2**sys.hash_info.bits-1, sys.hash_info.modulus) + # quick probable primality tests (doesn't exclude the possibility + # of a Carmichael number) + for x in range(1, 100): + self.assertEqual( + pow(x, sys.hash_info.modulus-1, sys.hash_info.modulus), + 1, + "sys.hash_info.modulus {} is a non-prime".format( + sys.hash_info.modulus) + ) + self.assertIsInstance(sys.hash_info.inf, int) + self.assertIsInstance(sys.hash_info.ninf, int) + self.assertIsInstance(sys.hash_info.nan, int) + self.assertIsInstance(sys.maxsize, int) self.assertIsInstance(sys.maxunicode, int) self.assertIsInstance(sys.platform, str)