不能直接用 M_PI 或 3.14159265358979323846,因 M_PI 非标准且编译器支持不一,而 double 字面量仅约17位有效数字;高精度需整数数组模拟大数+ Machin 公式(π = 16·arctan(1/5) − 4·arctan(1/239))配合泰勒展开逐位计算。

为什么不能直接用 M_PI 或 3.14159265358979323846
因为 M_PI 是非标准宏,GCC/Clang 默认不定义,MSVC 也不保证存在;而硬编码的 double 字面量最多只有约17位有效数字,超出即失真。真要算到几百、几千位小数,必须脱离浮点数,自己管理每一位数字。
用整数数组模拟大数 + Machin 公式最稳
Machin 公式:π = 16 * arctan(1/5) - 4 * arctan(1/239),收敛快、项数少、整数运算友好。arctan(x) 展开为泰勒级数:arctan(x) = x - x³/3 + x⁵/5 - x⁷/7 + ...,代入 1/5 和 1/239 后,每一步都是整数除法+累加,适合用数组存十进制各位。
实操建议:
- 用
std::vector<int></int>存储结果(每位0–9),索引0为个位,高位在后,方便进位处理 - 每一项计算时,对分子反复乘以
10^k(k为当前所需小数位数)再整除分母,模拟“高精度除法” - 用“长除法逐位生成”比“先算整数再转字符串”更省内存,尤其算万位以上时
- 别用
std::string拼接中间结果——字符串拷贝开销大,且易内存碎片
arctan(1/n) 的整数迭代怎么写才不出错
关键不是公式本身,而是如何避免中间溢出和截断。例如算 arctan(1/5) 第100项:(1/5)^(2*100+1) / (2*100+1),直接算分数会崩。正确做法是边算边缩放:
立即学习“C++免费学习笔记(深入)”;
// 伪代码:计算 arctan(1/n) 的前 N 项,保留 prec 位小数
vector<int> res(prec + 1, 0); // 个位 + 小数部分
vector<int> num(prec + 1, 0); // 当前分子(初始为 1,对应 1/n)
num[0] = 1;
for (int k = 0; k < N; ++k) {
// num = num / n² (因每次幂增2,等价于除 n²)
div_by_int(num, n * n);
// 加到 res:res += num / (2*k+1)
div_by_int(num, 2*k + 1);
if (k % 2 == 0) add_to(res, num);
else subtract_from(res, num);
}
注意:div_by_int 必须从高位往低位做长除法,并处理借位;add_to 要带进位传播;所有操作都得在整数数组上原地完成。
编译和运行时容易被忽略的坑
算到10000位以上时,常见问题不是算法错,而是工程细节翻车:
- 没关
-O2优化可能导致循环被误判为无副作用而删掉——加volatile或用asm volatile("")插桩验证 - 用
std::vector::reserve()预分配空间,否则频繁push_back触发多次重分配,性能跌5倍以上 - 输出时别用
std::cout << digit逐个刷——改用fwrite或预拼char*缓冲区,I/O 可快10倍 - Linux 下栈默认只有8MB,
std::vector在栈上声明大数组会段错误——务必堆分配或用std::unique_ptr<std::vector<int>>
真正卡住人的,往往不是公式推导,而是第3次进位没清零、或除法余数用了有符号类型导致负数截断——这些错不会报编译错误,只会让小数点后第200位开始全偏1。


















