Skip to content

Commit 7867f61

Browse files
committed
extend Garner's algorithm and move it to a dedicated article
1 parent 00cb603 commit 7867f61

File tree

3 files changed

+170
-138
lines changed

3 files changed

+170
-138
lines changed

src/algebra/chinese-remainder-theorem.md

Lines changed: 8 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -209,149 +209,19 @@ It has the solution $53 \pmod{60}$, and indeed $53 \bmod{10} = 3$ and $53 \bmod{
209209

210210
## Garner's Algorithm
211211

212-
Another consequence of the CRT is that we can represent big numbers using an array of small integers. For example, let $p$ be the product of the first $1000$ primes. From calculations we can see that $p$ has around $3000$ digits.
212+
Another consequence of the CRT is that we can represent big numbers using an array of small integers.
213213

214-
Any number $a$ less than $p$ can be represented as an array $a_1, \ldots, a_k$, where $a_i \equiv a \pmod{p_i}$. But to do this we obviously need to know how to get back the number $a$ from its representation. In this section, we discuss Garner's Algorithm, which can be used for this purpose. We seek a representation on the form
214+
Instead of doing a lot of computations with very large numbers numbers, which might be expensive (think of doing divisions with 1000-digit numbers), you can pick a couple of coprime moduli and represent the large number as a system of congruences, and perform all operations on the system of equations.
215+
Any number $a$ less than $m_1 m_2 \cdots m_k$ can be represented as an array $a_1, \ldots, a_k$, where $a \equiv a_i \pmod{m_i}$.
215216

216-
$$a = x_1 + x_2 \cdot p_1 + x_3 \cdot p_1 \cdot p_2 + \ldots + x_k \cdot p_1 \cdots p_{k-1}$$
217+
By using the above algorithm, you can again reconstruct the large number whenever you need it.
217218

218-
which is called the mixed radix representation of $a$.
219-
Garner's algorithm computes the coefficients $x_1, \ldots, x_k$.
219+
Alternatively you can represent the number in the **mixed radix** representation:
220220

221-
Let $r_{ij}$ denote the inverse of $p_i$ modulo $p_j$
221+
$$a = x_1 + x_2 m_1 + x_3 m_1 m_2 + \ldots + x_k m_1 \cdots m_{k-1} \text{ with }x_i \in [0, m_i)$$
222222

223-
$$r_{ij} = (p_i)^{-1} \pmod{p_j}$$
224-
225-
which can be found using the algorithm described in [Modular Inverse](module-inverse.md). Substituting $a$ from the mixed radix representation into the first congruence equation we obtain
226-
227-
$$a_1 \equiv x_1 \pmod{p_1}.$$
228-
229-
Substituting into the second equation yields
230-
231-
$$a_2 \equiv x_1 + x_2 p_1 \pmod{p_2}.$$
232-
233-
which can be rewritten by subtracting $x_1$ and dividing by $p_1$ to get
234-
235-
$$\begin{array}{rclr}
236-
a_2 - x_1 &\equiv& x_2 p_1 &\pmod{p_2} \\
237-
(a_2 - x_1) r_{12} &\equiv& x_2 &\pmod{p_2} \\
238-
x_2 &\equiv& (a_2 - x_1) r_{12} &\pmod{p_2}
239-
\end{array}$$
240-
241-
Similarly we get that
242-
243-
$$x_3 \equiv ((a_3 - x_1) r_{13} - x_2) r_{23} \pmod{p_3}.$$
244-
245-
Now, we can clearly see an emerging pattern, which can be expressed by the following code:
246-
247-
```cpp
248-
for (int i = 0; i < k; ++i) {
249-
x[i] = a[i];
250-
for (int j = 0; j < i; ++j) {
251-
x[i] = r[j][i] * (x[i] - x[j]);
252-
253-
x[i] = x[i] % p[i];
254-
if (x[i] < 0)
255-
x[i] += p[i];
256-
}
257-
}
258-
```
259-
260-
So we learned how to calculate coefficients $x_i$ in $O(k^2)$ time. The number $a$ can now be calculated using the previously mentioned formula
261-
262-
$$a = x_1 + x_2 \cdot p_1 + x_3 \cdot p_1 \cdot p_2 + \ldots + x_k \cdot p_1 \cdots p_{k-1}$$
263-
264-
It is worth noting that in practice, we almost always need to compute the answer using Big Integers, but the coefficients $x_i$ can usually be calculated using built-in types, and therefore Garner's algorithm is very efficient.
265-
266-
## Implementation of Garner's Algorithm
267-
268-
It is convenient to implement this algorithm using Java, because it has built-in support for large numbers through the `BigInteger` class.
269-
270-
Here we show an implementation that can store big numbers in the form of a set of congruence equations.
271-
It supports addition, subtraction and multiplication.
272-
And with Garner's algorithm we can convert the set of equations into the unique integer.
273-
In this code, we take 100 prime numbers greater than $10^9$, which allows numbers as large as $10^{900}$.
274-
275-
```java
276-
final int SZ = 100;
277-
int pr[] = new int[SZ];
278-
int r[][] = new int[SZ][SZ];
279-
280-
void init() {
281-
for (int x = 1000 * 1000 * 1000, i = 0; i < SZ; ++x)
282-
if (BigInteger.valueOf(x).isProbablePrime(100))
283-
pr[i++] = x;
284-
285-
for (int i = 0; i < SZ; ++i)
286-
for (int j = i + 1; j < SZ; ++j)
287-
r[i][j] =
288-
BigInteger.valueOf(pr[i]).modInverse(BigInteger.valueOf(pr[j])).intValue();
289-
}
290-
291-
class Number {
292-
int a[] = new int[SZ];
293-
294-
public Number() {
295-
}
296-
297-
public Number(int n) {
298-
for (int i = 0; i < SZ; ++i)
299-
a[i] = n % pr[i];
300-
}
301-
302-
public Number(BigInteger n) {
303-
for (int i = 0; i < SZ; ++i)
304-
a[i] = n.mod(BigInteger.valueOf(pr[i])).intValue();
305-
}
306-
307-
public Number add(Number n) {
308-
Number result = new Number();
309-
for (int i = 0; i < SZ; ++i)
310-
result.a[i] = (a[i] + n.a[i]) % pr[i];
311-
return result;
312-
}
313-
314-
public Number subtract(Number n) {
315-
Number result = new Number();
316-
for (int i = 0; i < SZ; ++i)
317-
result.a[i] = (a[i] - n.a[i] + pr[i]) % pr[i];
318-
return result;
319-
}
320-
321-
public Number multiply(Number n) {
322-
Number result = new Number();
323-
for (int i = 0; i < SZ; ++i)
324-
result.a[i] = (int)((a[i] * 1l * n.a[i]) % pr[i]);
325-
return result;
326-
}
327-
328-
public BigInteger bigIntegerValue(boolean can_be_negative) {
329-
BigInteger result = BigInteger.ZERO, mult = BigInteger.ONE;
330-
int x[] = new int[SZ];
331-
for (int i = 0; i < SZ; ++i) {
332-
x[i] = a[i];
333-
for (int j = 0; j < i; ++j) {
334-
long cur = (x[i] - x[j]) * 1l * r[j][i];
335-
x[i] = (int)((cur % pr[i] + pr[i]) % pr[i]);
336-
}
337-
result = result.add(mult.multiply(BigInteger.valueOf(x[i])));
338-
mult = mult.multiply(BigInteger.valueOf(pr[i]));
339-
}
340-
341-
if (can_be_negative)
342-
if (result.compareTo(mult.shiftRight(1)) >= 0)
343-
result = result.subtract(mult);
344-
345-
return result;
346-
}
347-
}
348-
```
349-
350-
### Note on negative numbers
351-
352-
* Let $p$ be the product of all primes.
353-
354-
* Modular scheme itself does not allow representing negative numbers. However, it can be seen that if we know that the absolute values our numbers are smaller than $p / 2$, then we know that it must be negative when the resulting number is greater than $p / 2$. The flag `can_be_negative` in this code allows converting it to negative in this case.
223+
Garner's algorithm, which is discussed in the dedicated article [Garner's algorithm](garners-algorithm.md), computes the coefficients $x_i$.
224+
And with those coefficients you can restore the full number.
355225

356226
## Practice Problems:
357227

src/algebra/garners-algorithm.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Garner's algorithm
2+
3+
A consequence of the [Chinese Remainder Theorem](chinese-remainder-theorem.md) is, that we can represent big numbers using an array of small integers.
4+
For example, let $p$ be the product of the first $1000$ primes. $p$ has around $3000$ digits.
5+
6+
Any number $a$ less than $p$ can be represented as an array  $a_1, \ldots, a_k$, where $a_i \equiv a \pmod{p_i}$.
7+
But to do this we obviously need to know how to get back the number $a$ from its representation.
8+
One way is discussed in the article about the Chinese Remainder Theorem.
9+
10+
In this article we discuss an alternative, Garner's Algorithm, which can also be used for this purpose.
11+
12+
## Mixed Radix Representation
13+
14+
We can represent the number $a$ in the **mixed radix** representation:
15+
16+
$$a = x_1 + x_2 p_1 + x_3 p_1 p_2 + \ldots + x_k p_1 \cdots p_{k-1} \text{ with }x_i \in [0, p_i)$$
17+
18+
A mixed radix representation is a positional numeral system, that's a generalization of the typical number systems, like the binary numeral system or the decimal numeral system.
19+
For instance the decimal numeral system is a positional numeral system with the radix (or base) 10.
20+
Every a number is represented as a string of digits $d_1 d_2 d_3 \dots d_n$ between $0$ and $9$, and
21+
E.g. the string $415$ represents the number $4 \cdot 10^2 + 1 \cdot 10^1 + 5 \cdot 10^0$.
22+
In general the string of digits $d_1 d_2 d_3 \dots d_n$ represents the number $d_1 b^{n-1} + d_2 b^{n-2} + \cdots + d_n b^0$ in the positional numberal system with radix $b$.
23+
24+
In a mixed radix system, we don't have one radix any more. The base varies from position to position.
25+
26+
## Garner's algorithm
27+
28+
Garner's algorithm computes the digits $x_1, \ldots, x_k$.
29+
Notice, that the digits are relatively small.
30+
The digit $x_i$ is an integer between $0$ and $p_i - 1$.
31+
32+
Let $r_{ij}$ denote the inverse of $p_i$ modulo $p_j$
33+
34+
$$r_{ij} = (p_i)^{-1} \pmod{p_j}$$
35+
36+
which can be found using the algorithm described in [Modular Inverse](module-inverse.md).
37+
38+
Substituting $a$ from the mixed radix representation into the first congruence equation we obtain
39+
40+
$$a_1 \equiv x_1 \pmod{p_1}.$$
41+
42+
Substituting into the second equation yields
43+
44+
$$a_2 \equiv x_1 + x_2 p_1 \pmod{p_2},$$
45+
46+
which can be rewritten by subtracting $x_1$ and dividing by $p_1$ to get
47+
48+
$$\begin{array}{rclr}
49+
a_2 - x_1 &\equiv& x_2 p_1 &\pmod{p_2} \\
50+
(a_2 - x_1) r_{12} &\equiv& x_2 &\pmod{p_2} \\
51+
x_2 &\equiv& (a_2 - x_1) r_{12} &\pmod{p_2}
52+
\end{array}$$
53+
54+
Similarly we get that
55+
56+
$$x_3 \equiv ((a_3 - x_1) r_{13} - x_2) r_{23} \pmod{p_3}.$$
57+
58+
Now, we can clearly see an emerging pattern, which can be expressed by the following code:
59+
60+
```cpp
61+
for (int i = 0; i < k; ++i) {
62+
x[i] = a[i];
63+
for (int j = 0; j < i; ++j) {
64+
x[i] = r[j][i] * (x[i] - x[j]);
65+
66+
x[i] = x[i] % p[i];
67+
if (x[i] < 0)
68+
x[i] += p[i];
69+
}
70+
}
71+
```
72+
73+
So we learned how to calculate digits $x_i$ in $O(k^2)$ time. The number $a$ can now be calculated using the previously mentioned formula
74+
75+
$$a = x_1 + x_2 \cdot p_1 + x_3 \cdot p_1 \cdot p_2 + \ldots + x_k \cdot p_1 \cdots p_{k-1}$$
76+
77+
It is worth noting that in practice, we almost probably need to compute the answer $a$ using [Arbitrary-Precision Arithmetic](big-integer.md), but the digits $x_i$ (because they are small) can usually be calculated using built-in types, and therefore Garner's algorithm is very efficient.
78+
79+
## Implementation of Garner's Algorithm
80+
81+
It is convenient to implement this algorithm using Java, because it has built-in support for large numbers through the `BigInteger` class.
82+
83+
Here we show an implementation that can store big numbers in the form of a set of congruence equations.
84+
It supports addition, subtraction and multiplication.
85+
And with Garner's algorithm we can convert the set of equations into the unique integer.
86+
In this code, we take 100 prime numbers greater than $10^9$, which allows representing numbers as large as $10^{900}$.
87+
88+
```java
89+
final int SZ = 100;
90+
int pr[] = new int[SZ];
91+
int r[][] = new int[SZ][SZ];
92+
93+
void init() {
94+
for (int x = 1000 * 1000 * 1000, i = 0; i < SZ; ++x)
95+
if (BigInteger.valueOf(x).isProbablePrime(100))
96+
pr[i++] = x;
97+
98+
for (int i = 0; i < SZ; ++i)
99+
for (int j = i + 1; j < SZ; ++j)
100+
r[i][j] =
101+
BigInteger.valueOf(pr[i]).modInverse(BigInteger.valueOf(pr[j])).intValue();
102+
}
103+
104+
class Number {
105+
int a[] = new int[SZ];
106+
107+
public Number() {
108+
}
109+
110+
public Number(int n) {
111+
for (int i = 0; i < SZ; ++i)
112+
a[i] = n % pr[i];
113+
}
114+
115+
public Number(BigInteger n) {
116+
for (int i = 0; i < SZ; ++i)
117+
a[i] = n.mod(BigInteger.valueOf(pr[i])).intValue();
118+
}
119+
120+
public Number add(Number n) {
121+
Number result = new Number();
122+
for (int i = 0; i < SZ; ++i)
123+
result.a[i] = (a[i] + n.a[i]) % pr[i];
124+
return result;
125+
}
126+
127+
public Number subtract(Number n) {
128+
Number result = new Number();
129+
for (int i = 0; i < SZ; ++i)
130+
result.a[i] = (a[i] - n.a[i] + pr[i]) % pr[i];
131+
return result;
132+
}
133+
134+
public Number multiply(Number n) {
135+
Number result = new Number();
136+
for (int i = 0; i < SZ; ++i)
137+
result.a[i] = (int)((a[i] * 1l * n.a[i]) % pr[i]);
138+
return result;
139+
}
140+
141+
public BigInteger bigIntegerValue(boolean can_be_negative) {
142+
BigInteger result = BigInteger.ZERO, mult = BigInteger.ONE;
143+
int x[] = new int[SZ];
144+
for (int i = 0; i < SZ; ++i) {
145+
x[i] = a[i];
146+
for (int j = 0; j < i; ++j) {
147+
long cur = (x[i] - x[j]) * 1l * r[j][i];
148+
x[i] = (int)((cur % pr[i] + pr[i]) % pr[i]);
149+
}
150+
result = result.add(mult.multiply(BigInteger.valueOf(x[i])));
151+
mult = mult.multiply(BigInteger.valueOf(pr[i]));
152+
}
153+
154+
if (can_be_negative)
155+
if (result.compareTo(mult.shiftRight(1)) >= 0)
156+
result = result.subtract(mult);
157+
158+
return result;
159+
}
160+
}
161+
```

src/navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ search:
2929
- [Modular Inverse](algebra/module-inverse.md)
3030
- [Linear Congruence Equation](algebra/linear_congruence_equation.md)
3131
- [Chinese Remainder Theorem](algebra/chinese-remainder-theorem.md)
32+
- [Garner's Algorithm](algebra/garners-algorithm.md)
3233
- [Factorial modulo p](algebra/factorial-modulo.md)
3334
- [Discrete Log](algebra/discrete-log.md)
3435
- [Primitive Root](algebra/primitive-root.md)

0 commit comments

Comments
 (0)