Problem 26 — Reciprocal Cycles

solved Tue, 29 Oct 2019, 01:05 · level 3 · difficulty 10% projecteuler.net github

A unit fraction contains $1$ in the numerator. The decimal representation of the unit fractions with denominators $2$ to $10$ are given:

$$\begin{align} 1/2 &= 0.5\\ 1/3 &=0.(3)\\ 1/4 &=0.25\\ 1/5 &= 0.2\\ 1/6 &= 0.1(6)\\ 1/7 &= 0.(142857)\\ 1/8 &= 0.125\\ 1/9 &= 0.(1)\\ 1/10 &= 0.1 \end{align}$$

Where $0.1(6)$ means $0.166666\cdots$, and has a $1$-digit recurring cycle. It can be seen that $1/7$ has a $6$-digit recurring cycle.

Find the value of $d \lt 1000$ for which $1/d$ contains the longest recurring cycle in its decimal fraction part.


Problem Analysis

When you carry out long division to express the unit fraction 1/d as a decimal, the digits after the point either terminate or eventually fall into a repeating loop. The fraction terminates exactly when d's only prime factors are 2 and 5 - the prime factors of our base, 10. For every other d, the expansion settles into a purely repeating cycle, and the length of that cycle is a well-studied number-theoretic quantity called the multiplicative order of 10 modulo d. The multiplicative order of a base a modulo n is the smallest positive integer k such that a^k is congruent to 1 modulo n - written a^k = 1 (mod n). It exists if and only if a and n are coprime (their greatest common divisor is 1). The reason this maps onto our problem is that the remainders produced during long division of 1/d are precisely the sequence 10^k mod d, and the cycle closes the first time that remainder returns to its starting value of 1.

The observation that makes the search efficient is a bound on how long a cycle can be. The remainders in the long-division process are all in the range 1 to d-1, so the cycle length can be at most d-1. When d is prime, Fermat's little theorem tells us the order divides d-1, and the maximum achievable is exactly d-1. This means the longest cycle below a limit L must come from a large d near L, because a small d simply cannot produce a long enough cycle. I exploit this by scanning the top of the range downward rather than testing every d from 2 upward. Computing the order by direct simulation costs O(d) per candidate, and restricting the scan to the top tenth of the range makes the whole search O(L/10 * L) - trivially fast for L = 1000.

Solution Approach

Multiplicative-Order Search by Simulation (Python and C)

Both files implement the same algorithm in two languages. The heart of it is multiplicative_order(a, modulus): I start with a remainder r = 1 and repeatedly apply r = r * a % modulus, counting each step until r returns to 1. The step count at that moment is the cycle length. I cap the loop at modulus - 1 iterations because, by Fermat's argument above, the order can never exceed that - and if the loop finishes without returning to 1, no order exists and I return None (or -1 in C).

Before invoking the order computation I filter with a greatest common divisor check: if gcd(d, 10) != 1, then d shares a factor of 2 or 5 with the base, the order does not exist, and the simulation loop would never terminate by finding r == 1. Skipping these candidates is both a correctness guard and a small performance win. The Python version uses math.gcd; the C version implements the Euclidean algorithm by hand in a tight while loop.

The search window covers the top 10% of the range (with a floor of 10 iterations so tiny limits like the dev case of 10 still examine enough candidates). I justify this restriction with the cycle-length bound: a d below L/10 can produce a cycle no longer than L/10, which will always be beaten by a large d near L carrying a near-maximal cycle. The Python solution feeds a generator of (cycle_length, d) tuples into max(), relying on lexicographic tuple comparison to maximise length first and break ties toward the larger d. The C solution tracks best_len and best_d explicitly, encoding the identical tie-break in a conditional. Both take the limit as a runtime argument so the same binary handles the dev and main cases without recompilation.

workbench

Test cases

categoryinputanswer
dev limit = 10 7
main limit = 1000 983

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0026_s0 dev 7 correct 0.000000 (84) 0.000002 (84)
p0026_s0 main 983 correct 0.000062 (84) 0.000470 (84)

Files

Tags & blogs

Blogs