0. 简介
The C++ Mathematical Expression Toolkit Library (ExprTk) is a versatile, simple to use, easy to integrate and extremely efficient runtime mathematical expression parser and evaluation engine. ExprTk supports numerous forms of functional, logical and vector processing semantics and is very easily extendible.
C++ 数学表达式工具包库(ExprTk)是一个多功能、易于使用、易于集成且极其高效的运行时数学表达式解析器和评估引擎。ExprTk 支持多种函数式、逻辑处理和向量处理语义,且易于扩展。
网站:https://www.partow.net/programming/exprtk/index.html
github: exprtk
1. 用表达式拜年
1.1 知识点
- ExprTk 能算什么东西?
- ExprTk 为什么又大又久?
- 三板斧:表达式(Expression)、符号表(Symbol Table)、解析器(Parser)
- 如何导入 ExprTk 的扩展函数?
- 如何绑定外部字符串变量?
- 如何获取脚本编译出错的描述?
- 附:ExprTk 无法输出汉字问题的问题解决
- 附:ExprTk 过大造成编译失败的问题解决
1.2 视频一
1.3 基本原理:

- 给定一个表达式字符串 ‘z := x - (3 * y)’ 和三个变量(x、y 和 z)。
- 首先实例化一个 exprtk::symbol_table 并将这些变量添加到其中。
- 接着实例化一个 exprtk::expression,并将符号表注册到该表达式实例中。
- 最后实例化一个exprtk::parser,将表达式对象和表达式的字符串形式传递给解析器的编译方法。
- 如果编译过程成功,表达式实例将保存一个可用于计算原始表达式的抽象语法树(AST);
- 否则,将引发编译错误,并通过解析器的错误报告接口提供与错误相关的诊断信息。
- 本例中的表达式将使用变量 x 和 y 进行计算,然后将计算结果赋值给变量 z。
2. 像牛顿一样计算
2.1 知识点
- 如何绑定普通变量?
- 如何在脚本中定义变量?
- 条件判断、for 循环
- return 如何返回异构数据?
- 调用者如何获取返回值:value()、get_xxx()的区别
- 如何使用 ExprTk 内置函数,如:sin、swap、equal 等?
- switch-case
- repeat-until-
- 与原生 C++ 函数的性能对比
- 如何在脚本中定义函数?
- 使用 ExprTk 实现牛顿开方法和割圆术
2.2 视频二
3. 源代码
- CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(hello_exprTk)
add_executable(${PROJECT_NAME} main.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE "c:\\msys64\\ucrt64\\include") # 改成你的目录
# gcc/clang,-Wa,用于将后续参数传递给汇编器,中间不能有空格
target_compile_options(${PROJECT_NAME} PRIVATE "-Wa,-mbig-obj")
- main.cpp
#include <cassert>
#include <chrono>
#include <iostream>
#include <vector>
#include <exprtk.hpp>
void demo_0() // 问候新年
{
std::string wishs = "新年好!马年马上发财!";
std::string const s = "say(wishs); say('Happy New Year! Wish you a prosperous Year of the Horse!') // 注释";
exprtk::expression<double> e; // 三板斧之一:表达式
exprtk::symbol_table<double> t; // 三板斧之二:符合表
exprtk::parser<double> p; // 三板斧之三:解析器
exprtk::rtl::io::println<double> println; // 内置函数
t.add_function("say", println);
t.add_stringvar("wishs", wishs);
e.register_symbol_table(t);
if (!p.compile(s, e)) {
std::cerr << "编译失败!" << p.error() << std::endl;
return;
}
// 必须求值,才能让脚本 run 起来!
e.value();
}
void demo_1()
{
// 计算 2 * x + sin(y)
std::string const s = "2 * x + sin(y)";
double x = 0.0, y = 0.0;
exprtk::expression<double> e;
exprtk::symbol_table<double> t;
e.register_symbol_table(t); // 绑定符号表
t.add_variable("x", x);
t.add_variable("y", y);
exprtk::parser<double> p;
if (!p.compile(s, e)) {
std::cerr << "编译失败!" << p.error() << std::endl;
return;
}
x = 5.0;
y = 3.14159 / 2; // 编译之后修改值
double value = e.value();
// 输出计算的结果
auto showResult = [&]() {
std::cout << "x = " << x << ", y = " << y << "\n" << s << " = " << value << "\n---------\n";
};
showResult();
x = 10.1;
y = 3.14159 * 2 / 3.0;
value = e.value();
showResult();
}
void demo_2() // 循环求累加值
{
std::string const s = " if (step <= 0) return ['Error!']; \n"
" if (first > last) { swap (first, last); }\n"
" var s := 0; \n"
" for (var i := first; i<=last; i += step) { \n"
" s += i; \n"
" }";
exprtk::expression<double> e;
exprtk::symbol_table<double> t;
e.register_symbol_table(t);
double first = 1, last = 100, step = 1;
t.add_variable("first", first);
t.add_variable("last", last);
t.add_variable("step", step);
exprtk::parser<double> p;
if (!p.compile(s, e)) {
std::cerr << "编译失败!" << p.error() << std::endl;
return;
}
auto normal_value = e.value();
auto showResult = [&]() {
if (!e.return_invoked()) {
std::cout << normal_value << std::endl;
// std::cout << e.value() << std::endl; // 也可以直接再次调用
} else {
// 因为此时发生了return 调用,所以无法简单地从e.value() 获取表达式求值结果
auto const& results = e.results(); // 结果数组
if (results.count() > 0) {
std::string msg;
results.get_string(0, msg);
std::cout << "出错了!表达式返回:" << msg << std::endl;
} else {
std::cout << "出错了!表达式返回空!" << std::endl;
}
}
std::cout << "==============\n";
};
showResult();
first = 100;
last = 1; // step 1
e.value();
std::cout << "first = " << first << ", last = " << last << "\n";
showResult();
step = 0; // 错误设定
e.value();
showResult();
}
// C++ 直接求 fibonacci 数列
long double fibonacci(int n)
{
long double curr = 1;
switch (n) {
case 0:
return 0;
case 1:
return 1;
default: {
long double prev = 0;
while ((n -= 1) > 0) {
auto tmp = prev;
prev = curr;
curr += tmp;
}
}
}
return curr;
}
class ExprFibonacci {
long double x; // 级数
exprtk::expression<long double> e;
exprtk::parser<long double> p;
exprtk::function_compositor<long double> compositor;
public:
explicit ExprFibonacci(int n)
: x(n)
{
std::string const s = "fibonacci(x)";
using Function = exprtk::function_compositor<long double>::function;
this->compositor.add(Function("fibonacci")
.var("x")
.expression(" switch \n"
" { \n"
" case x == 0 : 0; \n"
" case x == 1 : 1; \n"
" default : \n"
" { \n"
" var prev := 0; \n"
" var curr := 1; \n"
" while ((x -= 1) > 0) \n"
" { \n"
" var temp := prev; \n"
" prev := curr; \n"
" curr += temp; \n"
" }; \n"
" }; \n"
" } \n"));
exprtk::symbol_table<long double>& t = compositor.symbol_table();
t.add_variable("x", x);
e.register_symbol_table(t);
if (!this->p.compile(s, this->e)) {
std::cerr << "编译失败!" << p.error() << std::endl;
}
}
// 要修改计算的级别数
void SetCount(int count) { x = count; }
// 取值
long double Calc() const
{
assert(p.error_count() == 0);
return this->e.value();
}
};
int64_t test_fibonacci_cpp_immediate(std::vector<long double>& values)
{
auto start = std::chrono::system_clock::now();
for (int i = 1; i <= 100; ++i) {
values.push_back(fibonacci(i));
}
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start).count();
}
int64_t test_fibonacci_exprTkScript(std::vector<long double>& values)
{
auto start = std::chrono::system_clock::now();
ExprFibonacci ef(1);
for (int i = 1; i <= 100; i++) {
values.push_back(ef.Calc());
ef.SetCount(i + 1);
}
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start).count();
}
void demo_3()
{
std::vector<long double> values_1, values_2;
values_1.reserve(100);
values_2.reserve(100);
auto d1 = test_fibonacci_cpp_immediate(values_1);
auto d2 = test_fibonacci_exprTkScript(values_2);
std::cout << "# -immediate-\t-exprTk-\n";
for (int i = 0; i < 100; ++i) {
std::cout << i + 1 << ":\t" << values_1[i] << "\t" << values_2[i] << "\n";
}
std::cout << "duration:\t" << d1 << "微秒\t" << d2 << "微秒\n";
}
void demo_4() // 牛顿开根法
{
std::string const s = "newton_sqrt(x)";
double x = 0.0;
exprtk::expression<double> e;
exprtk::symbol_table<double> t;
e.register_symbol_table(t);
t.add_variable("x", x);
exprtk::function_compositor<double> compositor(t); // 使用外部符号表
compositor.add(exprtk::function_compositor<double>::function("newton_sqrt")
.var("x")
.expression(" switch "
" { "
" case x < 0 : null; "
" case x == 0 : 0; "
" case x == 1 : 1; "
" default: "
" { "
" var remaining_itrs := 100; "
" var sqrt_x := x / 2; "
" repeat "
" if (equal(sqrt_x * sqrt_x, x)) "
" break[sqrt_x]; "
" else "
" sqrt_x := (1 / 2) * (sqrt_x + (x / sqrt_x)); "
" until ((remaining_itrs -= 1) <= 0); "
" }; "
" } "));
exprtk::parser<double> p;
if (!p.compile(s, e)) {
std::cerr << "编译失败!" << p.error() << std::endl;
return;
}
std::cout << "# " << "\tnewton::sqrt\tstd::sqrt\n";
for (; x < 500; x += 0.5) {
std::cout << x << "\t";
std::cout << e.value() << "\t" << std::sqrt(x) << "\n";
}
}
void demo_5() // 割圆术-祖冲之求 pai 值
{
std::string const s = R"(
var iterations := 15; var n := 6;
var side_length := 1.0; var r := 1.0;
for (var i := 0; i < iterations; i+= 1) {
var half_perimeter : = n * side_length / 2.0;
var a : = side_length / 2.0;
var h : = sqrt(r * r - a * a);
var b : = r - h;
side_length:= sqrt(a * a + b * b);
n *= 2;
};
n * side_length / 2.0;
)";
exprtk::expression<double> e;
exprtk::parser<double> p;
if (!p.compile(s, e)) {
std::cerr << "编译出错!" << p.error() << std::endl;
return;
}
std::cout << "π = " << e.value() << std::endl;
}
int main()
{
demo_0();
demo_1();
demo_2();
demo_3();
demo_4();
demo_5();
}