Python math.integer
last modified October 29, 2026 Python 3.15
In this article we show how to use the math.integer module, which
was added in Python 3.15 by PEP 791. We also cover the new floating-point
functions that Python 3.15 added to the math module.
The math module has two kinds of functions. Most of them, such as
sqrt, sin, or log, work with
floating-point numbers and return floats. A few work only with integers and
return exact integer results. The math.integer module collects
the integer functions in one place:
gcd– greatest common divisorlcm– least common multipleisqrt– integer square rootfactorial– factorial of a numbercomb– number of combinationsperm– number of permutations
The functions with the same names in the math module remain as
aliases for backward compatibility. Since Python 3.15 they are soft
deprecated: they produce no warning and are not scheduled for removal, but new
code should use math.integer.
The module contents
The first example lists the public functions of the module.
import math
import math.integer
names = [name for name in dir(math.integer) if not name.startswith('_')]
print(names)
print(math.integer.isqrt(17))
print(math.integer.gcd is math.gcd)
The module is imported with import math.integer; the functions
can also be imported directly with from math.integer import gcd,
which we do in the rest of the article.
print(math.integer.gcd is math.gcd)
The aliases in math are the very same function objects, so both
names behave identically.
$ python contents.py ['comb', 'factorial', 'gcd', 'isqrt', 'lcm', 'perm'] 4 True
Greatest common divisor
The gcd function returns the greatest common divisor of its
arguments: the largest integer that divides all of them without a remainder.
from math.integer import gcd
print(gcd(48, 18))
print(gcd(48, 18, 30))
print(gcd(-12, 8))
print(gcd(0, 7))
print(gcd())
num, den = 1071, 462
d = gcd(num, den)
print(f'{num}/{den} = {num // d}/{den // d}')
The function accepts any number of arguments.
print(gcd(-12, 8)) print(gcd(0, 7)) print(gcd())
The result is always non-negative. Zero is divisible by every integer, so
gcd(0, 7) is 7. Called without arguments, the function returns
0.
num, den = 1071, 462
d = gcd(num, den)
print(f'{num}/{den} = {num // d}/{den // d}')
Dividing the numerator and the denominator of a fraction by their greatest common divisor reduces the fraction to lowest terms.
$ python gcd_fraction.py 6 6 4 7 0 1071/462 = 51/22
Least common multiple
The lcm function returns the least common multiple: the smallest
positive integer that is divisible by all the arguments.
from math.integer import lcm
print(lcm(4, 6))
print(lcm(4, 6, 10))
print(lcm(5, 0))
print(lcm())
intervals = {'line 3': 12, 'line 7': 18, 'line 11': 30}
minutes = lcm(*intervals.values())
print(f'All three buses leave together again after {minutes} minutes')
If any argument is zero, the result is zero. Without arguments, the function returns 1.
intervals = {'line 3': 12, 'line 7': 18, 'line 11': 30}
minutes = lcm(*intervals.values())
Three bus lines leave a stop at the same time, every 12, 18, and 30 minutes. The least common multiple of the intervals tells us when all three leave together again.
$ python lcm_schedule.py 12 60 0 1 All three buses leave together again after 180 minutes
Integer square root
The isqrt function returns the integer part of the square root of
a non-negative integer, that is, the largest integer a such that
a * a <= n.
import math
from math.integer import isqrt
print(isqrt(17), isqrt(25), isqrt(0))
n = 2**106 - 1
print(int(math.sqrt(n)))
print(isqrt(n))
def is_square(n):
return isqrt(n) ** 2 == n
def ceil_sqrt(n):
return 1 + isqrt(n - 1) if n else 0
print([k for k in range(1, 100) if is_square(k)])
print(ceil_sqrt(16), ceil_sqrt(17))
The result is computed exactly, without converting the number to a float.
n = 2**106 - 1 print(int(math.sqrt(n))) print(isqrt(n))
For large numbers, the float-based math.sqrt loses precision. A
float has 53 bits of mantissa, so 2**106 - 1 is rounded up to
2**106 before the root is computed, and the result is off by one.
The isqrt function works with integers of any size.
def is_square(n):
return isqrt(n) ** 2 == n
def ceil_sqrt(n):
return 1 + isqrt(n - 1) if n else 0
Two common helpers: a perfect-square test and the ceiling of the square root,
the smallest integer a such that a * a >= n.
$ python isqrt_demo.py 4 5 0 9007199254740992 9007199254740991 [1, 4, 9, 16, 25, 36, 49, 64, 81] 4 5
Primality test
A number is prime if it has no divisor between 2 and its square root. The
isqrt function gives the upper bound of the search.
from math.integer import isqrt
def is_prime(n):
if n < 2:
return False
return all(n % d for d in range(2, isqrt(n) + 1))
print([n for n in range(50) if is_prime(n)])
print(is_prime(2**31 - 1))
print(is_prime(2**31 + 1))
The all function returns False as soon as some
n % d is zero, that is, as soon as a divisor is found.
return all(n % d for d in range(2, isqrt(n) + 1))
For the Mersenne prime 2**31 - 1, only divisors up to 46,340 have
to be tried. The number 2**31 + 1 is divisible by 3.
$ python primes.py [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] True False
Factorial
The factorial function returns the product of all positive
integers up to n. By definition, factorial(0) is 1.
from math.integer import factorial
print([factorial(n) for n in range(8)])
f = factorial(100)
digits = str(f)
print(len(digits))
def trailing_zeros(n):
count = 0
while n:
n //= 5
count += n
return count
print(trailing_zeros(100))
print(len(digits) - len(digits.rstrip('0')))
Python integers have arbitrary precision, so the factorial of 100 is computed exactly; it has 158 digits.
def trailing_zeros(n):
count = 0
while n:
n //= 5
count += n
return count
The number of trailing zeros of n! equals the number of factors
of 5 in the product, which Legendre's formula counts without computing the
factorial. We verify the result by stripping the zeros from the digits.
$ python factorial_demo.py [1, 1, 2, 6, 24, 120, 720, 5040] 158 24 24
Combinations and permutations
The comb(n, k) function returns the number of ways to choose
k items from n items when the order does not matter.
The perm(n, k) function returns the number of ways when the order
matters.
from math.integer import comb, factorial, perm
print(comb(5, 2), perm(5, 2))
print(comb(5, 7), perm(5, 7))
print(perm(5), factorial(5))
print(f'Ways to pick 6 numbers out of 49: {comb(49, 6):,}')
print(f'Possible podiums with 10 runners: {perm(10, 3):,}')
Choosing two items out of five gives 10 combinations and 20 permutations, since each pair can be ordered in two ways.
print(comb(5, 7), perm(5, 7)) print(perm(5), factorial(5))
When k is greater than n, both functions return 0.
If k is omitted, perm returns n!, the
number of orderings of all n items.
print(f'Ways to pick 6 numbers out of 49: {comb(49, 6):,}')
print(f'Possible podiums with 10 runners: {perm(10, 3):,}')
A lottery ticket is a combination, since the order of the drawn numbers does not matter. A podium is a permutation, because first, second, and third place are different results.
$ python comb_perm.py 10 20 0 0 120 120 Ways to pick 6 numbers out of 49: 13,983,816 Possible podiums with 10 runners: 720
Pascal's triangle
The entries of Pascal's triangle are binomial coefficients: the
k-th entry of row n is comb(n, k).
from math.integer import comb
rows = 7
width = rows * 4
for n in range(rows):
line = ' '.join(f'{comb(n, k):3}' for k in range(n + 1))
print(line.center(width).rstrip())
Each row is built from the binomial coefficients and centered with the
center string method.
$ python pascal.py
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
Poker hand probabilities
The comb function is the basic tool for counting in probability.
The next example computes the odds of several poker hands.
from math.integer import comb
hands = comb(52, 5)
counts = {
'four of a kind': 13 * comb(4, 4) * comb(48, 1),
'full house': 13 * comb(4, 3) * 12 * comb(4, 2),
'flush (incl. straight flush)': comb(4, 1) * comb(13, 5),
'three of a kind': 13 * comb(4, 3) * comb(12, 2) * 4 ** 2,
'two pair': comb(13, 2) * comb(4, 2) ** 2 * 11 * 4,
}
print(f'{hands:,} possible hands')
for name, count in counts.items():
print(f'{name:30} {count:8,} 1 in {hands / count:,.0f}')
There are comb(52, 5) five-card hands. Each count multiplies the
number of ways to pick ranks by the number of ways to pick suits.
'full house': 13 * comb(4, 3) * 12 * comb(4, 2),
For example, a full house consists of three cards of one rank (13 ranks, 4 choose 3 suits) and two cards of another rank (the 12 remaining ranks, 4 choose 2 suits).
$ python poker.py 2,598,960 possible hands four of a kind 624 1 in 4,165 full house 3,744 1 in 694 flush (incl. straight flush) 5,148 1 in 505 three of a kind 54,912 1 in 47 two pair 123,552 1 in 21
Invalid arguments
The functions raise a ValueError for negative arguments where they
make no sense and a TypeError for non-integers.
from math.integer import comb, factorial, gcd, isqrt
calls = [
('factorial(-1)', lambda: factorial(-1)),
('factorial(5.0)', lambda: factorial(5.0)),
('isqrt(-4)', lambda: isqrt(-4)),
('comb(5, -1)', lambda: comb(5, -1)),
('gcd(4.0, 6)', lambda: gcd(4.0, 6)),
]
for label, call in calls:
try:
call()
except (ValueError, TypeError) as e:
print(f'{label:15} {type(e).__name__}: {e}')
Each call is wrapped in a lambda so that we can catch and print its exception.
Note that factorial(5.0) is rejected even though 5.0 has an
integral value; floats are never accepted.
$ python arg_errors.py factorial(-1) ValueError: factorial() not defined for negative values factorial(5.0) TypeError: 'float' object cannot be interpreted as an integer isqrt(-4) ValueError: isqrt() argument must be nonnegative comb(5, -1) ValueError: k must be a non-negative integer gcd(4.0, 6) TypeError: 'float' object cannot be interpreted as an integer
The __index__ method
The functions accept integers and any objects that implement the
__index__ special method.
from math.integer import factorial, gcd
class Count:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
print(factorial(Count(6)))
print(gcd(Count(12), 18))
print(factorial(True))
The __index__ method tells Python how to convert an object to an
integer losslessly. It is also used by slicing and by functions such as
bin and hex.
print(factorial(True))
The bool type is a subclass of int, so
True is accepted as 1.
$ python index_protocol.py 720 6 1
The signbit function
Python 3.15 also added several floating-point functions to the
math module. The math.signbit function returns
True if the sign bit of a float is set.
import math
values = [3.0, -3.0, 0.0, -0.0, math.inf, -math.inf, math.nan, -math.nan]
for x in values:
print(f'{x!r:>5} x < 0: {x < 0!s:5} signbit: {math.signbit(x)}')
For most values, signbit(x) gives the same answer as
x < 0. The two differ for negative zero and NaN.
Negative zero compares equal to zero, so -0.0 < 0 is false,
but its sign bit is set. NaN values also carry a sign bit, although their
representation does not show it. Before Python 3.15, the sign bit was usually
tested with math.copysign(1, x) < 0.
$ python signbit.py 3.0 x < 0: False signbit: False -3.0 x < 0: True signbit: True 0.0 x < 0: False signbit: False -0.0 x < 0: False signbit: True inf x < 0: False signbit: False -inf x < 0: True signbit: True nan x < 0: False signbit: False nan x < 0: False signbit: True
The fmax and fmin functions
The math.fmax and math.fmin functions return the
larger and the smaller of two floats. Unlike the built-in max and
min, they treat NaN as missing data.
import math from functools import reduce print(max(21.5, math.nan), max(math.nan, 21.5)) print(math.fmax(21.5, math.nan), math.fmax(math.nan, 21.5)) print(math.fmin(math.nan, math.nan)) readings = [math.nan, 21.5, 23.0, math.nan, 19.8] print(max(readings), min(readings)) print(reduce(math.fmax, readings), reduce(math.fmin, readings))
Every comparison with NaN is false, so the result of the built-in
max depends on the order of the arguments.
print(math.fmax(21.5, math.nan), math.fmax(math.nan, 21.5)) print(math.fmin(math.nan, math.nan))
The fmax function ignores a NaN argument and returns the other
value. It returns NaN only if both arguments are NaN.
print(reduce(math.fmax, readings), reduce(math.fmin, readings))
The functions take exactly two arguments. To find the extremes of a sequence
with missing measurements, we fold the sequence with
functools.reduce. The built-in functions return NaN here, because
the first reading is NaN.
$ python fmax_fmin.py 21.5 nan 21.5 21.5 nan nan nan 23.0 19.8
Normal and subnormal numbers
The math.isnormal and math.issubnormal functions
classify floats. A normal float uses the full 53-bit precision. Numbers
smaller than sys.float_info.min can still be represented as
subnormal floats, but with fewer significant bits.
import math
import sys
smallest_normal = sys.float_info.min
values = [1.0, smallest_normal, smallest_normal / 2, 5e-324,
0.0, math.inf, math.nan]
for x in values:
print(f'{x!r:>23} normal: {math.isnormal(x)!s:5} '
f'subnormal: {math.issubnormal(x)}')
The smallest normal float is about 2.2e-308. Half of it is subnormal, and
5e-324 is the smallest positive subnormal float. Zero, infinity,
and NaN are neither normal nor subnormal. Subnormal numbers lose precision and
can be slow on some hardware, so numerical code sometimes checks for them.
$ python subnormal.py
1.0 normal: True subnormal: False
2.2250738585072014e-308 normal: True subnormal: False
1.1125369292536007e-308 normal: False subnormal: True
5e-324 normal: False subnormal: True
0.0 normal: False subnormal: False
inf normal: False subnormal: False
nan normal: False subnormal: False
Source
math.integer – integer-specific mathematics functions
In this article we have worked with the math.integer module and
the new floating-point functions of the math module in
Python 3.15.
Author
List all Python tutorials.