muParser公式库使用简介( 转)
muParser是一个跨平台的公式解析库,它可以自定义多参数函数,自定义常量、变量及一元前缀、后缀操作符,二元操作符等,它将公式编译成字节码,所以计算起来非常快。
当前版本V1.28,官方网址http://sourceforge.net/projects/muparser/,这里是关于该库使用交流https://sourceforge.net/forum/forum.php?forum_id=462843
它提供两种方式使用,一种是将它编译进你的程序中,使用C++类,另一种是将它编译成共享库,可以使用其它语言调用,下面主要介绍C接口方式使用该库,本文大部分例子都是muParser库中自带的,也包括本人使用过程中的一点点心得。
一、初始化公式库
#include “muParserDLL.h”
muParserHandle_t hParser;
hParser = mupCreate();//创建公式对象,并返回该对象的句柄
二、设置公式
muChar_t szLine[100];
mupSetExpr(hParser, szLine);//这里szLine一定要是以\0结尾的字符串
三、计算公式
muFloat_t fVal = 0,
fVal = mupEval(hParser);
计算公式中的数值类都是以浮点处理的,其实就是double类型,除了计算浮点数,还可以处理字符串参数
四、释放公式对象
mupRelease(hParser);
五、变量,常量操作
1.定义字符串常量
mupDefineStrConst(hParser, "strBuf", "Hallo welt");
它表示定义一个值为"Hallo welt”的字符串常量strBuf,你可以在公式引用该常量的值,如:a=strBuf, 它表示将strBuf的值赋给变量a,当然它要求a为字符串变量
2.定义数值常量
mupDefineConst(hParser, "const1", 1); //表示定义一个名为const1值为1的数值常量
注意常量只能引用它的值,不能改变它的值
3.定义数值变量
muFloat_t var;
mupDefineVar(hParser, "a", &var);
在公式中你可以随意引用或改变变量a的值,注意如果你要全局使用自定的变量a,则不能将变量var定义为局部变量,否则会引起内存地址非法访问
六.自定函数
自定义函数有以下几种原型
typedef muFloat_t (*muFun1_t)(muFloat_t);
typedef muFloat_t (*muFun2_t)(muFloat_t, muFloat_t);
typedef muFloat_t (*muFun3_t)(muFloat_t, muFloat_t, muFloat_t);
typedef muFloat_t (*muFun4_t)(muFloat_t, muFloat_t, muFloat_t, muFloat_t);
typedef muFloat_t (*muFun5_t)(muFloat_t, muFloat_t, muFloat_t, muFloat_t, muFloat_t);
typedef muFloat_t (*muMultFun_t)(const muFloat_t*, muInt_t);
typedef muFloat_t (*muStrFun1_t)(const muChar_t*);
typedef muFloat_t (*muStrFun2_t)(const muChar_t*, muFloat_t);
typedef muFloat_t (*muStrFun3_t)(const muChar_t*, muFloat_t, muFloat_t);
分为三大类,即固定数值参数的函数(1个或2个或3个或4个或5个数值参数),固定带字符串参数的函数(1个字符串参数,0个或1个或2个数值参数),可变参数函数(一个数值数组参数,一个数组大小参数),下面分别介绍
1.固定数值参数的函数
函数原型:
API_EXPORT(void) mupDefineFun1(muParserHandle_t a_hParser, const muChar_t *a_szName, muFun1_t a_pFun, muBool_t a_bOptimize);
API_EXPORT(void) mupDefineFun2(muParserHandle_t a_hParser, const muChar_t *a_szName, muFun2_t a_pFun, muBool_t a_bOptimize);
API_EXPORT(void) mupDefineFun3(muParserHandle_t a_hParser, const muChar_t *a_szName, muFun3_t a_pFun, muBool_t a_bOptimize);
API_EXPORT(void) mupDefineFun4(muParserHandle_t a_hParser, const muChar_t *a_szName, muFun4_t a_pFun, muBool_t a_bOptimize);
API_EXPORT(void) mupDefineFun5(muParserHandle_t a_hParser, const muChar_t *a_szName, muFun5_t a_pFun, muBool_t a_bOptimize);
//定义一个参数的函数,对参数平方运算
muFloat_t Power(muFloat_t a)
{
return a*a;
}
//定义两个参数的函数,求和
muFloat_t Add(muFloat_t a, muFloat b)
{
return a+b;
}
mupDefineFun1(hParser, “power”, Power, 0);//最后一个参数表示是否对它进行优化
mupDefineFun2(hParser, “add”, Add, 0);
公式使用方法:power(4)*add(3,4)
2.固定带字符串参数的函数
函数原型:
API_EXPORT(void) mupDefineStrFun1(muParserHandle_t a_hParser, const muChar_t *a_szName, muStrFun1_t a_pFun);
API_EXPORT(void) mupDefineStrFun2(muParserHandle_t a_hParser, const muChar_t *a_szName, muStrFun2_t a_pFun);
API_EXPORT(void) mupDefineStrFun3(muParserHandle_t a_hParser, const muChar_t *a_szName, muStrFun3_t a_pFun);
//定义一个带字符串参数的函数,将字符串表示的十六进制数转换成十进制数
muFloat_t hex(const muChar_t* str)
{
int len = strlen(str);
int ret, tmp, i;
if(len <= 0)
return 0;
for (i = 0; i < len; i++)
{
if(str[i]>= '0'&& str[i]<= '9')
tmp = str[i]-'0';
else if(str[i]>= 'a'&& str[i] <= 'f')
tmp = str[i]-'a'+10;
else if(str[i]>='A'&& str[i]<='F')
tmp = str[i]-'A'+10;
else
return ret;
ret = (ret<<4) | tmp;
}
return ret;
}
mupDefineStrFun1(hParser, “hex”, hex);
公式使用方法: hex(“0FA0”),公式中的字符中参数用双引号括起来
3.可变参数函数
函数原型:
API_EXPORT(void) mupDefineMultFun( muParserHandle_t a_hParser,
const muChar_t* a_szName,
muMultFun_t a_pFun,
muBool_t a_bOptimize);
//求多个参数的和,参数个数不确定
double Sum(const double *a_afArg, int a_iArgc)
{
double fRes=0;
int i=0;
for (i=0; i<a_iArgc; ++i)
fRes += a_afArg[i];
return fRes;
}
mupDefineMultFun(hParser, “sum”, sum, 0);
公式使用:sum(1,2,3,4,5,6,7,8,9)
在公式解析的时候,会自动将所有的参数压入到一个数组中,并将该数组和参数个数传给自定义回调函数
七、定义运算操作符(这可是非常有新意的功能)
运算操作符分为前缀一元操作符,后缀一元操作符,二元操作符等
完全可以实现C中的按位运算(&, | ~ << >>),自加减运算(++ --),但不能与库的内置操作符冲突
//按位操作运算(注意按位运算只能运算整型,所以运算前要先转换)
double BitAnd(double a, double b)
{
return (int)a&(int)b;
}
double BitOr(double a, double b)
{
return (int)a|(int)b;
}
double BitXor(double a, double b)
{
return (int)a^(int)b;
}
double BitNot(double a)
{
return ~(int)a;
}
double Shll(double a, double b)
{
return (int)a<<(int)b;
}
double Shlr(double a, double b)
{
return (int)a>>(int)b;
}
mupDefineOprt(hParser, "&", BitAnd, 2, 0);
mupDefineOprt(hParser, "|", BitOr, 2, 0);
mupDefineOprt(hParser, "bxor", BitXor, 2, 0);
mupDefineInfixOprt(hParser, "~", BitNot, 5);
mupDefineOprt(hParser, "<<", Shll, 3, 0);
mupDefineOprt(hParser, ">>", Shlr, 3, 0);
公式使用:((a&b)>>8)bxor((a|b)<<8)
注意该库解析还有一些BUG,所以上面公式并不能正确解析,在自定义的运算符与变量之间添加空格可以暂时解决这个问题,如((a & b)>> 8)bxor((a | b)<<8),在本文后面会介绍如何修正该BUG
八、错误处理
错误处理是很重要的,因为库是用C++写的,对错误采用异常处理方式,所以如果公式解析出错,而又没有捕捉该异常,程序就会挂掉,C接口没有捕捉异常的能力,这里可以使用安装错误处理回调函数解决这个问题,使用方法如下:
void OnError(muParserHandle_t hParser)
{
printf("\nError:\n");
printf("------\n");
printf("Message: %s\n", mupGetErrorMsg(hParser));
printf("Token: %s\n", mupGetErrorToken(hParser));
}
mupSetErrorHandler(hParser, OnError);
九、显示公式对象的已定义变量,常量,表达式
这些功能使用非常简单,首先取得变量或常量或表达式的个数,然后根据对应的索引位置取出它们的值,我就不多说了,附上相关的API声明
API_EXPORT(int) mupGetExprVarNum(muParserHandle_t a_hParser);
API_EXPORT(int) mupGetVarNum(muParserHandle_t a_hParser);
API_EXPORT(int) mupGetConstNum(muParserHandle_t a_hParser);
API_EXPORT(void) mupGetExprVar(muParserHandle_t a_hParser, unsigned a_iVar, const muChar_t** a_pszName, muFloat_t** a_pVar);
API_EXPORT(void) mupGetVar(muParserHandle_t a_hParser, unsigned a_iVar, const muChar_t** a_pszName, muFloat_t** a_pVar);
API_EXPORT(void) mupGetConst(muParserHandle_t a_hParser, unsigned a_iVar, const muChar_t** a_pszName, muFloat_t* a_pVar);
十、变量工厂
也就是在解析到一个未定义的标识符时,根据用户注册的回调函数规则自动创建以该标识符命名的变量,如下示例:
muFloat_t* AddVariable(const muChar_t* a_szName, void* pUserData)
{
static double afValBuf[PARSER_MAXVARS];//定义变量缓冲区
static int iVal = 0;
printf("Generating new variable \"%s\" (slots left: %d)\n", a_szName, PARSER_MAXVARS-iVal);
afValBuf[iVal] = 0;
if (iVal>=PARSER_MAXVARS-1)
{
printf("Variable buffer overflow.");
return NULL;
}
return &afValBuf[iVal++]; //返回存储该变量值的地址
}
mupSetVarFactory(hParser, AddVariable, NULL);
十一、关于BUG
1.解析操作符的问题,如果你定义了“&”操作符, 那么公式 a&b不会工作,它首先解析出变量a,然后解析”&b”, 发现”&b”即不是操作符,也不是常量,变量,或函数,最后就抛出异常,问题出在muParserTokenReader.cpp文件中的ExtractToken函数的算法上,
int ParserTokenReader::ExtractToken( const char_type *a_szCharSet,
string_type &a_sTok, int a_iPos ) const
{
int iEnd = (int)m_strFormula.find_first_not_of(a_szCharSet, a_iPos);
if (iEnd==(int)string_type::npos)
iEnd = (int)m_strFormula.length();
a_sTok = string_type( m_strFormula.begin()+a_iPos, m_strFormula.begin()+iEnd);
a_iPos = iEnd;
return iEnd;
}
它采用find_first_not_of来搜索给定字符串与指定集合不匹配的第一个元素位置,因为操作符命名可以包含算术运算符,英文字母 ,所以会认为&b也是一个合法的操作符名,但要修正不能修改该函数,因为变量,函数等解析都会调用该函数,我们要修正的只是操作符解析算法,即IsOprt函数
以下是我的解决方法:
bool ParserTokenReader::IsOprt(token_type &a_Tok)
{
string_type strTok;
int iEnd = (int)m_strFormula.find_first_not_of(m_pParser->ValidOprtChars(), m_iPos);
if(iEnd == string_type::npos)
iEnd = m_strFormula.length();
while(iEnd>m_iPos)
{
strTok = string_type(m_strFormula.begin()+m_iPos, m_strFormula.begin()+iEnd);
funmap_type::const_iterator item = m_pOprtDef->find(strTok);
if(item!=m_pOprtDef->end())
break;
iEnd--;
}
//int iEnd = ExtractToken(m_pParser->ValidOprtChars(), strTok, m_iPos);
if (iEnd==m_iPos)
return false;
funmap_type::const_iterator item = m_pOprtDef->find(strTok);
if (item==m_pOprtDef->end())
return false;
a_Tok.Set(item->second, strTok);
if (m_iSynFlags & noOPT)
{
// An operator was found but is not expected to occur at
// this position of the formula, maybe it is an infix
// operator, not a binary operator. Both operator types
// can share characters in their identifiers.
if ( IsInfixOpTok(a_Tok) ) return true;
// nope, no infix operator
Error(ecUNEXPECTED_OPERATOR, m_iPos, a_Tok.GetAsString());
}
m_iPos = (int)iEnd;
m_iSynFlags = noBC | noOPT | noCOMMA | noPOSTOP | noEND | noBC | noASSIGN;
return true;
}
思路是首先读出最长合法操作符名,然后判断是否被定义了,如果没有则再读取次长操作符名判断是否被定义,依次类推,直到字符串为空,如果找到操作符名被定义了,则立即返回,当然此法也可能引起其它的问题。
2.也是关于操作符解析问题,如果你定义了一个名为or的二元操作符,同时还定义了一个名为order的函数,那么解析时会将order解析为or加上未定义的der,不过这个问题在下一个版本中已经修正了。解决方法如下:
将函数ReadNextToken中的如下语句交换即可
if ( IsOprt(tok) ) return SaveBeforeReturn(tok);
if ( IsBuiltIn(tok) ) return SaveBeforeReturn(tok);
//To:
if ( IsBuiltIn(tok) ) return SaveBeforeReturn(tok);
if ( IsOprt(tok) ) return SaveBeforeReturn(tok);
3.在错误处理时应该注意,不能同时调用以下函数,否则程序一定挂掉
API_EXPORT(const muChar_t*) mupGetErrorMsg(muParserHandle_t a_hParser);
API_EXPORT(muInt_t) mupGetErrorCode(muParserHandle_t a_hParser);
API_EXPORT(muInt_t) mupGetErrorPos(muParserHandle_t a_hParser);
API_EXPORT(const muChar_t*) mupGetErrorToken(muParserHandle_t a_hParser);
API_EXPORT(const muChar_t*) mupGetErrorExpr(muParserHandle_t a_hParser);
原因很简单,当解析出错时,一定有错误消息和错误标识符,但不一定有错误码、出错位置、出错表达式(表达式可能为空),这应该是对应C接口上的设计缺陷。
转自:http://blog.chinaunix.net/uid-20619801-id-1612046.html
另外
数学表达式解析器 Expression4J
Expression4J是一个基于Java的开源框架,它用于对数学表达式进行操作,是一个数学公式解析器,在Expression4J中可以将数学表达式存储在字符串对象中,如“f(x,b)=2*x-cos(b)”和“g(x,y)=f(y,x)*-2”等。Expression4J是高度定制的,用户可以自定义文法,其主要功能包括实数和复数的基本数学运算,支持基本数学函数(如sin、cos等函数)、复杂函数(如f(x)=2*x+5、g(x)=3*f(x+2)-x等)以及用户使用Java语言自定义的函数和文法,还可以定义函数目录(函数集)、支持XML配置文件等。目前它还不是一个十分成熟的框架,仍在不断完善中。
项目主页:http://www.open-open.com/lib/view/home/1341366018031
muParser公式库使用简介( 转)的更多相关文章
- Raphael Js矢量库API简介:
Raphael Js矢量库API简介:Raphael Javascript 是一个 Javascript的矢量库. 2010年6月15日,著名的JavaScript库ExtJS与触摸屏代码库项目jQT ...
- c++ 日志输出库 spdlog 简介(1)
参考文章: log库spdlog简介及使用 - 网络资源是无限的 - CSDN博客 http://blog.csdn.net/fengbingchun/article/details/78347105 ...
- Python3.x:日期库dateutil简介
Python3.x:日期库dateutil简介 安装 pip install python-dateutil 关于parser #字符串可以很随意,可以用时间日期的英文单词,可以用横线.逗号.空格等做 ...
- 基于 groovy 实现公式库
formula 基于 groovy 实现的公式库 项目地址 Github 语法 公式名(参数) 比如: ECHO(大侠王波波) 支持公式嵌套: 公式名1(公式名2(参数), 参数) 比如: ECHO( ...
- STL标准模板库(简介)
标准模板库(STL,Standard Template Library)是C++标准库的重要组成部分,包含了诸多在计算机科学领域里所常见的基本数据结构和基本算法,为广大C++程序员提供了一个可扩展的应 ...
- lua工具库penlight--01简介
lua的设计目标是嵌入式语言,所以和其它动态语言(如python.ruby)相比其自带的库缺少很多实用功能. 好在有lua社区有Penlight,为lua提供了许多强大的功能,接下来的几篇博客,我会简 ...
- Python 外部函数调用库ctypes简介
Table of Contents 1. 参考资料 2. ctypes简介 2.1. 数据类型 2.2. 调用.so/.dll 2.2.1. 加载动态链接库 2.2.2. 调用加载的函数 2.2.3. ...
- libcurl库的简介(一)
一.Libcurl库简介 LibCurl是免费的客户端URL传输库,支持FTP,FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, FILE ,LDAP ...
- Java SSH库使用简介:Apache sshd和JSch(Java Secure Channel)
1.Apache sshd Apache sshd是一个SSH协议的100%纯Java库,支持客户端和服务器.sshd库基于Apache MINA项目(可伸缩高性能的异步IO库). 官方网站:http ...
随机推荐
- ASP.NET对HTML元素进行权限控制(二)
这是这个权限控制的第一步,扫描界面把要分配权限的元素的信息获取出来存入到数据库中. 这一步分三小步: (1).标出界面所要分配权限的元素 (2).扫描界面获取所要分配权限的元素信息.(ID,标题,层级 ...
- 多线程 1-pthread 和NSThread
一.基本内容介绍: 进程: 正在运行的程序就叫进程 每个进程之间是相互独立的,每个进程均运行在其专用且受保护的内存空间内. 线程: 在程序内工作的基本执行单元(每个进程至 ...
- iOS常见问题(5)
一.注意将之前storyboard中控制器删除之后,拖入一个新的控制器的时候,stroyboard中控制器的class也要重新填入自己想要展示的控制器,告诉stroyboard去加载哪个控制器. 二. ...
- 下拉刷新ListView实现原理
(1)主要是onScroll()方法和onTouchEvent()方法,先是onTouchEvent()的ACTION_DOWN,然后是 ACTION_MOVE和onScroll()方法同时进行,最后 ...
- 管道Pipe
管道Pipe java.nio.channels包中含有一个名为Pipe(管道)的类.广义上讲,管道就是一个用来在两个实体之间单向传输数据的导管.管道的概念对于Unix(和类Unix)操作系统的用户来 ...
- C# 数据结构--排序[上]
概述 看了几天的排序内容,现在和大家分享一些常见的排序方法. 啥是排序? 个人理解的排序:通过对数组中的值进行对比,交换位置最终得到一个有序的数组.排序分为内存排序和外部排序.本次分享排序方法都为内存 ...
- 【HDOJ】【1512】Monkey King
数据结构/可并堆 啊……换换脑子就看了看数据结构……看了一下左偏树和斜堆,鉴于左偏树不像斜堆可能退化就写了个左偏树. 左偏树介绍:http://www.cnblogs.com/crazyac/arti ...
- 基于密度的聚类之Dbscan算法
一.算法概述 DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法.与划分和层次 ...
- 设置配置文件信息时的classpath
首先 classpath是指 WEB-INF文件夹下的classes目录 其中:lib和classes下文件访问优先级的问题: lib>classes classpath 和 classpa ...
- POJ 1060 Modular multiplication of polynomials(多项式的加减乘除,除法转化成减法来求)
题意:给出f(x),g(x),h(x)的 (最高次幂+1)的值,以及它们的各项系数,求f(x)*g(x)/h(x)的余数. 这里多项式的系数只有1或0,因为题目要求:这里多项式的加减法是将系数相加/减 ...