Skip to content

Commit a24ebd5

Browse files
committed
feat(maths): add repunit theorem helpers
1 parent fc2f947 commit a24ebd5

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

maths/repunit_theorem.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Utilities related to repunits and a classical repunit divisibility theorem.
3+
4+
A repunit of length ``k`` is the number made of ``k`` ones:
5+
``R_k = 11...1``.
6+
7+
For every positive integer ``n`` with ``gcd(n, 10) = 1``,
8+
there exists a repunit ``R_k`` divisible by ``n``.
9+
"""
10+
11+
from math import gcd
12+
13+
14+
def has_repunit_multiple(divisor: int) -> bool:
15+
"""
16+
Check whether a divisor admits a repunit multiple.
17+
18+
>>> has_repunit_multiple(7)
19+
True
20+
>>> has_repunit_multiple(13)
21+
True
22+
>>> has_repunit_multiple(2)
23+
False
24+
>>> has_repunit_multiple(25)
25+
False
26+
"""
27+
if divisor <= 0:
28+
raise ValueError("divisor must be a positive integer")
29+
return gcd(divisor, 10) == 1
30+
31+
32+
def least_repunit_length(divisor: int) -> int:
33+
"""
34+
Return the smallest length ``k`` such that repunit ``R_k`` is divisible by divisor.
35+
36+
Uses modular arithmetic to avoid constructing huge integers.
37+
38+
>>> least_repunit_length(3)
39+
3
40+
>>> least_repunit_length(7)
41+
6
42+
>>> least_repunit_length(41)
43+
5
44+
"""
45+
if divisor <= 0:
46+
raise ValueError("divisor must be a positive integer")
47+
if not has_repunit_multiple(divisor):
48+
raise ValueError("divisor must be coprime to 10")
49+
50+
remainder = 0
51+
for length in range(1, divisor + 1):
52+
remainder = (remainder * 10 + 1) % divisor
53+
if remainder == 0:
54+
return length
55+
56+
# Unreachable when gcd(divisor, 10) == 1 (pigeonhole principle theorem).
57+
raise ArithmeticError("no repunit length found for divisor")
58+
59+
60+
if __name__ == "__main__":
61+
import doctest
62+
63+
doctest.testmod()

0 commit comments

Comments
 (0)