mirror of
https://github.com/python/cpython.git
synced 2025-07-12 05:45:15 +00:00

* bpo-26680: Adds support for int.is_integer() for compatibility with float.is_integer(). The int.is_integer() method always returns True. * bpo-26680: Adds a test to ensure that False.is_integer() and True.is_integer() are always True. * bpo-26680: Adds Real.is_integer() with a trivial implementation using conversion to int. This default implementation is intended to reduce the workload for subclass implementers. It is not robust in the presence of infinities or NaNs and may have suboptimal performance for other types. * bpo-26680: Adds Rational.is_integer which returns True if the denominator is one. This implementation assumes the Rational is represented in it's lowest form, as required by the class docstring. * bpo-26680: Adds Integral.is_integer which always returns True. * bpo-26680: Adds tests for Fraction.is_integer called as an instance method. The tests for the Rational abstract base class use an unbound method to sidestep the inability to directly instantiate Rational. These tests check that everything works correct as an instance method. * bpo-26680: Updates documentation for Real.is_integer and built-ins int and float. The call x.is_integer() is now listed in the table of operations which apply to all numeric types except complex, with a reference to the full documentation for Real.is_integer(). Mention of is_integer() has been removed from the section 'Additional Methods on Float'. The documentation for Real.is_integer() describes its purpose, and mentions that it should be overridden for performance reasons, or to handle special values like NaN. * bpo-26680: Adds Decimal.is_integer to the Python and C implementations. The C implementation of Decimal already implements and uses mpd_isinteger internally, we just expose the existing function to Python. The Python implementation uses internal conversion to integer using to_integral_value(). In both cases, the corresponding context methods are also implemented. Tests and documentation are included. * bpo-26680: Updates the ACKS file. * bpo-26680: NEWS entries for int, the numeric ABCs and Decimal. Co-authored-by: Robert Smallshire <rob@sixty-north.com>
233 lines
8.3 KiB
Python
233 lines
8.3 KiB
Python
# test interactions between int, float, Decimal and Fraction
|
|
|
|
import unittest
|
|
import random
|
|
import math
|
|
import sys
|
|
import operator
|
|
|
|
from numbers import Real, Rational, Integral
|
|
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_MODULUS.
|
|
_PyHASH_MODULUS = sys.hash_info.modulus
|
|
_PyHASH_INF = sys.hash_info.inf
|
|
|
|
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):
|
|
# check special case for fractions where either the numerator
|
|
# or the denominator is a multiple of _PyHASH_MODULUS
|
|
self.assertEqual(hash(F(1, _PyHASH_MODULUS)), _PyHASH_INF)
|
|
self.assertEqual(hash(F(-1, 3*_PyHASH_MODULUS)), -_PyHASH_INF)
|
|
self.assertEqual(hash(F(7*_PyHASH_MODULUS, 1)), 0)
|
|
self.assertEqual(hash(F(-_PyHASH_MODULUS, 1)), 0)
|
|
|
|
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('-1e425000000'),
|
|
-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)
|
|
|
|
|
|
class IsIntegerTest(unittest.TestCase):
|
|
|
|
def test_real_is_integer(self):
|
|
self.assertTrue(Real.is_integer(-1.0))
|
|
self.assertTrue(Real.is_integer(0.0))
|
|
self.assertTrue(Real.is_integer(1.0))
|
|
self.assertTrue(Real.is_integer(42.0))
|
|
|
|
self.assertFalse(Real.is_integer(-0.5))
|
|
self.assertFalse(Real.is_integer(4.2))
|
|
|
|
def test_rational_is_integer(self):
|
|
self.assertTrue(Rational.is_integer(F(-1, 1)))
|
|
self.assertTrue(Rational.is_integer(F(0, 1)))
|
|
self.assertTrue(Rational.is_integer(F(1, 1)))
|
|
self.assertTrue(Rational.is_integer(F(42, 1)))
|
|
self.assertTrue(Rational.is_integer(F(2, 2)))
|
|
self.assertTrue(Rational.is_integer(F(8, 4)))
|
|
|
|
self.assertFalse(Rational.is_integer(F(1, 2)))
|
|
self.assertFalse(Rational.is_integer(F(1, 3)))
|
|
self.assertFalse(Rational.is_integer(F(2, 3)))
|
|
|
|
def test_integral_is_integer(self):
|
|
self.assertTrue(Integral.is_integer(-1))
|
|
self.assertTrue(Integral.is_integer(0))
|
|
self.assertTrue(Integral.is_integer(1))
|
|
self.assertTrue(Integral.is_integer(1729))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|