A bit of theory. The discrete Fourier transform is a linear transform defined by a Vandermonde matrix. The matrix consists of degrees of a primitive root of a unit of degree n , where n is the length of the vectors and the dimension of the square matrix. It's easier to show how it is built, than to describe in words.
The inverse transformation matrix is ββgenerated by the powers of the element inverse to the selected primitive root of one.
Fourier transform over a finite number field is somehow unpopular with Roisse coders, but it is and is used in cryptography. The difference is that the root of the unit is not extracted in the complex field, but in the field of residue classes. The rest is all the same.
Convolution can be viewed as the multiplication of polynomials with coefficients from a field or a finite ring of classes of residues. If we apply the normalization (transfer the transfer when the digits overflow), then we get the multiplication of long numbers. As I understand it, convolutions and Fourier transforms are smart enough to expand the final field when multiplying numbers if there are not enough digits in it. Here it is necessary to make an important explanation of the example.
We want to find the Fourier transform of the vector (4, 3, 2, 1) over the Galois number field GF(5) characteristic 5. The Vandermonde matrix is ββgenerated by the primitive root of this field, i.e. two.
Note: not every element that is mutually simple with the module on which the field is built is a primitive root. Russian pedivikiya is lying. For example, powers of two will not cover all elements of the GF(7) field.
As a result of the action of this matrix on the vector (4, 3, 2, 1) , a vector is obtained (0, 1, 2, 3) . But what if we want to find the Fourier transform of the vector (7, 1, 7, 1) ? In the field GF(5) there is no element 7, but there is an equivalence class [2] containing a seven. How does the conversion work in this case?
A similar situation will arise if we multiply the numbers with the help of these transformations. For example, multiply the number 7777 (vector (7, 7, 7, 7) ) by itself. Here again, the GF(5) field is used, in which again there is no seven.
The convolution works like this: we supplement the two vector data with zeros ( their length doubles ), apply the Fourier transform to them, multiply the two vectors obtained by term, and apply the inverse Fourier transform to the result. This operation works as a multiplication of two polynomials with given coefficients from the field.
Example. Find the convolution (2, 1) and (1, 1) . Let's add vectors with zeros, we get a' = (2, 1, 0, 0) and b' = (1, 1, 0, 0) . Apply the direct transformation to them: F(a') = (3, 4, 1, 0) and F(b') = (2, 3, 0, 4) . Multiply the last vectors by term: (1, 2, 0, 0) . Apply to the resulting vector the inverse transform: (2, 3, 1, 0) .
My implementation of Fourier transforms, convolutions and multiplications of long numbers:
#include <iostream> #include <vector> // ΠΠΎΠ·Π²Π΅Π΄Π΅Π½ΠΈΠ΅ a Π² ΡΡΠ΅ΠΏΠ΅Π½Ρ b ΠΏΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ p int powmod (int a, int b, int p) { int res = 1; while (b) if (b & 1) res = int ((long long) res * a % p), --b; else a = int ((long long) a * a % p), b >>= 1; return res; } // ΠΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ°ΡΠ½ΠΎΠ³ΠΎ ΠΊ a ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΠΏΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ n int inverse(int a, int n) { int b0 = n, t, q; int x0 = 0, x1 = 1; if (n == 1) return 1; while (a > 1) { q = a / n; t = n, n = a % n, a = t; t = x0, x0 = x1 - q * x0, x1 = t; } if (x1 < 0) x1 += b0; return x1; } // ΠΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π½ΠΎΠ³ΠΎ ΠΊΠΎΡΠ½Ρ ΡΠΈΡΠ»ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΠ»Ρ // (Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ DFT ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡΠ½ΡΠ΅ ΡΠΈΡΠ»Π°, ΠΏΠΎΡΡΠΎΠΌΡ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠΌ Π°Π»Π³ΠΎΡΠΈΡΠΌ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Π² Zp) // ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅: p - ΠΏΡΠΎΡΡΠΎΠ΅ int generator (int p) { std::vector<int> fact; int phi = p - 1, n = phi; for (int i = 2; i * i <= n; ++i) if (n % i == 0) { fact.push_back (i); while (n % i == 0) n /= i; } if (n > 1) fact.push_back (n); for (int res = 2; res <= p; ++res) { bool ok = true; for (size_t i = 0; i < fact.size() && ok; ++i) ok &= powmod (res, phi / fact[i], p) != 1; if (ok) return res; } return -1; } // Π Π΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΏΡΡΠΌΠΎΠ³ΠΎ DFT Π½Π°Π΄ Zp // ΠΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π²Π΅ΠΊΡΠΎΡ Π΄Π»ΠΈΠ½Ρ n, ΡΡΠ° Π΄Π»ΠΈΠ½Π° ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΏΡΠΈ Π²ΡΡΠΈΡΠ»Π΅Π½ΠΈΠΈ // ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π½ΠΎΠ³ΠΎ ΠΊΠΎΡΠ½Ρ ΠΏΠΎΠ»Ρ // ΠΡΡΠΈΡΠ»Π΅Π½ΠΈΡ ΠΏΡΠΈΠ²ΠΎΠ΄ΡΡΡΡ ΠΏΠΎ mod (n + 1), Ρ.ΠΊ. Π΄Π»ΠΈΠ½Π° Π²Π΅ΠΊΡΠΎΡΠ° Π½Π° 1 ΠΌΠ΅Π½ΡΡΠ΅ p, // Π³Π΄Π΅ p: Zp std::vector<int> forward_dft (std::vector<int>& a) { std::vector<int> result; int prim_root = generator (a.size()); // ΠΠΎΡΠ΅Π½Ρ ΡΡΠ΅ΠΏΠ΅Π½ΠΈ n ΠΈΠ· Π΅Π΄ΠΈΠ½ΠΈΡΡ for (int i = 0; i < a.size(); i++) { int sum = 0; int power = 0; for (int j = 0; j < a.size(); j++) { int power_of_root = powmod (prim_root, power, a.size() + 1); sum += power_of_root * a[j]; // ΠΠ°ΠΊΠ°ΠΏΠ»ΠΈΠ²Π°Π΅ΠΌ ΡΡΠΌΠΌΡ ΠΏΡΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΡΡΠΎΠΊΠΈ ΠΌΠ°ΡΡΠΈΡΡ Π½Π° Π²Π΅ΠΊΡΠΎΡ power += i; // Π£Π²Π΅Π»ΠΈΡΠΈΠ²Π°Π΅ΠΌ ΡΡΠ΅ΠΏΠ΅Π½Ρ, Π² ΠΊΠΎΡΠΎΡΡΡ Π²ΠΎΠ·Π²ΠΎΠ΄ΠΈΡΡΡ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π½ΡΠΉ ΠΊΠΎΡΠ΅Π½Ρ ΠΈΠ· Π΅Π΄ΠΈΠ½ΠΈΡΡ } result.push_back(sum % (a.size() + 1)); // ΠΠ°Π±ΡΠ°Π»ΠΈ ΡΡΠΌΠΌΡ, ΠΏΡΠΈΠ²ΠΎΠ΄ΠΈΠΌ ΠΏΠΎ mod p, p = Π΄Π»ΠΈΠ½Π° Π²Π΅ΠΊΡΠΎΡΠ° + 1 } return result; } std::vector<int> inversed_dft (std::vector<int>& a) { std::vector<int> result; int prim_root = generator (a.size()); // ΠΠΎΡΠ΅Π½Ρ ΡΡΠ΅ΠΏΠ΅Π½ΠΈ n ΠΈΠ· Π΅Π΄ΠΈΠ½ΠΈΡΡ int inv_prim_root = inverse (prim_root, a.size() + 1); // ΠΠ±ΡΠ°ΡΠ½ΡΠΉ ΠΊ Π½Π°ΠΉΠ΄Π΅Π½Π½ΠΎΠΌΡ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π½ΠΎΠΌΡ ΠΊΠΎΡΠ½Ρ int inv_n = inverse (a.size(), a.size() + 1); // ΠΠ±ΡΠ°ΡΠ½ΡΠΉ ΠΊ n ΠΏΠΎ mod (n + 1) for (int i = 0; i < a.size(); i++) { int sum = 0; int power = 0; for (int j = 0; j < a.size(); j++) { int power_of_inv_root = powmod (inv_prim_root, power, a.size() + 1); sum += inv_n * power_of_inv_root * a[j]; // ΠΠ°ΠΊΠ°ΠΏΠ»ΠΈΠ²Π°Π΅ΠΌ ΡΡΠΌΠΌΡ ΠΏΡΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΡΡΠΎΠΊΠΈ ΠΌΠ°ΡΡΠΈΡΡ Π½Π° Π²Π΅ΠΊΡΠΎΡ power += i; // Π£Π²Π΅Π»ΠΈΡΠΈΠ²Π°Π΅ΠΌ ΡΡΠ΅ΠΏΠ΅Π½Ρ, Π² ΠΊΠΎΡΠΎΡΡΡ Π²ΠΎΠ·Π²ΠΎΠ΄ΠΈΡΡΡ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π½ΡΠΉ ΠΊΠΎΡΠ΅Π½Ρ ΠΈΠ· Π΅Π΄ΠΈΠ½ΠΈΡΡ } result.push_back(sum % (a.size() + 1)); // ΠΠ°Π±ΡΠ°Π»ΠΈ ΡΡΠΌΠΌΡ, ΠΏΡΠΈΠ²ΠΎΠ΄ΠΈΠΌ ΠΏΠΎ mod p, p = Π΄Π»ΠΈΠ½Π° Π²Π΅ΠΊΡΠΎΡΠ° + 1 } return result; } // Π‘Π²Π΅ΡΡΠΊΠ° Π΄Π²ΡΡ
Π²Π΅ΠΊΡΠΎΡΠΎΠ² ΠΏΡΠΈ ΠΏΠΎΠΌΠΎΡΠΈ Π΄ΠΈΡΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ Π€ΡΡΡΠ΅ std::vector<int> convolution (std::vector<int> a, std::vector<int> b) { a.insert(a.end(), a.size(), 0); b.insert(b.end(), b.size(), 0); a = forward_dft(a); b = forward_dft(b); std::vector<int> result(a.size(), 0); for(int i = 0; i < result.size(); i++) result[i] = a[i] * b[i]; result = inversed_dft(result); return result; } // Π£ΠΌΠ½ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π΄Π²ΡΡ
Π±ΠΎΠ»ΡΡΠΈΡ
ΡΠΈΡΠ΅Π» std::vector<int> multiply (std::vector<int> a, std::vector<int> b) { std::vector<int> result = convolution(a, b); // ΠΠΎΡΠΌΠ°Π»ΠΈΠ·Π°ΡΠΈΡ, Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΏΠ΅ΡΠ΅Π½ΠΎΡΠΎΠ² int carry = 0; for (int i = 0; i < result.size(); i++) { result[i] += carry; carry = result[i] / 10; result[i] %= 10; } return result; } int main() { int a[] = {1, 2, 1, 2, 1, 2, 1, 2}; int b[] = {7, 5, 7, 5, 7, 5, 7, 5}; std::vector<int> u(std::begin(a), std::end(a)); std::vector<int> v(std::begin(b), std::end(b)); std::vector<int> result = multiply(u, v); for(int i = 0; i < result.size(); i++) std::cout << result[i] << " "; /*std::vector<int> u(std::begin(a), std::end(a)); std::vector<int> v(std::begin(b), std::end(b)); std::vector<int> result = convolution(u, v); for(int i = 0; i < result.size(); i++) std::cout << result[i] << " ";*/ /*int a[] = {4, 3, 2, 1}; std::vector<int> v(std::begin(a), std::end(a)); std::vector<int> result = forward_dft(v); std::cout << "[DEBUG]: Forward DFT" << std::endl; for(int i = 0; i < result.size(); i++) std::cout << result[i] << " "; std::cout << std::endl; result = inversed_dft(result); std::cout << "[DEBUG]: Inversed DFT" << std::endl; for(int i = 0; i < result.size(); i++) std::cout << result[i] << " ";*/ } Multiplying long numbers in the main function does not work correctly. At first I thought that this was due to the fact that the algorithm was working on a finite field, in which there were not some βdigitsβ of a large number. But it can be seen that when multiplying the numbers 12121212 by 75757575, a field of sufficient size is used to contain the correspondence for the digit 7. There, when the number 12 is squared, the algorithm works almost correctly, i.e. a vector is obtained (1, 4, 4, 0) , but an extra zero occurs due to the dimension of the vectors and the padding with zeros. Where am I wrong?
