// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntRandom.hpp — 多倍長乱数整数生成 // // GMP 互換 API: // randomBits(n) : [0, 2^n) の一様乱数 (mpz_urandomb) // randomBelow(upper) : [0, upper) の一様乱数 (mpz_urandomm) #ifndef CALX_INT_RANDOM_HPP #define CALX_INT_RANDOM_HPP #include #include #include namespace calx { /** * @brief 多倍長乱数整数ジェネレータ * * 内部に mt19937_64 エンジンを保持し、任意ビット幅の一様乱数 Int を生成する。 * デフォルトコンストラクタは std::random_device でシードする。 */ class IntRandom { public: /// デフォルトコンストラクタ (random_device でシード) IntRandom() { std::random_device rd; std::seed_seq seq{rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()}; m_engine.seed(seq); } /// 明示的シード explicit IntRandom(uint64_t seed) : m_engine(seed) {} /// [0, 2^bits) の一様乱数を生成 (GMP mpz_urandomb 相当) /// bits == 0 → 0 を返す Int randomBits(size_t bits) { if (bits == 0) return Int(0); size_t nwords = (bits + 63) / 64; Int result; result.m_words.resize_uninitialized(nwords); for (size_t i = 0; i < nwords; ++i) { result.m_words[i] = m_engine(); } // 最上位ワードのマスク: bits が 64 の倍数でなければ上位ビットをクリア size_t topBits = bits % 64; if (topBits != 0) { result.m_words[nwords - 1] &= (uint64_t(1) << topBits) - 1; } // normalize: 上位ゼロワードを除去 while (!result.m_words.empty() && result.m_words.back() == 0) { result.m_words.pop_back(); } result.m_sign = result.m_words.empty() ? 0 : 1; result.m_state = NumericState::Normal; return result; } /// [0, upper) の一様乱数を生成 (GMP mpz_urandomm 相当) /// rejection sampling: upper.bitLength() ビットの乱数を生成し、 /// upper 以上なら再試行 /// upper <= 0 → 0 を返す Int randomBelow(const Int& upper) { if (upper.getSign() <= 0) return Int(0); size_t bits = upper.bitLength(); // rejection sampling for (;;) { Int r = randomBits(bits); if (r < upper) return r; } } /// [lo, hi] の一様乱数を生成 (両端を含む) Int randomRange(const Int& lo, const Int& hi) { if (lo > hi) return Int(0); if (lo == hi) return lo; Int range = hi - lo + Int(1); return lo + randomBelow(range); } /// テスト用の偏った乱数 (GMP mpz_rrandomb 相当) /// 長いビットラン (連続する 0/1) を持つ bits ビットの正整数を生成。 /// MSB は常に 1。乗算・除算のエッジケーステストに有用。 /// bits == 0 → 0 を返す Int rrandomb(size_t bits) { if (bits == 0) return Int(0); size_t nwords = (bits + 63) / 64; Int result; result.m_words.resize_uninitialized(nwords); for (size_t i = 0; i < nwords; ++i) result.m_words[i] = 0; // ランダム長のビットランで埋める (GMP 方式) // 現在のビット値 (0 or 1) を交互に切り替え bool current_bit = true; // MSB 側から 1 で開始 size_t pos = bits; // 残りビット数 while (pos > 0) { // ラン長を幾何分布風に決定 (短いランが多い、時々長い) // GMP: gmp_urandomb_ui(state, 0 のとき BITS_PER_MP_LIMB が上限) size_t max_run = std::min(pos, static_cast(64)); uint64_t r = m_engine(); // 下位ビットから連続する 0 の数 + 1 をラン長に (幾何分布の近似) size_t run_len; if (r == 0) { run_len = max_run; } else { run_len = static_cast(std::countr_zero(r)) + 1; if (run_len > max_run) run_len = max_run; } // [pos - run_len, pos) のビットを current_bit で埋める if (current_bit) { for (size_t i = 0; i < run_len; ++i) { size_t bit_pos = pos - 1 - i; result.m_words[bit_pos / 64] |= uint64_t(1) << (bit_pos % 64); } } // current_bit == false なら既に 0 なのでスキップ pos -= run_len; current_bit = !current_bit; } // MSB を確実に 1 にする (bits ビットの数として) result.m_words[(bits - 1) / 64] |= uint64_t(1) << ((bits - 1) % 64); // 最上位ワードの余剰ビットをクリア size_t topBits = bits % 64; if (topBits != 0) { result.m_words[nwords - 1] &= (uint64_t(1) << topBits) - 1; } // normalize while (!result.m_words.empty() && result.m_words.back() == 0) result.m_words.pop_back(); result.m_sign = result.m_words.empty() ? 0 : 1; result.m_state = NumericState::Normal; return result; } /// エンジンへのアクセス (外部カスタマイズ用) std::mt19937_64& engine() { return m_engine; } private: std::mt19937_64 m_engine; }; /// thread_local グローバルエンジンを使うフリー関数 /// [0, 2^bits) の一様乱数 inline Int randomBits(size_t bits) { thread_local IntRandom g_rng; return g_rng.randomBits(bits); } /// [0, upper) の一様乱数 inline Int randomBelow(const Int& upper) { thread_local IntRandom g_rng; return g_rng.randomBelow(upper); } /// [lo, hi] の一様乱数 inline Int randomRange(const Int& lo, const Int& hi) { thread_local IntRandom g_rng; return g_rng.randomRange(lo, hi); } /// テスト用偏り乱数 (長いビットラン) inline Int rrandomb(size_t bits) { thread_local IntRandom g_rng; return g_rng.rrandomb(bits); } } // namespace calx #endif // CALX_INT_RANDOM_HPP