cpython/Lib/test/test_numeric_tower.py
Robert Smallshire 58a7da9e12
bpo-26680: Incorporate is_integer in all built-in and standard library numeric types (GH-6121)
* 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>
2020-10-01 17:30:08 +01:00

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()