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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy