|
工程量计算式求值,是工程量计算表的一项基础功能,是EXCEL在工程行业应用中的一个小难题。为解决这个问题,不少人想了各种各样的办法。
方法一、最早的Evaluate宏表函数+定义名称的办法,简单易用,流传颇广,但有一个明显的局限:受Evaluate宏表函数的限制,表达式的字符串长度不能超过255个字符,而且由于注释字符的转换会额外增加字符数量,导致表达式的字符数量甚至经常不能超过200个字符,否则就会出错。
方法二、针对方法一中的注释字符的影响,使用VBA做相应的改进:在用Evaluate求值之前,通过自定义函数先得到去除了注释内容的“纯净”的表达式,这样就可以保证计算式中有效长度最大可以达到255个字符的上限。此方法可以在自定义函数中使用宏表函数Evaluate,因此可以免去自定义名称的麻烦,但仍然没有完全突破字符长度的限制。
方法三、由于Evaluate宏表函数的参数长度不能超过255字符,是其本身的固有局限,要想完全突破字符长度限制,又进化出两种改进办法:一种是完全放弃Evaluate函数,改用vbs或js的Eval函数方法来求值。另一种是,对表达式做分段拆分,然后分别用Evaluate函数求值。
这两个改进方法中,后者需要准确合理的拆分计算式,其实是有相当的技术难度的,因为这相当于重新编写一个增强的Evaluate函数,因此能完美实现的很少,样例也很少;
而前者,需用Msscriptcontrol控件作为vbs代码或js代码的容器,才能让eval函数发挥作用,但这里涉及的步骤和代码并不复杂,于是成为新的潮流,而且由于Eval函数不限制参数长度,几乎就是完美的解决方案了。
之所以说“几乎”,因为它又带来了另一个局限:Msscriptcontrol控件是在32位office时代开发的,它只能应用在32位office环境中,而不能适用于64位office环境,之后微软并没有提供相应的64位控件。这个不足之处,在几年之前还算不上多大的缺限,因为那时候使用64位office的人员所占比例不大。但是,随着软硬件的快速更新换代,现在使用的电脑,主流OS平台一般都是64位的win7或win10了,其应用系统和办公软件也快速进入64位时代,于是32位旧代码如何适应新的64位office软件平台的问题就急切需要解决了。
方法四、
要解决方法三中使用32位的Msscriptcontrol控件在64位office平台上的兼容问题,首先想到的就是使用该控件的64位版本,但微软明显不愿意再提供此控件的64位版本,而且也没有第三方愿意做这样的事情,那么就只能由用户自行想办法解决了。
这种情况下,有的是制作dll格式的自定义函数文件,有的根据EXCEL的事件代码编写加载宏,在表达式输入完成时自动将其转换成Excel公式来完成计算。制作dll格式的自定义函数文件,需要有一些VB或VC软件开发经验,一般VBA爱好者都望而却步;而用EXCEL的内置事件驱动的自动宏代码,不如自定义函数代码便于移植。
而我,偶然想到,方法三中,作为vbs或js代码容器的msscriptcontrol控件,存在着不能跨平台的限制,但vbs或js本身却是可以跨平台的脚本语言,只要为它们重新找一个可以与平台无关的容器,或许问题就能够解决。再又想到html文档也可以作为vbs或js代码的容器,而html文档显然是可以跨32位和64位平台的。
就此,找到了一个突破口,写成了一个自认为还不错的计算式求值的VBA自定义函数:EEVAL,然后又几经改进,于是就有了这个主题贴,和下面的附件,特此与大家共享:- Option Explicit
- Dim oReg As New RegExp
- Function EEVAL(ByVal s$) As Double '计算复杂文本描述型表达式的值 利用正则消除无效字符 JS eval计算超长计算式
- Dim a$, b$, i%, oDom As Object, oWin As Object, strJS$
- a = "+-×÷()"
- b = "+-*/()"
- For i = 1 To 6
- s = Replace(s, Mid(a, i, 1), Mid(b, i, 1))
- Next
- s = VBA.LCase(s)
- With oReg
- .Global = True
- .IgnoreCase = True
- .Pattern = "(\w+(?:\([^\(\)]*\))?)\^(\w+(?:\([^\(\)]*\))?)"
- s = .Replace(s, "pow($1,$2)") '支持幂运算符
- .Pattern = "\b(sqrt|round|random|pow(er)?|PI|min|max|log|Int|floor|exp|E|ceil|abs|a?tan2?|a?sin|a?cos)\b"
- s = .Replace(LCase(s), "Math.$1") '支持基本数学函数和两个重要常数(圆周率和自然常数)
- .Pattern = "\[[^\[\]]*\]|(Math\.)(?=\1)"
- s = .Replace(s, "")
- .Pattern = "\b(Math\.)?Int"
- s = .Replace(s, "parseInt") '支持Int函数
- .Pattern = "pow(er)?"
- s = .Replace(s, "pow")
- .Pattern = "Math.e\b"
- s = .Replace(s, "Math.E")
- .Pattern = "\.pi\(\)"
- s = .Replace(s, ".PI")
- End With
- Set oDom = CreateObject("htmlfile")
- Set oWin = oDom.parentWindow
- oDom.write Replace("<script Language = JavaScript> function eEvaluate(){return eexp} </script>", "eexp", s)
- EEVAL = oWin.eval("eEvaluate()")
- End Function
复制代码
EXCEL算量表计算式求值自定义函数.rar
(21.63 KB, 下载次数: 328)
补充内容 (2019-8-8 13:23):
更新:修正3处Bug。详见23楼。 |
评分
-
9
查看全部评分
-
|