// 2^(2^34 - 1) = 2^17179869183 を計算する。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <assert.h>
#include <stdarg.h>
#include <vector>
#include <utility>
#include <numeric>
#include <omp.h>
// [DBGSW] 中間結果表示
//#define SHOW_ITM
// [DBGSW] I表示
//#define SHOW_I
// [DBGSW] SIN_TABLE作成状況表示
//#define SHOW_SIN_TABLE
// [DBGSW] 消費時間表示
#define SHOW_CSM_TIME
// [DBGSW] 計算途中結果の保存
//#define SAVE_ITM_RESULT
typedef uint32_t I_TYPE;
// [CONF] 基本SINテーブルの最大要素数
#define BASIC_SINTBL_P_MAX 29
// [CONF] 多倍長バッファ1要素の10進数での桁数
const int FIG = 5;
// [CONF] 10^FIG
const I_TYPE RADIX = 100000;
// [CONF] 使用するCPUのL2キャッシュサイズ(2の冪 Byte/コア)の指数
#ifdef NDEBUG
#define EXPN_L2CHACHE_SZ 18 /* 256 KB */
#else
#define EXPN_L2CHACHE_SZ 8 /* 256 Byte (DEBUG) */
#endif
// [CONF] 同一L2キャッシュを共用するスレッドの数(2の冪)の指数
// Core i7 (HT有り)では1、Core i5 (HT無し)では0
#define EXPN_L2_SHARING_THREADS 1
// [CONF] sizeof(複素数) (2の冪)の指数(double 2個で16 Byte)
#define EXPN_SIZEOF_COMPLEX 4
static_assert((size_t)1 << EXPN_SIZEOF_COMPLEX == 2 * sizeof(double), "*** ERR ***");
// L2キャッシュにsrcとdstの両方が複素数が乗る最大要素数
#define EXPN_CAPACITY (EXPN_L2CHACHE_SZ - EXPN_SIZEOF_COMPLEX - EXPN_L2_SHARING_THREADS - 1)
#ifdef _WIN32
int fopen_s_wrp(FILE** const p_fp, const char* const fpath, const char* const mode)
{
return (int)fopen_s(p_fp, fpath, mode);
}
int sprintf_s_wrp(char buf[], const size_t bufsz, const char* const fmt, ...)
{
va_list ap;
int nRet;
va_start(ap, fmt);
nRet = vsprintf_s(buf, bufsz, fmt, ap);
va_end(ap);
return nRet;
}
#else
int fopen_s_wrp(FILE** const p_fp, const char* const fpath, const char* const mode)
{
*p_fp = fopen(fpath, mode);
return (*p_fp == NULL);
}
int sprintf_s_wrp(char buf[], const size_t bufsz, const char* const fmt, ...)
{
va_list ap;
int nRet;
va_start(ap, fmt);
nRet = vsprintf(buf, fmt, ap);
va_end(ap);
return nRet;
}
#define _countof(arr) (sizeof(arr) / sizeof(arr[0]))
#endif
/// 実部と虚部が交代に並ぶ形式の複素数リストを表示する。
void show_re_im_seq(const std::vector<double>& s, const int p)
{
const size_t hsz = (size_t)1 << (p - 1);
printf("Re:");
for (size_t i = 0; i < hsz; i++) {
printf(" % 12f", s[2 * i]);
}
printf("\n");
printf("Im:");
for (size_t i = 0; i < hsz; i++) {
printf(" % 12f", s[2 * i + 1]);
}
printf("\n");
}
/// 実部と虚部が交代に並ぶ形式の複素数リストを表示する。
void show_re_im_seq(const std::vector<I_TYPE>& s, const int p)
{
char fmt[128];
sprintf_s_wrp(fmt, _countof(fmt), "%%0%dlu", FIG);
const size_t hsz = (size_t)1 << (p - 1);
printf("Re:");
for (size_t i = 0; i < hsz; i++) {
printf(fmt, s[2 * i]);
}
printf("\n");
printf("Im:");
for (size_t i = 0; i < hsz; i++) {
printf(fmt, s[2 * i + 1]);
}
printf("\n");
}
namespace priroot
{
// データの桁数の桁数を表す型
typedef int expn_t;
// データの桁数を表す型
typedef int64_t pow_t;
#ifdef SHOW_SIN_TABLE
size_t g_sin_tblent_cnt = 0;
#endif
/// 1の原始n乗根を表す。
/// nの剰余計算を高速化するため、nは2^pに制限する。
class basic_nth_root
{
/*const*/ expn_t sv_p;
/*const*/ pow_t t360;
/*const*/ pow_t m360;
/*const*/ pow_t t090;
std::vector<double> m_sin;
public:
basic_nth_root() : sv_p(0), t360(0), m360(0), t090(0) { }
basic_nth_root(const expn_t p)
: sv_p(p), t360((pow_t)1 << p), m360(t360 - 1), t090(t360 / 4), m_sin((size_t)(t360 / 4 + 1))
{
if (p >= 2) {
assert(t360 >= 4);
t090 = t360 / 4;
const double k = 0.5 * M_PI;
for (pow_t i = 1; i < t090; i++) {
m_sin[i] = sin((k * (double)i) / (double)t090);
}
#ifdef SHOW_SIN_TABLE
g_sin_tblent_cnt += t090;
printf("******* %s: p=%d table made. (+%zu, %zu)\n", __FUNCTION__, p, t090, g_sin_tblent_cnt);
#endif
m_sin[0] = 0.0;
m_sin[t090] = 1.0;
}
}
expn_t p() const { return sv_p; }
pow_t n() const { return t360; }
/// 1の原始n乗根のk乗を取得する。
void pow(const pow_t k_, double& re, double& im) const {
assert(k_ >= 0);
pow_t k = k_;
k &= m360; // nの剰余
if (sv_p < 2) {
assert(k == 0 || k == 1);
im = 0.0;
re = (k == 0) ? 1.0 : -1.0;
return;
}
assert(0 <= k && k < 4 * t090);
if (k <= t090) {
re = m_sin[t090 - k];
im = m_sin[k];
}
else if (k <= 2 * t090) {
re = -m_sin[k - t090]; // = t090 - (2 * t090 - k)
im = m_sin[2 * t090 - k];
}
else if (k <= 3 * t090) {
re = -m_sin[3 * t090 - k]; // = t090 - (k - 2 * t090)
im = -m_sin[k - 2 * t090];
}
else {
re = m_sin[k - 3 * t090]; // = t090 - (4 * t090 - k)
im = -m_sin[4 * t090 - k];
}
}
};
/// 1の原始n乗根を表す。
class nth_root
{
expn_t max_p;
// 1の原始2^p乗根のr乗を収めたテーブルのベクタ
// basic_tables[p]が1の原始2^p乗根のテーブルを表す。
std::vector<basic_nth_root> basic_tables;
// 1の原始2^(p+m)乗根のみを収めたベクタ
// high_order_root_tables[m]が1の原始2^(p+m)乗根の値。(こちらはベクタ要素がテーブルではなく値を表す。)
std::vector<std::pair<double, double> > high_order_root_values;
/*
(NOTE)
1の原始n乗根をw_nと書くことにすると、{ b_n }を0か1からなる数列、rを適当な整数として
k = b_0 + 2 * b_1 + (2^2) * b_2 + ... * (2^(m-1)) * b_(m-1) + { (2^m) * r }
であるとき、
(w_(2^(p+m)))^k = w_(2^(p+m))^b_0 * w_(2^(p+(m-1)))^b_1 * w_(2^(p+(m-2))^b_2 * ... * w_(2^(p+1))^b_(m-1) * { w_p^(rの2^pに対する剰余) }
と書けるので、
w_p^r (r=0..(2^p-1)) の値
w_2^(p+q) (q=1..m) の値
があれば、(w^(p+m))^k を計算できる。
前者は任意の指数rについて計算できねばならないため、テーブルで実現する場合はO(2^p)オーダーのメモリを要するが、
後者はm個の値だけ記憶しておけば良いため、O(m)のオーダーで済む。そのかわりkが与えられる都度値の掛け算が発生する。
*/
public:
nth_root() : max_p(0) { }
/// 1の原始2^p乗根のテーブルを用意する。
void alloc_table(const expn_t p) {
if (p > max_p) {
// (テーブル生成済のpより大きいp)
// まずテーブル生成を検討する。
if (p <= BASIC_SINTBL_P_MAX) {
// (pが制限値以下)
// [max_p+1..p]のテーブルを新規生成
basic_tables.resize((size_t)(p + 1));
for (expn_t i = max_p + 1; i <= p; i++) {
basic_tables[i] = basic_nth_root(i);
}
max_p = p;
}
}
if (p > max_p) {
// (任意指数に対するテーブル化が不能な大きさのp)
// 指数k=1の値のみ記憶する。
const expn_t m = p - max_p;
if ((size_t)m >= high_order_root_values.size()) {
high_order_root_values.resize((size_t)(m + 1));
std::pair<double, double>& ent = high_order_root_values[m]; // Alias
const pow_t big_n = (pow_t)1 << p;
const double arg = (2 * M_PI) / big_n;
ent.first = cos(arg); // 実部
ent.second = sin(arg); // 虚部
}
}
}
/// 1の原始2^p乗根のk乗を取得する。
void pow(const expn_t p, const pow_t k, double& re, double& im) const {
if (p <= max_p) {
// (w_(2^p))^k算出
basic_tables[p].pow(k, re, im);
}
else {
const expn_t m = p - max_p;
const pow_t r = k >> m;
const pow_t b = k & (((pow_t)1 << m) - 1);
// (w_(2^max_p))^r算出
basic_tables[max_p].pow(r, re, im);
// w_(2^(max_p + i))を掛け合わせる
pow_t mask = (pow_t)1 << (m - 1);
for (expn_t i = 1; i <= m; i++) {
assert(mask > 0);
if ((b & mask) != 0) {
const std::pair<double, double>& ent = high_order_root_values[i]; // Alias
const double tmp_re = re * ent.first - im * ent.second;
const double tmp_im = re * ent.second + im * ent.first;
re = tmp_re;
im = tmp_im;
}
mask >>= 1;
}
assert(mask == 0);
}
}
};
} // namespace priroot
namespace fftn
{
using namespace priroot;
/// ビット数bitwのBTR演算
pow_t btr(const pow_t x, const expn_t bitw) {
pow_t y = 0;
for (expn_t i = 0; i <= (bitw - 1) / 2; i++) {
const pow_t b = ((pow_t)1 << i) & x;
y |= (b << ((bitw - 1) - 2 * i));
}
for (expn_t i = (bitw - 1) / 2 + 1; i < bitw; i++) {
const pow_t b = ((pow_t)1 << i) & x;
y |= (b >> (2 * i - (bitw - 1)));
}
return y;
}
/// フーリエ変換
/// DFTの原理式
/// X(k) = Σ[j=0..t360-1]{ x(j) * w^(j*k) } (k=0..t360-1)
/// より
/// X(k) = Σ[j=0..t180-1]{ x(2*j) * w^(2*j*k) } + Σ[j=0..t180-1]{ x(2*j+1) * w^((2*j+1)*k) }
/// = Σ[j=0..t180-1]{ x(2*j) * w^(2*j*k) } + w^k * Σ[j=0..t180-1]{ x(2*j+1) * w^(2*j*k) }
/// さらに、kをk+t180に置き換えると以下が成立する。
/// X(k+t180) = Σ[j=0..t180-1]{ x(2*j) * w^(2*j*(k + t180) } + w^(k+t180) * Σ[j=0..t180-1]{ x(2*j+1) * w^(2*j*(k+t180) }
/// = Σ[j=0..t180-1]{ x(2*j) * w^(2*j*k) } - w^k * Σ[j=0..t180-1]{ x(2*j+1) * w^(2*j*k) }
/// よって、
/// X_even(k) = Σ[j=0..t180-1]{ x(2*j ) * w^(2*j*k) } (k=0..t180-1)
/// X_odd(k) = Σ[j=0..t180-1]{ x(2*j+1) * w^(2*j*k) } (k=0..t180-1)
/// として、X(k) (k=0..t360)は以下のバタフライ演算で表せる。
/// X(k ) = X_even(k) + w^k * X_odd(k) (k=0..t180-1)
/// X(k+t180) = X_even(k) - w^k * X_odd(k) (k=0..t180-1)
/// これを、大元の時間領域データx(j)の実部がbuf[2*j]、虚部がbuf[2*j + 1]に格納されている前提で行う。
void fouri(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w)
{
// 再帰終端判定
if (p == 0) {
// (要素数2^0のフーリエ変換)
// 単一要素x(j)のフーリエ変換はx(j)なので何もしない。
return;
}
const pow_t t360 = (pow_t)1 << p;
const pow_t t180 = (pow_t)1 << (p - 1);
w.alloc_table(p);
// X_even(k)取得
// [0..t180-1]に対し、w^2kを回転因子とするフーリエ変換をin-placeで実行する。
fouri(buf, p - 1, w);
// X_odd(k)取得
// [t180..t360-1]に対し、w^2kを回転因子とするフーリエ変換をin-placeで実行する。
// t180に2を掛けているのは、実部と虚部の2要素セットでt001であることによる。
fouri(buf + 2 * t180, p - 1, w);
// バタフライ演算
// X(k ) = X_even(k) + w^k * X_odd(k)
// X(k+t180) = X_even(k) - w^k * X_odd(k)
for (pow_t k = 0; k < t180; k++) {
const pow_t ofs_even = k << 1;
const pow_t ofs_odd = (k + t180) << 1;
// X_even(k)
const double X_even_k_re = buf[ofs_even];
const double X_even_k_im = buf[ofs_even + 1];
// X_odd(k)
double X_odd_k_re = buf[ofs_odd];
double X_odd_k_im = buf[ofs_odd + 1];
// X_odd(k) * w^k 算出、ここでwは1の原始t360乗根
double wk_re, wk_im;
w.pow(p, k, wk_re, wk_im);
const double re = X_odd_k_re * wk_re - X_odd_k_im * wk_im;
const double im = X_odd_k_re * wk_im + X_odd_k_im * wk_re;
// X(k ) = X_even(k) + w^k * X_odd(k) = X_even(k) + (re + i * im)
buf[ofs_even] = X_even_k_re + re;
buf[ofs_even + 1] = X_even_k_im + im;
// X(k+t180) = X_even(k) - w^k * X_odd(k) = X_even(k) - (re + i * im)
buf[ofs_odd] = X_even_k_re - re;
buf[ofs_odd + 1] = X_even_k_im - im;
}
}
/// Bit reverse orderにin-placeで並べ替える。
void sort_by_btr(
std::vector<double>::iterator buf,
const expn_t p)
{
const pow_t t360 = (pow_t)1 << p;
for (pow_t i = 0; i < t360; i++) {
const pow_t btr_i = btr(i, p);
// (btr_i, i)と(i, btr_i)の両方についてswapすると元に戻ってしまうので、
// 片方のみswap対象とする。
if (btr_i < i) {
std::swap(buf[2 * btr_i], buf[2 * i]); // 実部
std::swap(buf[2 * btr_i + 1], buf[2 * i + 1]); // 虚部
}
}
}
/// 逆フーリエ変換
/// フーリエ変換とは逆の以下のバタフライ演算を行う。
/// x(k ) = (x_even(k) + w^(-k) * x_odd(k)) / t360 (k=0..t180-1)
/// x(k+t180) = (x_even(k) - w^(-k) * x_odd(k)) / t360 (k=0..t180-1)
/// これを、大元の周波数領域データX(k)の実部がbuf[2*k]、虚部がbuf[2*k+1]に格納されている前提で行う。
void fouriinv(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w)
{
// 再帰終端判定
if (p == 0) {
// (要素数2^0のフーリエ変換)
// 単一要素x(j)のフーリエ変換はx(j)なので何もしない。
return;
}
const pow_t t360 = (pow_t)1 << p;
const pow_t t180 = (pow_t)1 << (p - 1);
w.alloc_table(p);
// x_even(k)取得
// [0..t180-1]に対し、w^2kを回転因子とするフーリエ変換をin-placeで実行する。
fouriinv(buf, p - 1, w);
// x_odd(k)取得
// [t180..t360-1]に対し、w^2kを回転因子とするフーリエ変換をin-placeで実行する。
// t180に2を掛けているのは、実部と虚部の2要素セットでt001であることによる。
fouriinv(buf + 2 * t180, p - 1, w);
// バタフライ演算
// x(k ) = x_even(k) + w^k * x_odd(k)
// x(k+t180) = x_even(k) - w^k * x_odd(k)
for (pow_t k = 0; k < t180; k++) {
const pow_t ofs_even = k << 1;
const pow_t ofs_odd = (k + t180) << 1;
// x_even(k)
const double x_even_k_re = buf[ofs_even];
const double x_even_k_im = buf[ofs_even + 1];
// x_odd(k)
double x_odd_k_re = buf[ofs_odd];
double x_odd_k_im = buf[ofs_odd + 1];
// x_odd(k) * w^(-k) 算出、ここでwは1の原始t360乗根
double wk_re, wk_im;
w.pow(p, k, wk_re, wk_im);
const double re = x_odd_k_re * wk_re + x_odd_k_im * wk_im;
const double im = x_odd_k_im * wk_re - x_odd_k_re * wk_im;
// X(k ) = x_even(k) + w^k * x_odd(k) = x_even(k) + (re + i * im)
buf[ofs_even] = (x_even_k_re + re) / 2.0;
buf[ofs_even + 1] = (x_even_k_im + im) / 2.0;
// X(k+t180) = x_even(k) - w^k * x_odd(k) = x_even(k) - (re + i * im)
buf[ofs_odd] = (x_even_k_re - re) / 2.0;
buf[ofs_odd + 1] = (x_even_k_im - im) / 2.0;
}
}
} // namespace fftn
// ブロックSix-Step FFTアルゴリズム
// https://w...content-available-to-author-only...c.jp/public/VOL9/special/7.pdf
// のC++実装
// 実装の都合により、行数n1、列数n2、ブロックの行数nbは2の冪に制限する。
// また、PCスタンドアロン実行用に行列転置処理を簡素化している。
namespace fftb6
{
using namespace priroot;
/// 最適な行数(2の冪)の指数を返す。
expn_t get_best_expn_n1(const expn_t p)
{
const expn_t exp_expn_n1 = (p + 1) / 2;
return std::min(exp_expn_n1, EXPN_CAPACITY);
}
/// 最適なブロックサイズ(2の冪)の指数を返す。
expn_t get_best_expn_nb(const expn_t expn_n)
{
return std::min(expn_n, (EXPN_CAPACITY > expn_n) ? EXPN_CAPACITY - expn_n : 0);
}
/// 転置処理
/// 転置元配列src[]は (2^expn_n1)行(2^(p-expn_n1))列の2次元配列
/// 転置先配列dst[]は (2^(p-expn_n1))行(2^expn_n1)列の2次元配列
/// とそれぞれ解釈する。
void transpose(
std::vector<double>::const_iterator src,
std::vector<double>::iterator dst,
const expn_t p,
const expn_t expn_n1)
{
const expn_t expn_n2 = p - expn_n1;
const pow_t t360 = (pow_t)1 << p;
const pow_t n1 = (pow_t)1 << expn_n1;
const pow_t n2 = (pow_t)1 << expn_n2;
// ストレートな転置アルゴリズム
// 正方行列の転置への分解はPCスタンドアロン実行に向かないため行わない。
#pragma omp parallel for
for (pow_t i = 0; i < n1; i++) {
for (pow_t j = 0; j < n2; j++) {
const pow_t src_idx = n2 * i + j;
const pow_t dst_idx = n1 * j + i;
dst[2 * dst_idx] = src[2 * src_idx]; // 実部
dst[2 * dst_idx + 1] = src[2 * src_idx + 1]; // 虚部
}
}
}
// 前方宣言
void fouri_wrp(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w);
void fouriinv_wrp(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w);
/// フーリエ変換
void fourib6(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w,
const expn_t expn_n1)
{
// 行数が全体のサイズ以下であること
if (!(0 <= expn_n1 && expn_n1 <= p)) {
// ここに来たら呼び出し元のバグ
abort();
}
// 列数(2の冪)の指数
const expn_t expn_n2 = p - expn_n1;
// ブロックサイズ
const expn_t expn_nb = get_best_expn_nb(expn_n2);
// buf[0..t360-1]を複素数を要素とする2次元配列とみなす。
// 入力に関しては j=n2*j1 + j2 (0≦j2<n2) として、buf[j1, j2] (n1行n2列)
// 出力に関しては k=n1*k2 + k1 (0≦k1<n1) として、buf[k1, k2] (n1行n2列)
// 出力の順序とメモリのアドレス順を合わせるには、buf[k1, k2]を転置してn2行n1列にする必要がある。
const pow_t t360 = (pow_t)1 << p;
const pow_t n1 = (pow_t)1 << expn_n1;
const pow_t n2 = (pow_t)1 << expn_n2;
const pow_t nb = (pow_t)1 << expn_nb;
// SINテーブル獲得
// ここで指数pのテーブルを確保しておくことで、
// マルチスレッド処理中にテーブルのstd::vector::realloc()が呼ばれなくして
// 排他制御を省略できる。
w.alloc_table(p);
// 6 step algorithm
#pragma omp parallel for
for (pow_t JJ = 0; JJ < n2; JJ += nb) {
// 作業バッファ準備
// 2を掛けているのは、実部と虚部の2要素セット1要素であることによる。
// スレッド毎に確保する必要があるため、parallel for対象ブロック内で実施する。
std::vector<double> work((size_t)(2 * nb * n1));
// ストレートな転置アルゴリズム
// 正方行列の転置への分解はPCスタンドアロン実行に向かないため行わない。
for (pow_t j = JJ; j < JJ + nb; j++) {
for (pow_t i = 0; i < n1; i++) {
// ここでのi、jはj1、j2にあたる。
// 直下の処理は転置処理
// work[j2 - JJ, j1] = buf[j1, j2]
// にあたる。
const pow_t src_idx = n2 * i + j;
const pow_t dst_idx = n1 * (j - JJ) + i;
work[2 * dst_idx] = buf[2 * src_idx]; // 実部
work[2 * dst_idx + 1] = buf[2 * src_idx + 1]; // 虚部
}
}
// Step 2: nb組のn1点multi row FFT
for (pow_t i = 0; i < nb; i++) {
// ここでのiはj2-JJに対応する。
// 直下の処理はwork[j2 - JJ, j1]のj1に関するフーリエ変換
// work[j2 - JJ, k1] = Σ[j1=0..n1-1]{ work[j2 - JJ, j1] * (w_n1)^(j1 * k1) }
// = Σ[j1=0..n1-1]{ buf[j1, j2] * (w_n1)^(j1 * k1) }
// にあたる。ここで、w_n1は1の原始n1乗根。
const pow_t row_head = 2 * n1 * i;
fouri_wrp(work.begin() + row_head, expn_n1, w);
}
// Step 3: ひねり係数をかける
// Step 4: 転置
for (pow_t j = JJ; j < JJ + nb; j++) {
for (pow_t i = 0; i < n1; i++) {
// ここでのi、jはk1、j2にあたる。
// 転置のsrcとdst
const pow_t src_idx = n1 * (j - JJ) + i; // n1 * (j2 - JJ) + k1
const pow_t dst_idx = n2 * i + j; // n2 * k1 + j2
// ひねり係数(w_n)^(j2*k1)
// ここで、w_nは1の原始n乗根。
double omg_re, omg_im;
w.pow(p, j * i, omg_re, omg_im);
// ひねり係数を掛けて転置
// 直下の処理は、ひねり係数を掛けて転置する処理
// buf[k1, j2] = work[j2 - JJ, k1] * (w_n)^(j2*k1)
// にあたる。
const double src_re = work[2 * src_idx];
const double src_im = work[2 * src_idx + 1];
buf[2 * dst_idx] = src_re * omg_re - src_im * omg_im; // 実部
buf[2 * dst_idx + 1] = src_im * omg_re + src_re * omg_im; // 虚部
}
}
}
// Setp 5: N1組のN2点multi row FFT
// ブロックSix-step algorithmの引用元コードによれば
// nb組のn2点multi row FFTに区分して行うべきところだが、
// PCスタンドアロン実行に向かないため区分しない。
#pragma omp parallel for
for (pow_t i = 0; i < n1; i++) {
// ここでのiはk1にあたる。
// 直下の処理は、buf[k1, j2]のj2に関するフーリエ変換
// buf[k1, k2] = Σ[j2=0..n2-1]{ buf[k1, j2] * (w_n2)^(j2 * k2) }
// にあたる。ここで、w_n2は1の原始n2乗根。
const pow_t row_head = 2 * n2 * i;
fouri_wrp(buf + row_head, expn_n2, w);
// Step 6: 転置
// 本来はフーリエ変換結果X(k)がメモリ上で並ぶようにbuf[k1, k2]をn2行×n1列の2次元配列dst[,]に転置する工程だが、
// buf[N2 * i + j] を X(N1 * j + i)と解釈することにして省略する。
}
}
/// フーリエ逆変換
/// buf[]としては、fourib6()の結果を転置せずにそのまま与える前提。
/// expn_n1もfourib6()に与えたものと同じ値を与える。
void fourib6inv(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w,
const expn_t expn_n1)
{
// 行数が全体のサイズ以下であること
if (!(0 <= expn_n1 && expn_n1 <= p)) {
// ここに来たら呼び出し元のバグ
abort();
}
// 列数(2の冪)の指数
const expn_t expn_n2 = p - expn_n1;
// ブロックサイズ
const expn_t expn_nb = get_best_expn_nb(expn_n2);
// buf[0..t360-1]を複素数を要素とする2次元配列とみなす。
// 入力に関しては k=n1*k2 + k1 (0≦k1<n1) として、buf[k1, k2] (n1行n2列)
// 出力に関しては j=n2*j1 + j2 (0≦j2<n2) として、buf[j1, j2] (n1行n2列)
// 入力の順序とメモリのアドレス順を合わせるには、buf[k1, k2]を転置してn2行n1列にする必要があるが
// 当関数ではその転置を行わずに済ませる。
// 出力の順序とメモリのアドレス順は一致する。
const pow_t t360 = (pow_t)1 << p;
const pow_t n1 = (pow_t)1 << expn_n1;
const pow_t n2 = (pow_t)1 << expn_n2;
const pow_t nb = (pow_t)1 << expn_nb;
// SINテーブル獲得
// ここで指数pのテーブルを確保しておくことで、
// マルチスレッド処理中にテーブルのstd::vector::realloc()が呼ばれなくして
// 排他制御を省略できる。
w.alloc_table(p);
// Modified 6 step algorithm
// Setp 1: n1組のn2点multi row IFFT
// ブロックSix-step algorithmの引用元コードによれば
// nb組のn2点multi row FFTに区分して行うべきところだが、
// PCスタンドアロン実行に向かないため区分しない。
#pragma omp parallel for
for (pow_t i = 0; i < n1; i++) {
// ここでのiはk1にあたる。
// 直下の処理はbuf[k1, k2]のk2に関する逆フーリエ変換
// buf[k1, j2] = (1/n2) * Σ[k2=0..n2-1]{ buf[k1, k2] * (w_n2)^(-(j2 * k2)) }
// にあたる。ここで、w_n2は1の原始n2乗根。
const pow_t row_head = 2 * n2 * i;
fouriinv_wrp(buf + row_head, expn_n2, w);
}
// Step 3: ひねり係数をかける
// Step 4: 転置
#pragma omp parallel for
for (pow_t JJ = 0; JJ < n2; JJ += nb) {
// 作業バッファ準備
// 2を掛けているのは、実部と虚部の2要素セット1要素であることによる。
std::vector<double> work((size_t)(2 * nb * n1));
// ストレートな転置アルゴリズム
// 正方行列の転置への分解はPCスタンドアロン実行に向かないため行わない。
for (pow_t j = JJ; j < JJ + nb; j++) {
for (pow_t i = 0; i < n1; i++) {
// ここでのi、jはk1、j2にあたる。
// 転置のsrcとdst
const pow_t src_idx = n2 * i + j;
const pow_t dst_idx = n1 * (j - JJ) + i;
// ひねり係数の逆数(w_n)^(j2*k1)
// ここで、w_nは1の原始n乗根。
double omg_re, omg_im;
w.pow(p, j * i, omg_re, omg_im);
// ひねり係数を掛けて転置
// 直下の処理は
// work[j2 - JJ, k1] = buf[k1, j2] * (w_n)^(-(j2*k1))
// にあたる。
const double src_re = buf[2 * src_idx];
const double src_im = buf[2 * src_idx + 1];
work[2 * dst_idx] = src_re * omg_re + src_im * omg_im; // 実部
work[2 * dst_idx + 1] = src_im * omg_re - src_re * omg_im; // 虚部
}
}
// nb組のn1点multi row IFFT
for (pow_t i = 0; i < nb; i++) {
// ここでのiはj2-JJにあたる。
// 直下の処理は、work[j2 - JJ, k1]のk1に関する逆フーリエ変換
// work[j2 - JJ, j2] = (1/n1) * Σ[k1=0..n1-1]{ work[j2 - JJ, k1] * (w_n1)^(j1 * k1) }
// = (1/n1) * Σ[k1=0..n1-1]{ buf[k1, j2] * (w_n1)^(j1 * k1) }
// にあたる。ここで、w_n1は1の原始n1乗根。
const pow_t row_head = 2 * n1 * i;
fouriinv_wrp(work.begin() + row_head, expn_n1, w);
}
// IFFT結果をbuf[]の元の場所に書き戻す
// ストレートな転置アルゴリズム
// 正方行列の転置への分解はPCスタンドアロン実行に向かないため行わない。
for (pow_t j = JJ; j < JJ + nb; j++) {
for (pow_t i = 0; i < n1; i++) {
// ここでのi、jはj1、j2にあたる。
// 直下の処理は、転置処理
// buf[j1, j2] = work[j2 - JJ, j1]
// にあたる。
const pow_t src_idx = n1 * (j - JJ) + i;
const pow_t dst_idx = n2 * i + j;
buf[2 * dst_idx] = work[2 * src_idx]; // 実部
buf[2 * dst_idx + 1] = work[2 * src_idx + 1]; // 虚部
}
}
}
}
/// フーリエ変換のwrapper
void fouri_wrp(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w)
{
if (p <= EXPN_CAPACITY) {
// (FFT結果がL2キャッシュサイズの半分に収まる)
// ストレートにfftする。
fftn::sort_by_btr(buf, p);
fftn::fouri(buf, p, w);
}
else {
// (FFT結果がL2キャッシュサイズの半分に収まならい)
// Blocked 6 step algorithmでFFTする
// n1 = √(2^p)が最も効率が良いのでEXPN_CACACITYを超えない範囲で近い値にする。
// (2^p)/n1がEXPN_CAPACITYを超える場合は、foourib6()から当関数にさらに再帰して細分化する。
const int n1 = std::min((p + 1) / 2 + 1, EXPN_CAPACITY);
// フーリエ変換
fourib6(buf, p, w, n1);
// 転置
const pow_t t360 = (pow_t)1 << p;
std::vector<double> tmp(buf, buf + 2 * t360);
transpose(tmp.begin(), buf, p, n1);
}
}
/// 逆フーリエ変換のwrapper
void fouriinv_wrp(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w)
{
if (p <= EXPN_CAPACITY) {
// (IFFT結果がL2キャッシュサイズの半分に収まる)
// ストレートにifftする。
fftn::sort_by_btr(buf, p);
fftn::fouriinv(buf, p, w);
}
else {
// (IFFT結果がL2キャッシュサイズの半分に収まならい)
// Blocked 6 step algorithmでFFTする
// n1 = √(2^p)が最も効率が良いのでEXPN_CACACITYを超えない範囲で近い値にする。
// (2^p)/n1がEXPN_CAPACITYを超える場合は、foourib6()から当関数にさらに再帰して細分化する。
const int n1 = std::min((p + 1) / 2 + 1, EXPN_CAPACITY);
const pow_t t360 = (pow_t)1 << p;
// 転置
std::vector<double> tmp(buf, buf + (size_t)(2 * t360));
transpose(tmp.begin(), buf, p, n1);
// 逆フーリエ変換
fourib6inv(buf, p, w, p - n1);
}
}
} // namespace fftb6
namespace fftb6_4n
{
using namespace priroot;
/// 4n乗根によるフーリエ変換(Blocked 6-step algorithm版)
/// x(j) (j=0..(2*t360)-1)から
/// x'(j) = (x(j) + i * x(j+t360)) * u^(j/4) (j=0..t360-1)
/// を構成し、x'(j)をDFTする。ここで、uは1の原始(4 * mag * t360)乗根 (mag ≧ 1)。
void fourib6_4n(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w,
const expn_t expn_n1)
{
const pow_t t360 = (pow_t)1 << p;
w.alloc_table(p + 2);
// { x'(j) }構成
// { x(j) + i * x(j+t360) } にu^(j/4)を掛ける。
const expn_t p2 = p + 2;
for (pow_t j = 0; j < t360; j++) {
const double a = buf[2 * j];
const double b = buf[2 * j + 1];
// u = w^4としてu^(j/4) = w^(j)
double wj_re, wj_im;
w.pow(p2, j, wj_re, wj_im);
buf[2 * j] = a * wj_re - b * wj_im;
buf[2 * j + 1] = a * wj_im + b * wj_re;
}
// フーリエ変換
fftb6::fourib6(buf, p, w, expn_n1);
}
/// 4n乗根による逆フーリエ変換(Blocked 6-step algorithm版)
void fourib6_4ninv(
std::vector<double>::iterator buf,
const expn_t p,
nth_root& w,
const expn_t expn_n1)
{
const pow_t t360 = (pow_t)1 << p;
w.alloc_table(p + 2);
// 逆フーリエ変換
fftb6::fourib6inv(buf, p, w, expn_n1);
// { x(j) }復元
const expn_t p2 = p + 2;
for (pow_t j = 0; j < t360; j++) {
const double a = buf[2 * j];
const double b = buf[2 * j + 1];
// u = w^4としてu^(-j/4) = w^(-j)を掛ける
double wj_re, wj_im;
w.pow(p2, j, wj_re, wj_im);
buf[2 * j] = a * wj_re + b * wj_im;
buf[2 * j + 1] = b * wj_re - a * wj_im;
}
}
} // namespace fft4n
namespace oper
{
using namespace fftb6_4n;
struct I
{
std::vector<I_TYPE> data; // 基数RADIXの多倍長データ
size_t ndigits; // dataに入っている有効な桁数
I(const size_t cap)
: data(cap), ndigits((size_t)0)
{
/*pass*/
}
};
struct F
{
std::vector<double> data; // 基数[-(RADIX/2), RADIX/2)の多倍長データ
int p; // 2^p == (dataの要素数) となるp
F(const int p_)
: data((size_t)1 << p_), p(0)
{
/*pass*/
}
};
/// 整数のフーリエ変換
void i2f(F& a, const I& b, const int p, priroot::nth_root& w)
{
size_t i;
// 次数2^(p-1) = t360の4n乗根フーリエ変換を行う。
// 結果は実部と虚部を合わせて2^p = (2 * t360)桁分となる。
const pow_t t360 = (pow_t)1 << (p - 1);
assert(b.ndigits <= (size_t)t360);
assert((size_t)(2 * t360) <= a.data.size());
#ifdef SHOW_I
printf("%s: Before pre tr.:\n", __FUNCTION__);
show_re_im_seq(b.data, p);
#endif
// フーリエ変換の前処理
// (bが表す整数) = Σ[i=0..a.ndigits-1]{ b.data[i] * RADIX^i } (0≦b.data[i]<RADIX)
// を、
// (aが表す整数) = Σ[i=0..a.ndigits-1]{ a.data[i] * RADIX^i } (-RADIX / 2≦a.data[i]<RADIX / 2)
// に変換する。(a, bが同じ整数を表すように桁上げも行う。)
a.p = p;
int carry = 0;
for (i = 0; i < b.ndigits - 1; i++) {
// 実部虚部隣接化
// b.data[t360..]を虚部に配置する。
const size_t dst_i = (i < (size_t)t360) ? 2 * i : 2 * (i - (size_t)t360) + 1;
const int32_t t = b.data[i] + carry; // 桁[i]の値
if (t < (int32_t)RADIX / 2) {
// (桁[i]の値∈[0, RADIX / 2) )
// 何もしない
a.data[dst_i] = t;
carry = 0;
}
else {
// (桁[i]の値∈[RADIX / 2, RADIX) )
// 補正実施
// まず桁[i] -= RADIX により桁[i]の値∈[0, RADIX / 2)とする。ただし
// それだけでは(aが表す整数)が RADIX * RADIX^i = RADIX^(i+1)だけ減ってしまうので、
// 減った分をcarryで一つ上の桁に回して補填する。
a.data[dst_i] = (double)(t - (int32_t)RADIX);
carry = 1;
}
}
{
// 実部虚部隣接化
const size_t dst_i = (i < (size_t)t360) ? 2 * i : 2 * (i - (size_t)t360) + 1;
// 最上位桁
a.data[dst_i] = (double)(b.data[i] + (I_TYPE)carry);
i++;
}
for (; i < (size_t)(2 * t360); i++) {
// 実部虚部隣接化
const size_t dst_i = (i < (size_t)t360) ? 2 * i : 2 * (i - (size_t)t360) + 1;
a.data[dst_i] = 0.0;
}
#if defined(SHOW_ITM) || defined(SHOW_I)
printf("Before fouri4n():\n");
show_re_im_seq(a.data, a.p);
#endif
const expn_t expn_n2 = (p - 1) / 2;
const expn_t expn_n1 = (p - 1) - expn_n2;
// フーリエ変換実施
fftb6_4n::fourib6_4n(a.data.begin(), p - 1, w, expn_n1);
#ifdef SHOW_ITM
printf("After fouri4n():\n");
show_re_im_seq(a.data, a.p);
#endif
}
/// 整数のフーリエ変換結果の積(結果は整数)
void mul_f2i(I& a, const F& b, const F& c, F& d, double& err_max, priroot::nth_root& w) {
int64_t carry;
size_t i, t;
const int p = b.p;
// 次数2^(p-1) = t360の4n乗根フーリエ変換結果の積を逆フーリエ変換する。
// 結果は実部と虚部を合わせて2^p = (2 * t360)桁となる。
const pow_t t360 = (size_t)1 << (p - 1);
assert(p == c.p);
assert((size_t)(2 * t360) <= d.data.size());
// 積
for (i = 0; i < (size_t)t360; i++) {
const double b_i_re = b.data[2 * i];
const double b_i_im = b.data[2 * i + 1];
const double c_i_re = c.data[2 * i];
const double c_i_im = c.data[2 * i + 1];
// d[i] = b[i] * c[i]
const double d_i_re = b_i_re * c_i_re - b_i_im * c_i_im;
const double d_i_im = b_i_re * c_i_im + b_i_im * c_i_re;
d.data[2 * i] = d_i_re;
d.data[2 * i + 1] = d_i_im;
}
#ifdef SHOW_ITM
printf("Before fouri4ninv():\n");
show_re_im_seq(d.data, d.p);
#endif
const expn_t expn_n2 = (p - 1) / 2;
const expn_t expn_n1 = (p - 1) - expn_n2;
// 逆フーリエ変換
fftb6_4n::fourib6_4ninv(d.data.begin(), p - 1, w, expn_n1);
#ifdef SHOW_ITM
printf("After fouri4ninv():\n");
show_re_im_seq(d.data, d.p);
#endif
const size_t s = std::min(a.data.size(), (size_t)(2 * t360));
// Carry and fix処理
// ただし対象とする桁の並びd.data[]は、
// 桁の値が[0, RADIX)ではなく、[-RADIX / 2, RADIX / 2)である。
carry = 0;
t = 1;
//const double k = ldexp(1.0, -p + 1); // k = 1.0 * 2^(-p + 1)
double k = 1.0; // fouri4ninv()結果は2^pで割る必要無し。
for (i = 0; i < s; i++) {
// 実部虚部隣接化解除
// b.data[t360..]を虚部に配置する。
const size_t src_i = (i < (size_t)t360) ? 2 * i : 2 * (i - (size_t)t360) + 1;
//printf("t360=%lld, s=%zu, i=%zu, src_i=%zu\n", t360, s, i, src_i);
const double x = d.data[src_i] * k;
int64_t e = (int64_t)floor(x + 0.5);
err_max = std::max(err_max, fabs(x - e));
e += carry;
carry = e / RADIX;
int64_t f = e - carry * RADIX;
if (f < 0) {
f += RADIX;
carry--;
}
a.data[i] = (I_TYPE)f;
if (f != 0) {
t = i + 1;
}
}
a.ndigits = t;
if (carry != 0) {
if (a.ndigits < a.data.size()) {
assert(0 <= carry && carry < RADIX);
a.data[a.ndigits++] = (I_TYPE)carry;
}
else {
// ここに来たら桁あふれ
abort();
}
}
for (; i < s; i++) {
// 実部虚部隣接化解除
const size_t src_i = (i < (size_t)t360) ? 2 * i : 2 * i + 1;
const double x = d.data[src_i] * k;
const int64_t e = (int64_t)floor(x + 0.5);
err_max = std::max(err_max, fabs(x - e));
assert(e == 0);
}
}
/// 整数の和
void add_i(I& a, const I& b_, const I& c_) {
I_TYPE carry, d;
size_t i;
const I& b = (b_.ndigits > c_.ndigits) ? c_ : b_; // Alias
const I& c = (b_.ndigits > c_.ndigits) ? b_ : c_; // Alias
assert(b.ndigits <= c.ndigits);
assert(c.ndigits <= a.data.size());
carry = 0;
for (i = 0; i < b.ndigits; i++) {
d = b.data[i] + c.data[i] + carry;
carry = 0;
if (d >= RADIX) {
d -= RADIX;
carry++;
}
a.data[i] = d;
}
for (; i < c.ndigits; i++) {
d = c.data[i] + carry;
carry = 0;
if (d >= RADIX) {
d -= RADIX;
carry++;
}
a.data[i] = d;
}
a.ndigits = c.ndigits;
if (a.ndigits < a.data.size()) {
if (carry != 0) {
a.data[a.ndigits++] = carry;
}
}
else {
if (carry != 0) {
// ここに来たら桁あふれ
abort();
}
}
}
/// 整数のファイル出力
void print(const char* const filename, const I& a)
{
char format[10];
int nRet;
assert(a.data.size() > (size_t)0);
assert(a.ndigits <= a.data.size());
FILE* fp;
nRet = fopen_s_wrp(&fp, filename, "w");
if (nRet != 0) {
fflush(stdout);
fprintf(stderr, "ERROR: fopen_s_wrp() failed. (nRet=%d)\n", nRet);
abort();
}
sprintf_s_wrp(format, _countof(format), "%%0%du", FIG);
size_t s = a.ndigits;
fprintf(fp, "%lu", (unsigned long)a.data[--s]);
while (s != 0) {
fprintf(fp, format, a.data[--s]);
}
fclose(fp);
}
} // namespace oper
namespace
{
// N ≧ log2(s1 * s2)を満たす最小のNを求める。
int s2p(const size_t s1, const size_t s2) {
int i;
for (i = 1; i < 64; i++) {
if (((size_t)1 << i) + 1 >= s1 + s2) {
// (2^i + 1 ≧ (s1の有効桁数) + (s2の有効桁数))
break;
}
}
return i;
}
} // namespace
int main()
{
using namespace oper;
const int n = 34;
nth_root w;
const int max_p = s2p(((size_t)((1LLU << n) * log10(2)) / FIG + 1), 1);
const size_t cap = (size_t)1 << max_p;
I a(cap);
F x(max_p);
a.data[0] = 1;
a.ndigits = 1;
double err_max = 0.0;
#ifdef SHOW_CSM_TIME
std::vector<double> csm_time;
const double startTmg_0 = omp_get_wtime();
#endif
for (int i = 0; i < n; i++) {
#ifdef SHOW_CSM_TIME
const double startTmg = omp_get_wtime();
#endif
// 多倍長整数xのフーリエ変換
// p ≧ log2(a * a)を満たす最小のpを求め、
// aの次数2^pのフーリエ変換結果をxに格納する。
const int p = s2p(a.ndigits, a.ndigits);
i2f(x, a, p, w);
// x = x*x、および結果のxを整数に戻してaに格納する
mul_f2i(a, x, x, x, err_max, w);
add_i(a, a, a);
#ifdef SHOW_CSM_TIME
const double endTmg = omp_get_wtime();
csm_time.push_back(endTmg - startTmg);
#endif
// 途中経過出力
#ifdef SAVE_ITM_RESULT
/*pass*/
#else
if (i >= n - 1)
#endif
{
char filename[200];
sprintf_s_wrp(filename, _countof(filename), "%d.txt", i + 1);
print(filename, a);
}
printf("%d %f\n", i + 1, err_max);
}
#ifdef SHOW_CSM_TIME
const double endTmg_0 = omp_get_wtime();
printf("Total with file write = %10f sec\n", endTmg_0 - startTmg_0);
printf("Total only core time = %10f sec\n", std::accumulate(csm_time.begin(), csm_time.end(), 0.0));
for (size_t i = 0; i < csm_time.size(); i++) {
printf("2^2^(%2zu) %10f sec\n", i + 1, csm_time[i]);
}
#endif
return 0;
}