概览
boost::math::tools 提供两大类根求解工具:
-
无需导数(derivative-free)的方法:
bisect(二分,简单且健壮)bracket_and_solve_root+toms748_solve(先扩区间再用 TOMS 748 算法求解,效率高)- 还有方便函数用于"已知单调性并有初值猜测"的情况。
-
有导数的方法(基于导数的迭代法):
newton_raphson_iterate(牛顿/牛顿-拉弗森,二阶收敛)halley_iterate、schroder_iterate(三阶方法)- 以及
complex_newton等。此类要求开发者能同时计算一阶(或二阶)导数。
另外,Boost 的这些工具支持停止准则策略(tolerance functors) 、Policies(控制最大迭代、错误行为、抛异常或返回值等) ,以及调试宏 BOOST_MATH_INSTRUMENT 用于打印迭代信息。
重要概念
-
带括号(bracketed) vs 无括号(unbracketed):
- 带括号方法(例如
bisect、toms748_solve)需要使用者提供一个区间[a,b]且有根(或 f(a)*f(b) <= 0)。优点:健壮、收敛有保证(在连续函数上)。 - 无括号方法(例如牛顿)只需要初始猜测,但对初值敏感、可能发散或落入错误根,适合能提供导数且初值较好时使用。
- 带括号方法(例如
-
TOMS 748 :在无需导数的算法里是很高效的选择(混合插值法,针对平滑函数接近最优)。Boost 提供
toms748_solve与bracket_and_solve_root的封装。 -
停止准则 :Boost 使用"tolerance 函数对象"来决定达到多少精度停止。常用的是
eps_tolerance<T>(digits)(按二进制位数)或自定义 lambda(如abs(r - l) < tol)。也可以使用最大迭代次数的 Policy。 -
错误处理 :当前置条件不满足(如
f(a)*f(b)>0),Boost 默认会抛boost::math::evaluation_error,但可通过 Policy 改变行为(例如不抛而返回区间端点)。
常用函数签名与使用说明
下列示例基于 Boost 文档与官方示例;可直接在支持 C++17 的环境编译(需链接 Boost)并运行。
1) bisect(二分) --- 最稳健的通用方法
适用场景 :若有连续函数 f,并且已知区间 [a,b] 满足 f(a)*f(b) <= 0(即区间包含根或端点为根)。
基本用法(示例):
cpp
#include <boost/math/tools/roots.hpp>
#include <iostream>
#include <cmath>
int main(){
using boost::math::tools::bisect;
auto f = [](double x){ return x*x - 2.0; };
// 停止准则:区间长度小于 1e-12
auto tol = [](double l, double r){ return std::abs(r - l) < 1e-12; };
auto result = bisect(f, 0.0, 2.0, tol);
double root = (result.first + result.second) / 2.0;
std::cout << "root ~ " << root << "\n";
}
要点 :bisect 返回 std::pair<T,T>(保守地返回最终左右界)。如果前提 f(a)*f(b) <= 0 不满足,会触发错误(可通过 Policy 改变行为)。
2) bracket_and_solve_root + toms748_solve --- 自动扩区间并用 TOMS748 高效收敛
适用场景 :若只有一个初始猜测 x0(或对根数量级有估计),函数光滑且想比二分更快。bracket_and_solve_root 会先尝试从猜测扩展区间直到包含根,然后用 toms748_solve 求解。
用法示例:
cpp
#include <boost/math/tools/roots.hpp>
#include <iostream>
#include <cmath>
int main(){
using boost::math::tools::toms748_solve;
using boost::math::tools::bracket_and_solve_root;
auto f = [](double x){ return x*x - 2.0; };
// 容差按二进制有效位(例如 double 的 max_digits)
auto tol = boost::math::tools::eps_tolerance<double>(30); // 30 bits
std::size_t max_iter = 100;
// bracket_and_solve_root 会在内部用 toms748_solve 求根
auto result = bracket_and_solve_root(f, 1e-6, 1.0, true, tol, max_iter);
// 返回 pair<lower, upper>
double root = 0.5 * (result.first + result.second);
std::cout << "root ~ " << root << "\n";
}
注 :bracket_and_solve_root 有多种重载(可传 Policy、是否假设单调性等),并在面对根所在数量级与猜测相差极大时会有退避机制,但扩区间可能需要很多次函数评估。TOMS748 在平滑场景下效率很好。
3) newton_raphson_iterate / halley_iterate(基于导数的迭代)
适用场景 :若可以准确计算 f(x)f(x)f(x) 及其导数(或二阶导数),且能给出较好起始猜测。优点:收敛速度快(牛顿二阶,Halley 三阶);缺点:可能发散或收敛到错误根,尤其在函数平缓或导数近 0 时。
要求 :传入的 callable 必须返回 tuple / pair / boost::tuple:
- 对于
newton_raphson_iterate:返回(f(x), f'(x))。 - 对于
halley_iterate:返回(f(x), f'(x), f''(x))。([boost.org][2])
示例(牛顿法):
cpp
#include <boost/math/tools/roots.hpp>
#include <tuple>
#include <iostream>
int main(){
using boost::math::tools::newton_raphson_iterate;
// f(x) = x^2 - 2 ; f' = 2x
auto f_and_df = [](double x)->std::pair<double,double>{
return {x*x - 2.0, 2.0*x};
};
double guess = 1.0;
double min = 0.0, max = 2.0;
const int digits = std::numeric_limits<double>::digits; // binary digits
// newton_raphson_iterate returns the root estimate and uses template args to set accuracy
double root = newton_raphson_iterate(f_and_df, guess, min, max, digits);
std::cout << "root ~ " << root << "\n";
}
注意 :newton_raphson_iterate / halley_iterate 的模板参数和返回细节可参考 Boost 文档。若迭代遇到问题,通常要限制步长、加入信赖域或退回到带括号方法。
停止准则(tolerance)与 Policy 的细节
eps_tolerance<T>(bits):按二进制位精度停止(常用于要求"得到二进制位数的精度")。- 自定义 lambda :例如
[](T l, T r){ return std::abs(r - l) < tol; },适合你想按绝对距离停止。 - Policies :Boost.Math 有
policies::policy<>,可设置max_root_iterations等(通过policies::get_max_root_iterations<Policy>()获取)。还能控制错误是否抛出异常或返回特定值。
数值与实现注意(实践要点与陷阱)
-
一定要保证/检查括号条件 :对带括号算法,先检查
f(a)*f(b) <= 0。若不满足,考虑先用bracket_and_solve_root自动扩区间或用物理先验缩小初始区间。Boost 的bisect会在前条件不满足时报错(或按 Policy 行为处理)。 -
单调性与多根 :
toms748_solve对平滑单根或单调段效果极好;如果区间内有多个根或函数不光滑,结果可能不可预期;务必先分段或检测多根。 -
导数方法风险 :牛顿 / Halley 对初值敏感;若
f'(x)接近 0,会产生大步长并可能发散。常见对策:限制步长、使用信赖域或在不稳定时退回到二分/Bracketing。 -
精度 vs 迭代次数 :请求非常高精度(如接近机器极限)会显著增加函数评估次数;合理设置
digits与max_iter。可以通过policies::get_max_root_iterations<Policy>()管控迭代上限。 -
调试 :定义
#define BOOST_MATH_INSTRUMENT(在包含roots.hpp前)可打印迭代轨迹,帮助诊断为何迭代失败或为何收敛到错误根(注意输出可能非常庞大)。 -
数值病态 :在指数级差距(root 在 1e-300,但猜测在 1e-1)情况下,自动扩区间耗费巨大;如果知道尺度,先对变量做对数变换或缩放会更稳妥。
bracket_and_solve_root有部分鲁棒性,但也不是万能。
错误与已知问题(from upstream)
- Boost issue tracker 中有关于
newton_raphson_iterate在某些极端函数下表现不当的报告(在某些版本里可能出现问题)。使用前建议查阅所用 Boost 版本的文档与 issue(若遇到异常行为,尝试换版本或退回 bracket 方法)。
实战建议
-
当可提供可靠括号时 :优先
toms748_solve(或bracket_and_solve_root+toms748),兼顾速度与鲁棒性。若无法括号但能估计根量级,先bracket_and_solve_root。 -
当能给出良好初值并能计算导数时 :优先
newton_raphson_iterate或halley_iterate(更快),但务必设置边界或回退策略(如每次迭代检查是否越界或步长过大,必要时切换到 bracket 方法)。 -
极端鲁棒/保守场景 :使用
bisect(简单、可预测,但速度最慢)。
完整示例:一个"智能"求根封装(示意)
下面是一个示意性的 C++ 函数模板,演示如何结合 bracket_and_solve_root(TOMS748)与 newton_raphson_iterate 做"先快速尝试导数法,失败则退回到 bracket"策略。
cpp
#include <boost/math/tools/roots.hpp>
#include <iostream>
#include <tuple>
#include <functional>
#include <limits>
template<class Real>
Real robust_solve(std::function<std::pair<Real,Real>(Real)> f_df, // returns {f, f'}
std::function<Real(Real)> f_only,
Real guess, Real lo, Real hi)
{
using boost::math::tools::newton_raphson_iterate;
using boost::math::tools::bracket_and_solve_root;
using boost::math::tools::eps_tolerance;
// try Newton first (fast) but safe-guard with bracket bounds
try{
const int digits = std::numeric_limits<Real>::digits;
Real root = newton_raphson_iterate(
[&](Real x){ return f_df(x); },
guess, lo, hi, digits);
return root;
} catch(...){
// fallback to bracket + TOMS 748
auto tol = eps_tolerance<Real>(std::numeric_limits<Real>::digits - 3);
std::size_t max_iter = 200;
auto sol = bracket_and_solve_root(f_only, guess, 1, tol, max_iter);
return (sol.first + sol.second) / 2;
}
}