|
楼主 |
发表于 2021-9-21 17:06
|
显示全部楼层
四、函数代码如下:
- Function MyValue(Rng As Range, Optional Point As Integer = 2)
- '===================================清除计算式的注释===================================
- Dim Tmp As String, 计算式 As String
- '====================================修正运算符号====================================
- Tmp = Rng.Value '将单元格内容赋值给变量
- Tmp = Replace(Tmp, " ", "") '清除计算式中的空格
- Tmp = Replace(Replace(Tmp, "(", "("), ")", ")") '中文括号转换为英文括号
- Tmp = Replace(Replace(Tmp, "[", "("), "]", ")") '中括号转换为小括号
- Tmp = Replace(Replace(Tmp, "{", "("), "}", ")") '大括号转换为小括号
- Tmp = Replace(Replace(Tmp, "×", "*"), "÷", "/") '乘号、除号转换为计算符号
-
- '===================================清除计算式的注释===================================
- '注释内容一定要用【】标记,否侧会影响后面的判断
- If VBA.InStr(Tmp, "【") > 0 Then
- Dim i As Integer, j As Integer
- For i = 1 To VBA.Len(Tmp)
- If VBA.Mid(Tmp, i, 1) = "【" Then
- Do
- j = j + 1
- Loop Until VBA.Mid(Tmp, i + j, 1) = "】" Or i + j >= VBA.Len(Tmp)
- i = i + j
-
- If i > VBA.Len(Tmp) Then
- Exit For
- Else
- j = 0
- End If
- ElseIf VBA.Mid(Tmp, i, 1) = "】" Then
-
- Else
- 计算式 = 计算式 & Mid(Tmp, i, 1)
- End If
- Next
- Else
- 计算式 = Tmp
- End If
-
- '清除注释完成,对计算式做初步判断
- If Len(计算式) = 0 Then '简化后计算式为空,则无计算式,结果为空
- MyValue = ""
- Exit Function
- ElseIf VBA.InStr("+-*/", Right(计算式, 1)) > 0 Then '如果计算式最后一位是运算符号,则计算式错误,结果为错
- MyValue = "错"
- Exit Function
- End If
-
- '左括号和右括号数量不相等时,计算式有误,结果为错。这里只能先判断括号是否成对
- If VBA.Len(Replace(Tmp, "(", "")) <> VBA.Len(Replace(Tmp, ")", "")) Then
- MyValue = "错"
- Exit Function
- End If
-
- '计算式中出现连续的运算符,计算式有误,结果为错
- Dim Err, e As Byte
- Err = Array("++", "+-", "+*", "+/", "-+", "-*", "-/", "--", "*+", "*-", "**", "*/", "/+", "/-", "/*", "//", ")(")
- For e = 0 To UBound(Err)
- If VBA.InStr(计算式, Err(e)) > 0 Then
- MyValue = "错"
- Exit Function
- End If
- Next
-
- If VBA.Len(计算式) <= 255 Then GoTo 计算 '如果计算式字符数≤255,则直接结算;否则,运行下面的代码
-
- '====================================超长计算式的简化、计算思路====================================
- '/计算式长度>255字符,先将计算式中的函数公式通过计算,转化为数值,得到一个纯数字的计算式
- '// 如果计算式中存在函数,函数的括号会影响括号的计算,所以需要先把函数部分先转换成结果
- '/然后将所得计算式按照四则运算法则:括号→乘除→加减顺序,依次计算、简化,得到一个纯加减的计算式
- '// 如果计算式长度>255字符,则以【+、-】为依据,从左开始截取一段小于255字符的计算式,将其转化为数值
- '// 然后不断重复上一步操作,直到最终的计算式长度≤255字符,然后直接计算出结果
-
- '========================================计算式中的函数运算========================================
- '判断计算式中是否存在函数,若存在,先依次计算函数(以免函数中的括号影响下一步括号计算)
- Dim TmpStr1$, TmpValue1 As Double
- Dim a1%, b1%, c1%, m1%, n1%, 长度差1% '循环变量和计数器
-
- Do
- a1 = a1 + 1 '逐个字符串判断
- SS1 = VBA.Mid(计算式, a1, 1)
- If VBA.InStr("0123456789~!@#$%^&( )+-*/^<=>.,""", VBA.Mid(计算式, a1, 1)) = 0 Then '当前字符串非数值、运算符、括号、特殊符号
- Do
- b1 = b1 + 1 '遇到非数值、运算符、括号、特殊符号后,开始计数
- Loop Until VBA.InStr("+-*/^()", VBA.Mid(计算式, a1 + b1, 1)) > 0 '逐一判断,直到出现下一个运算符
-
- If VBA.InStr("((", VBA.Mid(计算式, a1 + b1, 1)) > 0 Then '如果下一个运算符是括号,则表示此处是函数【函数名称(函数参数)】
- Do '根据括号判断函数的嵌套层数
- c1 = c1 + 1
- SS2 = Mid(计算式, a1 + b1 + c1 - 1, 1)
- If VBA.InStr("((", Mid(计算式, a1 + b1 + c1 - 1, 1)) > 0 Then
- m1 = m1 + 1 '统计左括号数量
- ElseIf VBA.InStr("))", Mid(计算式, a1 + b1 + c1 - 1, 1)) > 0 Then
- n1 = n1 + 1 '统计右括号数量
- End If
- Loop Until m1 > 0 And n1 > 0 And m1 = n1 Or a1 + b1 + c1 - 1 >= Len(计算式) '当左括号=右括号,或者字符数超过总字符数时,退出Do循环
-
- If m1 = n1 Then '当左右括号相等时,把函数公式部分提取出来,计算结果
- TmpStr1 = Mid(计算式, a1, b1 + c1)
- TmpValue1 = Excel.Application.Evaluate(TmpStr1)
- 计算式 = Application.Substitute(计算式, TmpStr1, TmpValue1, 1) '把函数结果写入计算式
- 长度差1 = VBA.Len(TmpValue1) - VBA.Len(TmpStr1) - 1 '计算计算式与计算结果的字符数量差
- a1 = a1 + b1 + c1 + 长度差1 '根据a的位置,结合计算式长度和长度差,将得到的新起始位置赋值给a
- b1 = 0: c1 = 0: m1 = 0: n1 = 0 '计数器归零,以便下次使用
- End If
- End If
- End If
- Loop Until a1 >= VBA.Len(计算式) '当a的值等于或超过计算式长度时,退出循环
-
- If VBA.Len(计算式) <= 255 Then GoTo 计算 '如果整理后的计算式长度小于等于255,则直接计算
-
- '======================================简化计算式中的括号运算======================================
- '如果有括号,先计算括号内的内容
- If VBA.InStr(计算式, "(") > 0 Then
- Dim TmpValue2 As Double, TmpStr2$
- Dim KaiShi%, JieShu%, Dic
- Dim a2%, b2%, m2%, n2%, 长度差2%
- Set Dic = CreateObject("Scripting.dictionary")
-
- Do
- a2 = a2 + 1
- If VBA.InStr("((", Mid(计算式, a2, 1)) > 0 Then '当前字符是左括号
- m2 = m2 + 1 '统计左括号数量,即括号层数
- Dic(m2) = a2 '用字典记录每个左括号的位置
- ElseIf VBA.InStr("))", Mid(计算式, a2 + 1, 1)) > 0 Then '下一个字符是右括号
- Do
- b2 = b2 + 1
- If VBA.InStr("))", Mid(计算式, a2 + b2 + 长度差2 - 1, 1)) > 0 Then
- n2 = n2 + 1 '依次判断右口号位置,逐层计算
- KaiShi = Dic(m2 - n2 + 1)
- JieShu = a2 + b2 + 长度差2 - 1
-
- TmpStr2 = Mid(计算式, KaiShi, JieShu - KaiShi + 1)
- TmpValue2 = Application.Evaluate(TmpStr2)
- 计算式 = Application.Substitute(计算式, TmpStr2, TmpValue2) '把括号结果写入计算式
- 长度差2 = 长度差2 + VBA.Len(TmpValue2) - VBA.Len(TmpStr2) - 1 '计算计算式与计算结果的字符数量差
- End If
- Loop Until a2 + b2 > VBA.Len(计算式) Or VBA.InStr("((", Mid(计算式, a2 + b2 + 长度差2, 1)) > 0 '下一位是左括号时
-
- a2 = a2 + b2 + 长度差2 - 1 '根据a的位置,结合计算式长度和长度差,将得到的新起始位置赋值给a
- m2 = m2 - n2 '更新括号层数,左括号总数-已算的右括号数量
- b2 = 0: n2 = 0: 长度差2 = 0 '计数器归零,以便下次使用
- End If
- Loop Until a2 >= Len(计算式) '当字符数超过总字符数时,退出Do循环
- End If
-
- If VBA.Len(计算式) <= 255 Then GoTo 计算 '如果整理后的计算式长度小于等于255,则直接计算
-
- '======================================简化计算式中的乘除运算======================================
- '如果有乘除,再从左往右依次算乘除
- If VBA.InStr(计算式, "*") + VBA.InStr(计算式, "/") > 0 Then
- Dim TmpValue3 As Double, TmpStr3$, Str3$
- Dim a3%, b3%, m3%
- Dim Arr(1 To 1000)
- Arr(1) = 1
- For a3 = 1 To Len(计算式)
- If InStr("+-", Mid(计算式, a3, 1)) > 0 Then
- m3 = m3 + 1
- Arr(m3 + 1) = a3
- End If
- Next
- Arr(m3 + 2) = Len(计算式)
- For b3 = 1 To m3 + 1
- If b3 = 1 Then
- TmpStr3 = Mid(计算式, Arr(b3), Arr(b3 + 1) - 1)
- ElseIf b3 <= m3 Then
- TmpStr3 = Mid(计算式, Arr(b3) + 1, Arr(b3 + 1) - Arr(b3) - 1)
- ElseIf b3 = m3 + 1 Then
- TmpStr3 = Mid(计算式, Arr(b3) + 1, Arr(b3 + 1) - Arr(b3))
- TmpValue3 = Application.Evaluate(TmpStr3)
- Str3 = Str3 & TmpValue3
- Exit For
- End If
- TmpValue3 = Application.Evaluate(TmpStr3)
- Str3 = Str3 & TmpValue3 & Mid(计算式, Arr(b3 + 1), 1)
- Next
- 计算式 = Str3 '更新计算式
- End If
-
- If VBA.Len(计算式) <= 255 Then GoTo 计算 '如果整理后的计算式长度小于等于255,则直接计算
-
- '======================================简化计算式中的括号运算======================================
- '若最后得到的计算式仍大于255字符,再进行简化,直到长度小于等于254字符
- Dim TmpStr4$, TmpValue4 As Double
- Dim m4%
-
- Do
- Do
- m4 = m4 + 1
- Loop Until InStr("+-", Mid(计算式, 255 - m4 + 1, 1)) > 0
-
- TmpStr4 = VBA.Mid(计算式, 1, 255 - m4)
- TmpValue4 = Application.Evaluate(TmpStr4)
- 计算式 = Application.Substitute(计算式, TmpStr4, TmpValue4)
- m4 = 0 '计数器归零
- Loop Until Len(计算式) <= 255 '直到计算式长度≤255,才退出循环
- GoTo 计算
-
- '=======================================计算结果=======================================
- 计算:
- If VBA.IsError(Application.Evaluate(计算式)) Then '若计算式有误,结果为错
- MyValue = "错"
- Else
- MyValue = Application.Round(Application.Evaluate(计算式), Point)
- End If
- End Function
复制代码 |
|