home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 1_2002.ISO / Data / Zips / Mathematic581462272002.psc / Exp.cls next >
Encoding:
Visual Basic class definition  |  2002-02-27  |  15.8 KB  |  397 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "Exp"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. ' Jonathan A. Feucht
  15. ' Mathematics simulator
  16. '-------------------------
  17. ' This private class describes a part of an expression, determined by the expression's
  18. ' parenthesis. For instance, 3*(5+4^2) has two parts, part1=3*(part2) and part2=5+4^2.
  19. ' The math tool recursively splits an expression into Exp classes containing data
  20. ' found in the parenthesis. Afterwards, each part is solved, and replaced into the
  21. ' prior expression. All this is found in Sub SolveExpression.
  22.  
  23. ' Part2=5+4^2 becomes part2=21, then that is substituted into part1.
  24. ' Part1 becomes 3*9, which is 27. The process of carrying out the mathematical
  25. ' operations is described in Function Solve.
  26.  
  27. Option Explicit
  28. Option Compare Text
  29.  
  30. Const OPS As String = "^$*/%\+-"  ' Operator characters organized by priority in sets of two
  31.  
  32. Private Type SepData
  33.     ParseInt As Variant
  34.     Operator As String
  35.     NextOp As Integer
  36. End Type
  37.  
  38. Private Enum OpModes
  39.     OpExp
  40.     OpMod
  41.     OpMult
  42.     OpAdd
  43. End Enum
  44.  
  45. Public Expression As String
  46. Dim Expressions() As New Exp
  47. Dim eqVal As Double
  48.  
  49. 'This function returns the number of elements in the array Expressions
  50. Private Function GetExpressionCnt() As Integer
  51.  
  52.     On Error Resume Next
  53.       GetExpressionCnt = UBound(Expressions)
  54.  
  55. End Function
  56.  
  57. 'This property is used to return the value for the solved equation.
  58. Function GetValue() As Double ':( Missing Scope
  59.  
  60.     GetValue = eqVal
  61.  
  62. End Function
  63.  
  64. ' GoFunct executes functions, parenthesized expressions, and variables
  65. Private Function GoFunct(ExpresVal As String) As Double
  66.  
  67.   Dim i As Integer
  68.   Dim OpenPar As Integer, ClosePar As Integer
  69.   Dim FunctName As String, RefVal As Double
  70.   Dim PI As Double
  71.   Dim Dec As Double
  72.  
  73.     On Error Resume Next
  74.  
  75.       PI = Atn(1) * 4
  76.  
  77.       ' Find next closing and ending parentheses
  78.       OpenPar = InStr(1, ExpresVal, "[")
  79.       ClosePar = InStr(1, ExpresVal, "]")
  80.     
  81.       ' If there are no parentheses, it must be a variable
  82.       If OpenPar = 0 And ClosePar = 0 Then
  83.           ' If it is blank, then be totally confused
  84.           If ExpresVal = "" Or ExpresVal = "+" Or ExpresVal = "-" Then Exit Function ':( Expand Structure or consider reversing Condition
  85.           ' Otherwise, look up the variable
  86.           For i = 1 To VarCnt
  87.               ' If we find the variable name, return the variable value
  88.               If ExpresVal = Vars(i).Name Then
  89.                   GoFunct = Vars(i).Value
  90.                   Exit Function '>---> Bottom
  91.               End If
  92.           Next i
  93.           ' If the variable doesn't exist, be totally confused
  94.           Err.Raise FuncInvalid, , "Not recognized: """ & UCase$(ExpresVal) & """"
  95.         Else 'NOT OPENPAR...
  96.           ' Get the data preceeding the opening parenthesis
  97.           FunctName = Trim$(Left$(ExpresVal, OpenPar - 1))
  98.           ' Get the part referenced by the function
  99.           RefVal = Expressions(Val(Mid$(ExpresVal, OpenPar + 1, ClosePar - OpenPar - 1))).GetValue
  100.         
  101.           ' Execute the function
  102.           Select Case FunctName
  103.             Case "SIN"  ' Sine
  104.               GoFunct = Sin(RefVal)
  105.             Case "CSC"  ' Cosecant
  106.               GoFunct = 1 / Sin(RefVal)
  107.             Case "COS"  ' Cosine
  108.               GoFunct = Cos(RefVal)
  109.             Case "ARCSIN"   ' Arc sine
  110.               ' The arc sine of 1 or -1 normally returns an error in Visual Basic
  111.               If Abs(RefVal) = 1 Then
  112.                   GoFunct = Atn(1) * 4 / 2 * Sgn(RefVal)
  113.                 Else 'NOT ABS(REFVAL)...
  114.                   GoFunct = Atn(RefVal / Sqr(-RefVal * RefVal + 1))
  115.               End If
  116.             Case "ARCCOS"   ' Arc cosine
  117.               ' The arc cosine of 1 or -1 normally returns an error in Visual Basic
  118.               If RefVal = 1 Then
  119.                   GoFunct = 0
  120.                 ElseIf RefVal = -1 Then 'NOT REFVAL...
  121.                   GoFunct = PI
  122.                 Else 'NOT REFVAL...
  123.                   GoFunct = Atn(-RefVal / Sqr(-RefVal * RefVal + 1)) + 2 * Atn(1)
  124.               End If
  125.             Case "ARCTAN"   ' Arc tangent
  126.               GoFunct = Atn(RefVal)
  127.             Case "SEC"  ' Secant
  128.               GoFunct = 1 / Cos(RefVal)
  129.             Case "TAN"  ' Tangent
  130.               GoFunct = Tan(RefVal)
  131.             Case "COT"  ' Cotangent
  132.               GoFunct = 1 / Tan(RefVal)
  133.             Case "ABS"  ' Absolute value
  134.               GoFunct = Abs(RefVal)
  135.             Case "EXP"  ' e raised to a power
  136.               GoFunct = Exp(RefVal)
  137.             Case "INT"  ' Integer value
  138.               GoFunct = Int(RefVal)
  139.             Case "FIX"  ' RefVal without the decimal part
  140.               GoFunct = Fix(RefVal)
  141.             Case "DECPART"  ' Decimal part
  142.               GoFunct = RefVal - Fix(RefVal)
  143.             Case "ROUND"    ' Round to nearest integer
  144.               Dec = Abs(RefVal - Fix(RefVal))
  145.               GoFunct = Fix(RefVal)
  146.               If Dec >= 0.5 Then GoFunct = GoFunct + (1 * Sgn(RefVal)) ':( Expand Structure
  147.             Case "LN"   ' Natural log
  148.               GoFunct = Log(RefVal)
  149.             Case "LOG"  ' Log base 10
  150.               GoFunct = Log(RefVal) / Log(10)
  151.             Case "RND"  ' Random
  152.               Randomize Timer
  153.               GoFunct = Rnd * RefVal
  154.             Case "SGN"  ' Positive/Negative sign
  155.               GoFunct = Sgn(RefVal)
  156.             Case "DTR"  ' Decimal to radians
  157.               GoFunct = RefVal * (Atn(1) * 4) / 180
  158.             Case "RTD"  ' Radians to decimal
  159.               GoFunct = RefVal * 180 / (Atn(1) * 4)
  160.             Case "" ' No function name, must be just an expression
  161.               GoFunct = RefVal
  162.             Case Else   ' Function name not found, so be totally confused
  163.               Err.Raise FuncInvalid, , "No function """ & UCase$(FunctName) & """"
  164.           End Select
  165.       End If
  166.       
  167.       If Err.Number > 4 Then
  168.           Err.Raise Infinity, , "Invalid argument"
  169.       End If
  170.  
  171. End Function
  172.  
  173. ' This function returns the solved part
  174. Private Function Solve() As Double
  175.  
  176.   Dim OpMode As OpModes, OpVal As Integer
  177.   Dim NewOp As Integer, OldOp As Integer
  178.   Dim ParseOps() As SepData
  179.  
  180.     On Error Resume Next
  181.     
  182.       ' Split the expression into parts separated by operators
  183.       Split ParseOps(), Expression
  184.       ' If we hit an error in the process, be totally confused
  185.       If Err.Number > 0 Then Exit Function
  186.       ' Loop through operator priorities
  187.       For OpMode = OpExp To OpAdd
  188.           ' Reset the operator pointers
  189.           NewOp = 1
  190.           Do
  191.               ' Point to next operator, save old pointer
  192.               OldOp = NewOp
  193.               NewOp = ParseOps(OldOp).NextOp
  194.             
  195.               ' If there are no more operators, move on to next operator priority
  196.               If NewOp = 0 Then Exit Do
  197.             
  198.               ' Get current operator priority
  199.               OpVal = InStr(1, OPS, ParseOps(OldOp).Operator)
  200.             
  201.               ' If the current operator has the priority we're looking for, then
  202.               ' solve
  203.               If OpMode = (OpVal - 1) \ 2 Then
  204.             
  205.                   Select Case ParseOps(OldOp).Operator
  206.                     Case "^" ' Exponant Operator
  207.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt ^ ParseOps(NewOp).ParseInt
  208.                       ' Root of a negative number deserves utter confusion
  209.                       If Err.Number = 5 Then Err.Raise Infinity, , "Imaginary number" ':( Expand Structure
  210.                     Case "$" ' Square root operator
  211.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt ^ (1 / ParseOps(NewOp).ParseInt)
  212.                       If Err.Number = 5 Then Err.Raise Infinity, , "Imaginary number" ':( Expand Structure
  213.                     Case "*" ' Multiplication Operator
  214.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt * ParseOps(NewOp).ParseInt
  215.                     Case "/" ' Division Operator
  216.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt / ParseOps(NewOp).ParseInt
  217.                     Case "%" ' Mod Operator
  218.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt Mod ParseOps(NewOp).ParseInt
  219.                     Case "\" ' Div Operator
  220.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt \ ParseOps(NewOp).ParseInt
  221.                     Case "+" ' Addition Operator
  222.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt + ParseOps(NewOp).ParseInt
  223.                     Case "-" ' Subtraction Operator
  224.                       ParseOps(OldOp).ParseInt = ParseOps(OldOp).ParseInt - ParseOps(NewOp).ParseInt
  225.                   End Select
  226.                 
  227.                   With ParseOps(NewOp)
  228.                       ParseOps(OldOp).NextOp = .NextOp
  229.                       ParseOps(OldOp).Operator = .Operator
  230.                   End With 'PARSEOPS(NEWOP)
  231.                 
  232.                   NewOp = OldOp
  233.               End If
  234.             
  235.           Loop
  236.       Next OpMode
  237.     
  238.       If Err.Number >= 5 Or Err.Number = 1 Then
  239.           Select Case Err.Number
  240.             Case 11
  241.               Err.Raise Infinity, , "Division by 0"
  242.             Case Else
  243.               Err.Raise Infinity, , "Overflow"
  244.           End Select
  245.       End If
  246.     
  247.       Solve = ParseOps(1).ParseInt
  248.  
  249. End Function
  250.  
  251. 'This procedure solves the mathematical expression
  252. Public Sub SolveExpression()
  253.  
  254.   Dim BegPos As Integer, EndPos As Integer
  255.   Dim BegPar As Integer, EndPar As Integer
  256.   Dim CurPos As Integer, ParDepth As Integer
  257.   Dim NewParse As Integer, OldLen As Integer
  258.  
  259.     ' Add a space at the end of the expression if there is not one already
  260.     If Not Right$(Expression, 1) = " " Then Expression = Expression & " "
  261.     
  262.     CurPos = 1
  263.     Do
  264.         ' Find parentheses positions
  265.         BegPos = InStr(CurPos, Expression, "[")
  266.         EndPos = InStr(CurPos, Expression, "]")
  267.         
  268.         ' If the parentheses don't exist, point them to the last character
  269.         ' of the expression
  270.         If BegPos = 0 Then BegPos = Len(Expression)
  271.         If EndPos = 0 Then EndPos = Len(Expression)
  272.         If BegPos < EndPos Then ' If the next parenthesis is a "["...
  273.             ' Point to the data within the parentheses
  274.             CurPos = BegPos + 1
  275.             ' We only read data within the level 1 parenthesis. For instance, in the
  276.             ' expression "6*[9+4*{6-SIN(PI/2)}]+[4-3*{5}]", the brackets are level 1
  277.             ' parenthesis, braces are level 2, and parentheses are level 3.
  278.             If ParDepth = 0 Then BegPar = CurPos - 1
  279.             ' We're getting deeper
  280.             ParDepth = ParDepth + 1
  281.           ElseIf BegPos > EndPos Then ' If the next parenthesis is a "]"...
  282.             ' Point to data outside parenthesis
  283.             CurPos = EndPos + 1
  284.             ' We're getting shallower
  285.             ParDepth = ParDepth - 1
  286.             ' Check if we hit ending parenthesis for a level 1 statement
  287.             If ParDepth = 0 Then
  288.                 ' If so, then point to last character within parentheses
  289.                 EndPar = CurPos - 1
  290.                 ' Create a new expression containing the data within the parentheses
  291.                 ' marked by the BegPar and EndPar pointers
  292.                 NewParse = GetExpressionCnt + 1
  293.                 ReDim Preserve Expressions(1 To NewParse)
  294.                 Expressions(NewParse).Expression = Mid$(Expression, BegPar + 1, EndPar - BegPar - 1)
  295.                 ' Solve the new expression
  296.                 Expressions(NewParse).SolveExpression
  297.                 ' If there was an error in the solving process, be totally confused
  298.                 If Err.Number > 0 Then Exit Sub
  299.                 ' Modify the expression to contain a link to the new expression
  300.                 Expression = Left$(Expression, BegPar - 1) & "[" & NewParse & "]" & Right$(Expression, Len(Expression) - EndPar)
  301.                 ' Point to the new position for the closing parenthesis
  302.                 CurPos = InStr(BegPar, Expression, "]") + 1
  303.             End If
  304.           Else    ' There are no parentheses, so exit
  305.             Exit Do '>---> Loop
  306.         End If
  307.     Loop
  308.     
  309.     If ParDepth <> 0 Then   ' Wrong number of parenthesis, be totally confused
  310.         Err.Number = Syntax
  311.         Err.Description = "Parenthesis expected"
  312.       Else 'NOT PARDEPTH...
  313.         eqVal = Solve() ' Solve the current part
  314.     End If
  315.  
  316. End Sub
  317.  
  318. Private Function Split(ByRef PartArray() As SepData, StrVal As String)
  319.  
  320.   Dim i As Integer, Done As Boolean
  321.   Dim StartPart As Integer, EndPart As Integer, CurPart As String
  322.   Dim SepVal As Integer, ArrPos As Integer, CurOp As String
  323.   Dim CurChar As String, DecHit As Boolean, SignDat As Integer
  324.   Dim OpPos As Integer
  325.     
  326.     On Error Resume Next
  327.     
  328.       SignDat = 1
  329.     
  330.       ' Point to first character in StrVal
  331.       StartPart = 1
  332.       '
  333.       If Left$(StrVal, 1) = "-" Then StrVal = "0" & StrVal ':( Expand Structure
  334.       ' Loop through characters in StrVal
  335.       Do
  336.           ' Add a character to the string selection
  337.           EndPart = EndPart + 1
  338.           ' Get the last character in the selection
  339.           CurChar = Mid$(StrVal, EndPart, 1)
  340.           ' Check if the character is in the list of separating characters
  341.           SepVal = InStr(1, OPS, CurChar)
  342.           ' Check for decimal place
  343.           If CurChar = "." Then
  344.               ' If there is a double decimal place, be totally confused
  345.               If DecHit Then
  346.                   Err.Raise Syntax, , "Double decimal"
  347.                   Exit Function '>---> Bottom
  348.               End If
  349.               DecHit = True
  350.           End If
  351.           Done = EndPart >= Len(StrVal)
  352.           OpPos = InStr(1, OPS, CurChar)
  353.           ' If the current character is an operator, add it to the parts list
  354.           If OpPos > 0 Or Done Then
  355.               CurPart = Mid$(StrVal, StartPart, EndPart - StartPart)
  356.               If Not Done Then CurOp = Mid$(OPS, SepVal, 1)
  357.             
  358.               If Not (IsNumeric(CurPart) Or CurPart = "") Then
  359.                 
  360.                   If Not Right$(Trim$(CurPart), 1) = "]" And InStr(1, CurPart, "[") > 0 Then Err.Raise Syntax, , "Operator expected"
  361.                 
  362.                   CurPart = CStr(GoFunct(CurPart))
  363.                 ElseIf CurPart = "" And Done Then 'NOT NOT...
  364.                   Err.Raise Syntax, , "Expected expression"
  365.                   Exit Function '>---> Bottom
  366.               End If
  367.               If Err.Number > 1 Then Exit Function
  368.               If Not IsNumeric(CurPart) And Not Err.Number = Infinity Then
  369.                   Select Case CurOp
  370.                     Case "-"
  371.                       SignDat = -SignDat
  372.                     Case "+"
  373.                     Case Else
  374.                       Err.Raise Syntax, , "Unexpected operator"
  375.                   End Select
  376.                 Else
  377.                   ArrPos = ArrPos + 1
  378.                   ReDim Preserve PartArray(1 To ArrPos)
  379.                   ' If the current part isn't isnumeric, then run it as a function /
  380.                   ' expression / variable
  381.                   ' Store the data into the parts list
  382.                   With PartArray(ArrPos)
  383.                       .ParseInt = Val(CurPart) * SignDat
  384.                       If Not Done Then
  385.                           .Operator = Mid$(OPS, SepVal, 1)
  386.                           .NextOp = ArrPos + 1
  387.                       End If
  388.                   End With 'PARTARRAY(ARRPOS)
  389.                   SignDat = 1
  390.               End If
  391.               ' Reset the pointer
  392.               StartPart = EndPart + 1
  393.           End If
  394.       Loop Until Done
  395.  
  396. End Function
  397.