Skip to content

Commit 1019ed7

Browse files
committed
feat: add repunit theorem helpers
Fixes: #1866
1 parent 5c39e87 commit 1019ed7

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

Maths/RepunitTheorem.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Repunit theorem helpers.
3+
*
4+
* A repunit of length n is:
5+
* R_n = (10^n - 1) / 9
6+
*
7+
* For a prime p (p != 2, 5), p divides R_n iff ord_p(10) divides n.
8+
* Reference: https://en.wikipedia.org/wiki/Repunit
9+
*/
10+
11+
const gcd = (a, b) => {
12+
let x = BigInt(a)
13+
let y = BigInt(b)
14+
while (y !== 0n) {
15+
;[x, y] = [y, x % y]
16+
}
17+
return x < 0n ? -x : x
18+
}
19+
20+
const modPow = (base, exp, mod) => {
21+
let result = 1n
22+
let b = BigInt(base) % BigInt(mod)
23+
let e = BigInt(exp)
24+
const m = BigInt(mod)
25+
26+
while (e > 0n) {
27+
if (e & 1n) result = (result * b) % m
28+
b = (b * b) % m
29+
e >>= 1n
30+
}
31+
32+
return result
33+
}
34+
35+
const multiplicativeOrder10 = (prime) => {
36+
const p = BigInt(prime)
37+
if (p <= 1n) throw new RangeError('prime must be > 1')
38+
if (gcd(10n, p) !== 1n) throw new RangeError('10 and prime must be coprime')
39+
40+
// For prime p, ord_p(10) divides p-1.
41+
const upper = p - 1n
42+
for (let k = 1n; k <= upper; k++) {
43+
if (upper % k === 0n && modPow(10n, k, p) === 1n) {
44+
return k
45+
}
46+
}
47+
48+
throw new Error('multiplicative order not found')
49+
}
50+
51+
const repunitMod = (length, mod) => {
52+
if (!Number.isInteger(length) || length < 1) {
53+
throw new RangeError('length must be a positive integer')
54+
}
55+
const m = BigInt(mod)
56+
if (m <= 0n) throw new RangeError('mod must be > 0')
57+
58+
let remainder = 0n
59+
for (let i = 0; i < length; i++) {
60+
remainder = (remainder * 10n + 1n) % m
61+
}
62+
return remainder
63+
}
64+
65+
const isRepunitDivisibleByPrime = (length, prime) => {
66+
if (!Number.isInteger(length) || length < 1) {
67+
throw new RangeError('length must be a positive integer')
68+
}
69+
70+
const p = BigInt(prime)
71+
if (p === 2n || p === 5n) return false
72+
if (gcd(10n, p) !== 1n) return false
73+
74+
const order = multiplicativeOrder10(p)
75+
return BigInt(length) % order === 0n
76+
}
77+
78+
export { multiplicativeOrder10, repunitMod, isRepunitDivisibleByPrime }

Maths/test/RepunitTheorem.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
isRepunitDivisibleByPrime,
3+
multiplicativeOrder10,
4+
repunitMod
5+
} from '../RepunitTheorem'
6+
7+
describe('RepunitTheorem', () => {
8+
it('computes multiplicative order examples', () => {
9+
expect(multiplicativeOrder10(11n)).toBe(2n)
10+
expect(multiplicativeOrder10(37n)).toBe(3n)
11+
expect(multiplicativeOrder10(7n)).toBe(6n)
12+
})
13+
14+
it('checks repunit divisibility using the theorem', () => {
15+
// 111111 is divisible by 3, 7, 11, 13, 37
16+
expect(isRepunitDivisibleByPrime(6, 3n)).toBe(true)
17+
expect(isRepunitDivisibleByPrime(6, 7n)).toBe(true)
18+
expect(isRepunitDivisibleByPrime(6, 11n)).toBe(true)
19+
expect(isRepunitDivisibleByPrime(6, 13n)).toBe(true)
20+
expect(isRepunitDivisibleByPrime(6, 37n)).toBe(true)
21+
})
22+
23+
it('returns false when divisibility condition does not hold', () => {
24+
expect(isRepunitDivisibleByPrime(6, 19n)).toBe(false)
25+
expect(isRepunitDivisibleByPrime(9, 2n)).toBe(false)
26+
expect(isRepunitDivisibleByPrime(9, 5n)).toBe(false)
27+
})
28+
29+
it('computes repunit modulo without building huge integers', () => {
30+
expect(repunitMod(6, 37n)).toBe(0n)
31+
expect(repunitMod(6, 11n)).toBe(0n)
32+
expect(repunitMod(7, 13n)).toBe(1n)
33+
})
34+
})

0 commit comments

Comments
 (0)