Проблемы с использованием БПФ при умножении больших чисел

Я недавно пытался умножить кучу (до 10 ^ 5) очень маленьких чисел (порядка 10 ^ -4) друг на друга, поэтому я бы закончил в порядке 10 ^ — (4 * 10 ^ 5), что не вписывается ни в одну переменную.

Мой подход заключается в следующем:

  1. Умножьте каждое число на 10 ^ 8 и сохраните его в массиве, разделенном на степени 10, т. Е. Я делаю из него полином, определяемый степенями 10. Примером может быть: p = 0,1234 -> p * 10 ^ 8 = 12340000 -> A = {0, 0, 0, 0, 4, 3, 2, 1}.
  2. Умножьте эти массивы, используя FFT
  3. если результат

Это делается несколько раз для небольшого количества разных случаев.
В конце я хочу знать, какова доля одного такого продукта над суммой всех продуктов с точностью до 10 ^ -6. Для этого между шагами 2 и 3 результат добавляется в массив сумм, который в конце также iFFTed. Поскольку требуемая точность довольно низкая, я не делю многочлены, а принимаю только первые несколько чисел в целое число.

Моя проблема заключается в следующем: БПФ и / или iFFT не работает должным образом! Я новичок в этом деле и только один раз реализовал БПФ. Мой код выглядит следующим образом (C ++ 14):

#include <cstdio>
#include <vector>
#include <cmath>
#include <complex>
#include <iostream>
using namespace std;

const double PI = 4*atan(1.0);

vector<complex<double>> FFT (vector<complex<double>> A, long N)
{
vector<complex<double>> Ans(0);
if(N==1)
{
Ans.push_back(A[0]);
return Ans;
}
vector<complex<double>> even(N/2);
vector<complex<double>> odd(N/2);
for(long i=0; i<(N/2); i++)
{
even[i] = A[2*i];
odd[i] = A[2*i+1];
}
vector<complex<double>> L1 = FFT(even, N/2);
vector<complex<double>> L2 = FFT(odd, N/2);
for(long i=0; i<N; i++)
{
complex<double> z(cos(2*PI*i/N),sin(2*PI*i/N));
long k = i%(N/2);
Ans.push_back(L1[k] + z*L2[k]);
}
return Ans;
}

vector<complex<double>> iFFT (vector<complex<double>> A, long N)
{
vector<complex<double>> Ans(0);
if(N==1)
{
Ans.push_back(A[0]);
return Ans;
}
vector<complex<double>> even(N/2);
vector<complex<double>> odd(N/2);
for(long i=0; i<(N/2); i++)
{
even[i] = A[2*i];
odd[i] = A[2*i+1];
}
vector<complex<double>> L1 = FFT(even, N/2);
vector<complex<double>> L2 = FFT(odd, N/2);
for(long i=0; i<N; i++)
{
complex<double> z(cos(-2*PI*i/N),sin(-2*PI*i/N));
complex<double> inv(double(1.0/N), 0);
long k = i%(N/2);
Ans.push_back(inv*(L1[k]+z*L2[k]));
}
return Ans;
}

vector<complex<double>> PMult (vector<complex<double>> A, vector<complex<double>> B, long L)
{
vector<complex<double>> Ans(L);
for(int i=0; i<L; i++)
{
Ans[i] = A[i]*B[i];
}
return Ans;
}

vector<complex<double>> DtoA (double x)
{
vector<complex<double>> ans(8);
long X = long(x*10000000);
ans[0] = complex<double>(double(X%10), 0.0); X/=10;
ans[1] = complex<double>(double(X%10), 0.0); X/=10;
ans[2] = complex<double>(double(X%10), 0.0); X/=10;
ans[3] = complex<double>(double(X%10), 0.0); X/=10;
ans[4] = complex<double>(double(X%10), 0.0); X/=10;
ans[5] = complex<double>(double(X%10), 0.0); X/=10;
ans[6] = complex<double>(double(X%10), 0.0); X/=10;
ans[7] = complex<double>(double(X%10), 0.0);
return ans;
}

int main()
{
vector<vector<complex<double>>> W;
int T, N, M;
double p;
scanf("%d", &T);
while( T-- )
{
scanf("%d %d", &N, &M);
W.resize(N);
for(int i=0; i<N; i++)
{
cin >> p;
W[i] = FFT(DtoA(p),8);
for(int j=1; j<M; j++)
{
cin >> p;
W[i] = PMult(W[i], FFT(DtoA(p),8), 8);
}
}
vector<complex<double>> Sum(8);
for(int j=0; j<8; j++) Sum[j]=W[0][j];
for(int i=1; i<N; i++)
{
for(int j=0; j<8; j++)
{
Sum[j]+=W[i][j];
}
}

W[0]=iFFT(W[0],8);
Sum=iFFT(Sum, 8);

long X=0;
long Y=0;
int a;
for(a=0; Sum[a].real()!=0; a++);
for(int i=a; i<8; i++)
{
Y*=10;
Y=Y+long(Sum[i].real());
}
for(int i=a; i<8; i++)
{
X*=10;
X=X+long(W[0][i].real());
}
double ans = 0.0;
if(Y) ans=double(X)/double(Y);
printf("%.7f\n", ans);
}
}

Я заметил, что для массива, который состоит только из нулей, кроме одной записи, FFT возвращает массив с более чем одной непустой записью. Кроме того, после того, как iFFT сделан, результат все еще содержит записи с ненулевой мнимой частью.

Может кто-нибудь найти ошибку или дать мне совет, где я мог бы упростить решение? Поскольку я хочу, чтобы это было быстро, я не хотел бы делать наивное умножение. Будет ли алгоритм Карацубы лучше, так как мне не нужны комплексные числа?

2

Решение

Я проверил с моей старой реализацией Java для FFT (извините за неприятный код). Я нашел следующие вещи в ваших функциях FFT и DtoA:

  • 0 отсутствовал в вашей функции DtoA
  • порядок, в котором вы устанавливаете коэффициенты в векторе, был изменен (возможно, это было сделано намеренно по некоторым причинам)
  • фаза «объединения» алгоритма Кули Тьюки была неправильной: первая половина слагаемых имеет вид a + b * c и вторая половина имеет форму a - b * c,

Код ниже неэффективен, но должен быть понятным.

#include <iostream>
#include <vector>
#include <complex>
#include <exception>
#include <cmath>

using namespace std;

vector<complex<double>> fft(const vector<complex<double>> &x) {
int N = x.size();

// base case
if (N == 1) return vector<complex<double>> { x[0] };

// radix 2 Cooley-Tukey FFT
if (N % 2 != 0) { throw new std::exception(); }

// fft of even terms
vector<complex<double>> even, odd;
for (int k = 0; k < N/2; k++) {
even.push_back(x[2 * k]);
odd.push_back(x[2 * k + 1]);
}
vector<complex<double>> q = fft(even);
vector<complex<double>> r = fft(odd);

// combine
vector<complex<double>> y;
for (int k = 0; k < N/2; k++) {
double kth = -2 * k * M_PI / N;
complex<double> wk = complex<double>(cos(kth), sin(kth));
y.push_back(q[k] + (wk * r[k]));
}
for (int k = 0; k < N/2; k++) {
double kth = -2 * k * M_PI / N;
complex<double> wk = complex<double>(cos(kth), sin(kth));
y.push_back(q[k] - (wk * r[k])); // you didn't do this
}
return y;
}

vector<complex<double>> DtoA (double x)
{
vector<complex<double>> ans(8);
long X = long(x*100000000); // a 0 was missing  here
ans[7] = complex<double>(double(X%10), 0.0); X/=10;
ans[6] = complex<double>(double(X%10), 0.0); X/=10;
ans[5] = complex<double>(double(X%10), 0.0); X/=10;
ans[4] = complex<double>(double(X%10), 0.0); X/=10;
ans[3] = complex<double>(double(X%10), 0.0); X/=10;
ans[2] = complex<double>(double(X%10), 0.0); X/=10;
ans[1] = complex<double>(double(X%10), 0.0); X/=10;
ans[0] = complex<double>(double(X%10), 0.0);
return ans;
}

int main ()
{
double n = 0.1234;
auto nComplex = DtoA(n);
for (const auto &e : nComplex) {
std::cout << e << " ";
}
std::cout << std::endl;

try {
auto nFFT = fft(nComplex);

for (const auto &e : nFFT) {
std::cout << e << " ";
}
}
catch (const std::exception &e) {
std::cout << "exception" << std::endl;
}
return 0;
}

Вывод программы (я проверил это с помощью Octave, это то же самое):

(1,0) (2,0) (3,0) (4,0) (0,0) (0,0) (0,0) (0,0)
(10,0) (-0.414214,-7.24264) (-2,2) (2.41421,-1.24264) (-2,0) (2.41421,1.24264) (-2,-2) (-0.414214,7.24264)

Надеюсь это поможет.

РЕДАКТИРОВАТЬ:

Что касается обратного БПФ, вы можете продемонстрировать, что

iFFT(x) = (1 / N) conjugate( FFT( conjugate(x) ) )

где N — количество элементов в массиве x. Таким образом, вы можете использовать функцию fft для вычисления ifft:

vector<complex<double>> ifft(const vector<complex<double>> &vec) {
std::vector<complex<double>> conj;
for (const auto &e : vec) {
conj.push_back(std::conj(e));
}

std::vector<complex<double>> vecFFT = fft(conj);

std::vector<complex<double>> result;
for (const auto &e : vecFFT) {
result.push_back(std::conj(e) / static_cast<double>(vec.size()));
}

return result;
}

Вот модифицированный основной:

int main ()
{
double n = 0.1234;
auto nComplex = DtoA(n);
for (const auto &e : nComplex) {
std::cout << e << " ";
}
std::cout << std::endl;

auto nFFT = fft(nComplex);
for (const auto &e : nFFT)
std::cout << e << " ";
std::cout << std::endl;

auto iFFT = ifft(nFFT);
for (const auto &e : iFFT)
std::cout << e << " ";
return 0;
}

и вывод:

(1,0) (2,0) (3,0) (4,0) (0,0) (0,0) (0,0) (0,0)
(10,0) (-0.414214,-7.24264) (-2,2) (2.41421,-1.24264) (-2,0) (2.41421,1.24264) (-2,-2) (-0.414214,7.24264)
(1,-0) (2,-1.08163e-16) (3,7.4688e-17) (4,2.19185e-16) (0,-0) (0,1.13882e-16) (0,-7.4688e-17) (0,-2.24904e-16)

Обратите внимание, что цифры как 1e-16 в значительной степени 0 (двойники не идеальны в оборудовании).

1

Другие решения


По вопросам рекламы [email protected]