ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 1450|回复: 11

[分享] 关于EXCEL超长计算式的解决方案

[复制链接]

TA的精华主题

TA的得分主题

发表于 2021-9-21 17:04 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
今天分享的是,关于excel中超长计算式的解决方案。
自定义函数.rar (19.07 KB, 下载次数: 54)

刚才分享了一篇关于在excel中,用EVALUATE函数自动计算表达式结果,且支持注释(可在本人主页中查看)。
但是那个方法有两个缺陷:
1、注释的位置必须统一,要么在数值前面,要么在数值后面,否则结果就会出错。


2、计算式太长,结果就会出错。



但是作为建筑行业从业人员,在需要手动算量时,难免遇到一些复杂情况,写出一个超级长的计算式。为解决以上两个痛点,写了一个自定义函数,大家演示效果。
一、加载宏
下载开头的加载宏文件,并加载到excel中,就可以使用。下面是加载宏的演示。


二、使用函数
当把自定义函数加载宏文件加载到excel中时,这个函数就可以像excel的内置函数一样使用了。这个函数的特点:
1、可以计算超长字符的计算式;
2、对注释内容的位置没有要求,可以随意在计算式中做注释;
3、计算式中支持使用excel的内置函数;
4、可以直接引用单元格,比定义名称更方便。


三、移植代码
如果在文件中使用了加载宏的函数,这个文件发送给其他人后,若没有加载自定义函数的代码,结果就会显示错误。怎么样让这个函数和文件放到一起呢?就需要将自定义函数的代码放到需要这个函数的文件中,下面是操作演示::


1、先在VBE编辑器中,打开加载宏文件的自定义函数代码,全部复制下来;
2、在需要这个函数的文件中,在VBE界面插入【模块】,然后将代码粘贴到模块中;
3、取消加载宏后,就可以正常使用此函数了,并且函数也和文件一起了。

三、这个自定义函数的思路
如果只需要这个函数,看到这里就可以了。下面介绍的是这个自定义函数的思路。
由于【EVALUATE】函数不支持超过255字符的计算式,那么,就通过计算式拆分的方式,将一个超长的计算式不断拆分、简化和计算,使其长度最终小于255个字符的目的,最后再使用【EVALUATE】函数计算出结果。
清除注释信息→将函数公式部分转换为结果→将括号部分转化为结果→得到一个无括号的四则运算计算式→(若计算式长度>255字符)→再从左开始以×/÷号为分隔符号,截取小于255字符的一段计算式,将其转化为结果,替换到计算式中;如此反复操作,直到计算式的长度≤255个字符。
1、清除计算式中的注释信息。
文字注释要占用计算式的长度,且会影响计算,所以首先就是将注释内容清除,这是简化计算式的第一步;
2、将计算式中的函数公式部分转化为结果
函数有括号,且有些函数的参数不能提前转换为结果,所以在简化括号内容之前,需要先将函数公式部分转换为结果,并用结果替换掉原来的函数公式部分。
3、简化括号
在四则运算中,括号的运算级别较高,为避免结果出错,所以需要先对括号内的内容进行计算。
简化括号,难点在于判断括号的层数、判断哪个左括号和哪个右括号是一对的,尤其是遇到复杂的括号时,如果判断不对,就会导致计算结果出错。只有判断正确了,才能将一对括号中的内容提取出来,并正确地转化为结果,然后导入到计算式中。
然后一步一步地重复这个操作,直到所有括号都简化完毕,就得到一个无括号的四则运算的计算式。
4、简化计算式
如果经过上述操作,简化后最终的计算式还大于256字符,那么就用加减符号将计算式拆分为无数段,按照从左往右的顺序,先将乘除的部分计算成结果,然后替换到计算式中。然后不断重复上述操作,直到计算式长度达到要求。

经过以上4个步骤,再长的计算式,几乎都可以简化,那么,只要excel单元格中能录入的计算式,用这个自定义函数都可以计算了。
四、函数代码如下:
加上函数代码,文章内容就超长了,代码在楼下分享。


评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-9-21 17:06 | 显示全部楼层
四、函数代码如下:
  1. Function MyValue(Rng As Range, Optional Point As Integer = 2)
  2.     '===================================清除计算式的注释===================================
  3.      Dim Tmp As String, 计算式 As String
  4.     '====================================修正运算符号====================================
  5.      Tmp = Rng.Value                                      '将单元格内容赋值给变量
  6.      Tmp = Replace(Tmp, " ", "")                          '清除计算式中的空格
  7.      Tmp = Replace(Replace(Tmp, "(", "("), ")", ")")    '中文括号转换为英文括号
  8.      Tmp = Replace(Replace(Tmp, "[", "("), "]", ")")      '中括号转换为小括号
  9.      Tmp = Replace(Replace(Tmp, "{", "("), "}", ")")      '大括号转换为小括号
  10.      Tmp = Replace(Replace(Tmp, "×", "*"), "÷", "/")    '乘号、除号转换为计算符号
  11.      
  12.     '===================================清除计算式的注释===================================
  13.     '注释内容一定要用【】标记,否侧会影响后面的判断
  14.      If VBA.InStr(Tmp, "【") > 0 Then
  15.         Dim i As Integer, j As Integer
  16.         For i = 1 To VBA.Len(Tmp)
  17.             If VBA.Mid(Tmp, i, 1) = "【" Then
  18.                Do
  19.                   j = j + 1
  20.                Loop Until VBA.Mid(Tmp, i + j, 1) = "】" Or i + j >= VBA.Len(Tmp)
  21.                i = i + j
  22.                
  23.                If i > VBA.Len(Tmp) Then
  24.                   Exit For
  25.                Else
  26.                   j = 0
  27.                End If
  28.             ElseIf VBA.Mid(Tmp, i, 1) = "】" Then
  29.                
  30.             Else
  31.                计算式 = 计算式 & Mid(Tmp, i, 1)
  32.             End If
  33.         Next
  34.      Else
  35.         计算式 = Tmp
  36.      End If
  37.      
  38.     '清除注释完成,对计算式做初步判断
  39.      If Len(计算式) = 0 Then '简化后计算式为空,则无计算式,结果为空
  40.         MyValue = ""
  41.         Exit Function
  42.      ElseIf VBA.InStr("+-*/", Right(计算式, 1)) > 0 Then  '如果计算式最后一位是运算符号,则计算式错误,结果为错
  43.         MyValue = "错"
  44.         Exit Function
  45.      End If
  46.      
  47.     '左括号和右括号数量不相等时,计算式有误,结果为错。这里只能先判断括号是否成对
  48.      If VBA.Len(Replace(Tmp, "(", "")) <> VBA.Len(Replace(Tmp, ")", "")) Then
  49.         MyValue = "错"
  50.         Exit Function
  51.      End If
  52.      
  53.     '计算式中出现连续的运算符,计算式有误,结果为错
  54.      Dim Err, e As Byte
  55.      Err = Array("++", "+-", "+*", "+/", "-+", "-*", "-/", "--", "*+", "*-", "**", "*/", "/+", "/-", "/*", "//", ")(")
  56.      For e = 0 To UBound(Err)
  57.          If VBA.InStr(计算式, Err(e)) > 0 Then
  58.             MyValue = "错"
  59.             Exit Function
  60.          End If
  61.      Next
  62.      
  63.      If VBA.Len(计算式) <= 255 Then GoTo 计算      '如果计算式字符数≤255,则直接结算;否则,运行下面的代码
  64.      
  65.    '====================================超长计算式的简化、计算思路====================================
  66.    '/计算式长度>255字符,先将计算式中的函数公式通过计算,转化为数值,得到一个纯数字的计算式
  67.    '// 如果计算式中存在函数,函数的括号会影响括号的计算,所以需要先把函数部分先转换成结果
  68.    '/然后将所得计算式按照四则运算法则:括号→乘除→加减顺序,依次计算、简化,得到一个纯加减的计算式
  69.    '// 如果计算式长度>255字符,则以【+、-】为依据,从左开始截取一段小于255字符的计算式,将其转化为数值
  70.    '// 然后不断重复上一步操作,直到最终的计算式长度≤255字符,然后直接计算出结果
  71.    
  72.    '========================================计算式中的函数运算========================================
  73.    '判断计算式中是否存在函数,若存在,先依次计算函数(以免函数中的括号影响下一步括号计算)
  74.     Dim TmpStr1$, TmpValue1 As Double
  75.     Dim a1%, b1%, c1%, m1%, n1%, 长度差1%    '循环变量和计数器
  76.    
  77.     Do
  78.        a1 = a1 + 1         '逐个字符串判断
  79.        SS1 = VBA.Mid(计算式, a1, 1)
  80.        If VBA.InStr("0123456789~!@#$%^&( )+-*/^<=>.,""", VBA.Mid(计算式, a1, 1)) = 0 Then   '当前字符串非数值、运算符、括号、特殊符号
  81.           Do
  82.             b1 = b1 + 1    '遇到非数值、运算符、括号、特殊符号后,开始计数
  83.           Loop Until VBA.InStr("+-*/^()", VBA.Mid(计算式, a1 + b1, 1)) > 0   '逐一判断,直到出现下一个运算符
  84.          
  85.           If VBA.InStr("((", VBA.Mid(计算式, a1 + b1, 1)) > 0 Then   '如果下一个运算符是括号,则表示此处是函数【函数名称(函数参数)】
  86.              Do            '根据括号判断函数的嵌套层数
  87.                 c1 = c1 + 1
  88.                 SS2 = Mid(计算式, a1 + b1 + c1 - 1, 1)
  89.                 If VBA.InStr("((", Mid(计算式, a1 + b1 + c1 - 1, 1)) > 0 Then
  90.                    m1 = m1 + 1   '统计左括号数量
  91.                 ElseIf VBA.InStr("))", Mid(计算式, a1 + b1 + c1 - 1, 1)) > 0 Then
  92.                    n1 = n1 + 1   '统计右括号数量
  93.                 End If
  94.              Loop Until m1 > 0 And n1 > 0 And m1 = n1 Or a1 + b1 + c1 - 1 >= Len(计算式) '当左括号=右括号,或者字符数超过总字符数时,退出Do循环
  95.             
  96.              If m1 = n1 Then  '当左右括号相等时,把函数公式部分提取出来,计算结果
  97.                 TmpStr1 = Mid(计算式, a1, b1 + c1)
  98.                 TmpValue1 = Excel.Application.Evaluate(TmpStr1)
  99.                 计算式 = Application.Substitute(计算式, TmpStr1, TmpValue1, 1) '把函数结果写入计算式
  100.                 长度差1 = VBA.Len(TmpValue1) - VBA.Len(TmpStr1) - 1            '计算计算式与计算结果的字符数量差
  101.                 a1 = a1 + b1 + c1 + 长度差1      '根据a的位置,结合计算式长度和长度差,将得到的新起始位置赋值给a
  102.                 b1 = 0: c1 = 0: m1 = 0: n1 = 0   '计数器归零,以便下次使用
  103.              End If
  104.           End If
  105.        End If
  106.     Loop Until a1 >= VBA.Len(计算式)  '当a的值等于或超过计算式长度时,退出循环
  107.       
  108.     If VBA.Len(计算式) <= 255 Then GoTo 计算   '如果整理后的计算式长度小于等于255,则直接计算
  109.    
  110.    '======================================简化计算式中的括号运算======================================
  111.    '如果有括号,先计算括号内的内容
  112.     If VBA.InStr(计算式, "(") > 0 Then
  113.        Dim TmpValue2 As Double, TmpStr2$
  114.        Dim KaiShi%, JieShu%, Dic
  115.        Dim a2%, b2%, m2%, n2%, 长度差2%
  116.        Set Dic = CreateObject("Scripting.dictionary")
  117.       
  118.        Do
  119.           a2 = a2 + 1
  120.           If VBA.InStr("((", Mid(计算式, a2, 1)) > 0 Then          '当前字符是左括号
  121.              m2 = m2 + 1   '统计左括号数量,即括号层数
  122.              Dic(m2) = a2  '用字典记录每个左括号的位置
  123.           ElseIf VBA.InStr("))", Mid(计算式, a2 + 1, 1)) > 0 Then  '下一个字符是右括号
  124.              Do
  125.                 b2 = b2 + 1
  126.                 If VBA.InStr("))", Mid(计算式, a2 + b2 + 长度差2 - 1, 1)) > 0 Then
  127.                    n2 = n2 + 1     '依次判断右口号位置,逐层计算
  128.                    KaiShi = Dic(m2 - n2 + 1)
  129.                    JieShu = a2 + b2 + 长度差2 - 1
  130.                   
  131.                    TmpStr2 = Mid(计算式, KaiShi, JieShu - KaiShi + 1)
  132.                    TmpValue2 = Application.Evaluate(TmpStr2)
  133.                    计算式 = Application.Substitute(计算式, TmpStr2, TmpValue2)   '把括号结果写入计算式
  134.                    长度差2 = 长度差2 + VBA.Len(TmpValue2) - VBA.Len(TmpStr2) - 1 '计算计算式与计算结果的字符数量差
  135.                 End If
  136.              Loop Until a2 + b2 > VBA.Len(计算式) Or VBA.InStr("((", Mid(计算式, a2 + b2 + 长度差2, 1)) > 0   '下一位是左括号时
  137.             
  138.              a2 = a2 + b2 + 长度差2 - 1        '根据a的位置,结合计算式长度和长度差,将得到的新起始位置赋值给a
  139.              m2 = m2 - n2                      '更新括号层数,左括号总数-已算的右括号数量
  140.              b2 = 0: n2 = 0: 长度差2 = 0       '计数器归零,以便下次使用
  141.           End If
  142.        Loop Until a2 >= Len(计算式)            '当字符数超过总字符数时,退出Do循环
  143.     End If
  144.       
  145.     If VBA.Len(计算式) <= 255 Then GoTo 计算   '如果整理后的计算式长度小于等于255,则直接计算
  146.    
  147.    '======================================简化计算式中的乘除运算======================================
  148.    '如果有乘除,再从左往右依次算乘除
  149.     If VBA.InStr(计算式, "*") + VBA.InStr(计算式, "/") > 0 Then
  150.        Dim TmpValue3 As Double, TmpStr3$, Str3$
  151.        Dim a3%, b3%, m3%
  152.        Dim Arr(1 To 1000)
  153.        Arr(1) = 1
  154.        For a3 = 1 To Len(计算式)
  155.            If InStr("+-", Mid(计算式, a3, 1)) > 0 Then
  156.               m3 = m3 + 1
  157.               Arr(m3 + 1) = a3
  158.            End If
  159.        Next
  160.        Arr(m3 + 2) = Len(计算式)

  161.        For b3 = 1 To m3 + 1
  162.            If b3 = 1 Then
  163.               TmpStr3 = Mid(计算式, Arr(b3), Arr(b3 + 1) - 1)
  164.            ElseIf b3 <= m3 Then
  165.               TmpStr3 = Mid(计算式, Arr(b3) + 1, Arr(b3 + 1) - Arr(b3) - 1)
  166.            ElseIf b3 = m3 + 1 Then
  167.               TmpStr3 = Mid(计算式, Arr(b3) + 1, Arr(b3 + 1) - Arr(b3))
  168.               TmpValue3 = Application.Evaluate(TmpStr3)
  169.               Str3 = Str3 & TmpValue3
  170.               Exit For
  171.            End If
  172.            TmpValue3 = Application.Evaluate(TmpStr3)
  173.            Str3 = Str3 & TmpValue3 & Mid(计算式, Arr(b3 + 1), 1)
  174.        Next
  175.        计算式 = Str3  '更新计算式
  176.     End If
  177.    
  178.     If VBA.Len(计算式) <= 255 Then GoTo 计算  '如果整理后的计算式长度小于等于255,则直接计算
  179.    
  180.    '======================================简化计算式中的括号运算======================================
  181.    '若最后得到的计算式仍大于255字符,再进行简化,直到长度小于等于254字符
  182.     Dim TmpStr4$, TmpValue4 As Double
  183.     Dim m4%
  184.       
  185.     Do
  186.         Do
  187.            m4 = m4 + 1
  188.         Loop Until InStr("+-", Mid(计算式, 255 - m4 + 1, 1)) > 0
  189.         
  190.         TmpStr4 = VBA.Mid(计算式, 1, 255 - m4)
  191.         TmpValue4 = Application.Evaluate(TmpStr4)
  192.         计算式 = Application.Substitute(计算式, TmpStr4, TmpValue4)
  193.         m4 = 0     '计数器归零
  194.     Loop Until Len(计算式) <= 255   '直到计算式长度≤255,才退出循环
  195.     GoTo 计算
  196.    
  197.    '=======================================计算结果=======================================
  198. 计算:
  199.     If VBA.IsError(Application.Evaluate(计算式)) Then   '若计算式有误,结果为错
  200.        MyValue = "错"
  201.     Else
  202.        MyValue = Application.Round(Application.Evaluate(计算式), Point)
  203.     End If
  204. End Function
复制代码

TA的精华主题

TA的得分主题

发表于 2021-10-8 15:56 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2021-10-8 19:32 | 显示全部楼层
用逆波兰表达式或许更简单一点

TA的精华主题

TA的得分主题

 楼主| 发表于 2021-10-10 02:25 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
lipton 发表于 2021-10-8 19:32
用逆波兰表达式或许更简单一点

逆波兰表达式视觉上不太直观,但是逆波兰表达式计算结果确实更简单,但是,将普通计算式转换为逆波兰表达式,也不简单。

TA的精华主题

TA的得分主题

发表于 2022-1-17 18:46 来自手机 | 显示全部楼层
我发现一个简便的方法,将纯计算式字符加个等号写在工程量表格就对了。
=2+3-5/2+3*2*(1+1+2)

TA的精华主题

TA的得分主题

发表于 2022-12-21 16:49 | 显示全部楼层
你好!
这代码对超长计算式或其它,能不能用上
谢谢!

计算式代码.rar

1.34 KB, 下载次数: 7

TA的精华主题

TA的得分主题

发表于 2022-12-22 08:34 | 显示全部楼层
你好!
7楼,这代码对超长计算式或其它,能不能用上
谢谢!

TA的精华主题

TA的得分主题

发表于 2023-11-30 14:58 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2023-11-30 15:01 | 显示全部楼层
楼主请教个问

结果还是无法显示

结果还是无法显示
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-4-28 13:05 , Processed in 0.053071 second(s), 15 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表