C++如何进行快速素数概率测试(Miller-Rabin)?(大数质性验证)

2次阅读

绝大多数场景下需手写miller-rabin——标准c++库不提供,boost支持弱,gmp依赖重;小于2^64用确定性底数集可100%正确,大于则需大数库且仍为概率算法

C++如何进行快速素数概率测试(Miller-Rabin)?(大数质性验证)

Miller-Rabin 需要自己实现还是用现成库?

绝大多数场景下,Miller-Rabin 得自己写——标准 C++ 库不提供大整数质性测试,boost::multiprecision 虽有 is_prime(),但底层默认用的是确定性小范围筛 + 概率 Miller-Rabin,且对自定义大数支持弱;而 GMP(C 库)虽高效,但引入它会让项目依赖变重、跨平台编译麻烦。所以真要控制精度、速度和输入范围,手写一个轻量可靠的版本更实际。

关键点在于:你不需要从零实现大整数运算,而是假设已有能做模幂(mod_pow)、模乘(mod_mul)、减法和偶数判断的 uint64_t__int128 封装类型。超过 2^64 的数必须用第三方大数(如 boost::multiprecision::cpp_int),但此时 Miller-Rabin 本身不是瓶颈,慢的是模乘。

  • 小于 2^64:用 64 位整数 + 确定性底数集(见下条),100% 正确
  • 大于 2^64:必须用大数库,但 Miller-Rabin 仍只是概率算法,需多轮;别指望单次调用就“保证质数”
  • 别把 rand() 用在底数生成上——它周期短、低位劣质;改用 std::random_device + std::mt19937_64

为什么 64 位内用 {2, 325, 9375, 28178, 450775, 9780504, 1795265022} 这组底数?

这不是经验值,是数学证明过的最小确定性集合:对所有 n ,用这 7 个底数做 <code>Miller-Rabin 测试,结果为“通过”当且仅当 n 是质数。比用随机底数快,且零误判。

常见错误是只用 {2, 3, 5, 7}——这对 int32_t 够用,但一到 uint64_t 范围,立刻漏掉像 3215031751 这样的强伪证(它被前 4 个底数都骗过)。还有的代码硬写 for (int a = 2; a ,既慢又不可靠。

立即学习C++免费学习笔记(深入)”;

  • 底数必须 ,且与 <code>n 互质;若 n 是偶数或小质数(2/3/5…),提前返回
  • n ,其实只用 <code>a = 2 就够;但统一走 7 个底数分支反而更省逻辑判断
  • 底数顺序无关正确性,但把小的放前面能更快排除合数(小底数模幂快,且很多合数第一轮就失败)

模幂和模乘怎么写才不溢出、不丢精度?

核心陷阱是:(a * b) % nab 接近 UINT64_MAX 时必然溢出。不能直接乘,得拆解。64 位下推荐用 __int128(GCC/Clang 支持),它天然支持 128 位中间结果:

uint64_t mod_mul(uint64_t a, uint64_t b, uint64_t m) {     return (uint64_t)((__int128)a * b % m); }

__int128?就得用二进制拆分的倍增法(类似快速幂思想),时间多常数 2–3 倍,但安全:

uint64_t mod_mul(uint64_t a, uint64_t b, uint64_t m) {     uint64_t res = 0;     while (b > 0) {         if (b & 1) res = (res + a) % m;         a = (a << 1) % m;         b >>= 1;     }     return res; }
  • mod_pow 必须基于安全的 mod_mul,不能用原生 *
  • 所有中间变量(包括 n-1 的分解结果)都声明为 uint64_t,避免隐式符号转换引发负数模行为
  • 测试前先检查 n 、<code>n == 2n % 2 == 0,这些情况根本不用进主循环

误报率和轮数之间到底什么关系?

每轮独立随机底数的误报率 ≤ 1/4,k 轮后 ≤ 4⁻ᵏ。但这是理论上界,实际远更低。问题在于:你根本不需要靠堆轮数来“补救”不可靠的实现。重点应放在——

  • 小范围(n )直接试除,比跑 20 轮 <code>Miller-Rabin 还快
  • 64 位内老实用那 7 个确定性底数,别谈“概率”,就是确定性算法
  • 真要处理任意长度大数(比如 1024-bit RSA 模数),轮数设 64 是工程惯例;再往上收益极小,瓶颈早变成大数模乘了
  • 别信“用 time(NULL) 当随机种子”——密码学强度不重要,但至少别让每次运行都测同一组底数

最易被忽略的一点:Miller-Rabin 只说“很可能是质数”,但从不证明它是质数;如果你后续要用这个数做 RSA 密钥,必须接受它仍有极小概率是合数——而这个风险,往往比实现 bug 更难 debug。

text=ZqhQzanResources