Excel VBA failure of repeated Evaluate method - excel

I have written a little tool in VBA that charts a function you pass it as a string (e.g. "1/(1+x)" or "exp(-x^2)"). I use the built-in Evaluate method to parse the formula. The nub of it is this function, which evaluates a function of some variable at a given value:
Function eval(func As String, variable As String, value As Double) As Double
eval = Evaluate(Replace(func, variable, value))
End Function
This works fine, e.g. eval("x^2, "x", 2) = 4. I apply it element-wise down an array of x values to generate the graph of the function.
Now I want to enable my tool to chart the definite integral of a function. I have created an integrate function which takes an input formula string and uses Evaluate to evaluate it at various points and approximate the integral. My actual integrate function uses the trapezoidal rule, but for simplicity's sake let's suppose it is this:
Function integrate(func As String, variable As String, value As Double) As Double
integrate = value * (eval(func, variable, 0) + eval(func, variable, value)) / 2
End Function
This also works as expected, e.g. integrate("t", "t", 2) = 2 for the area of the triangle under the identity function.
The problem arises when I try to run integrate through the charting routine. When VBA encounters a line like this
eval("integrate(""t"",""t"",x)", "x", 2)
then it will stop with no error warning when Evaluate is called inside the eval function. (The internal quotes have to be doubled up to read the formula properly.) I expect to get the value 2 since Evaluate appears to try and evaluate integrate("t", "t", 2)
I suspect the problem is with second call on Evaluate inside integrate, but I've been going round in circles trying to figure it out. I know Evaluate is finicky and poorly documented http://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast but can anyone think of a way round this?
Thanks
George
Excel 2010 V14, VBA 7.0

Thanks Chris, your Debug.Print suggestion got me thinking and I narrowed the problem down a bit more. It does seem like Evaluate gets called twice, as this example shows:
Function g() As Variant
Debug.Print "g"
g = 1
End Function
Run from the Immediate Window:
?Evaluate("g()")
g
g
1
I found this http://www.decisionmodels.com/calcsecretsh.htm which shows a way round this by using Worksheet.Evaluate (Evaluate is actually the default for Application.Evaluate):
?ActiveSheet.Evaluate("g()+0")
g
1
However this still doesn't solve the problem with Evaluate calling itself. Define
Function f() As Variant
Debug.Print "f"
f = ActiveSheet.Evaluate("g()+0")
End Function
Then in the Immediate Window:
?ActiveSheet.Evaluate("f()+0")
f
Error 2015
The solution I found was define a different function for the second formula evaluation:
Function eval2(formula As String) As Variant
[A1] = "=" & formula
eval2 = [A1]
End Function
This still uses Excel's internal evaluation mechanism, but via a worksheet cell calculation. Then I get what I want:
?eval2("f()")
f
g
1
It's slower due to the repeated worksheet hits, but that's the best I can do. So in my original example, I use eval to calculate the integral and eval2 to chart it. Still interested if anyone has any other suggestions.

Related

Why declare function's type?

Sorry if this has been asked, but seriously can't find anything, so would also appreciate on how to search for this stuff.
So my question: what is the point of declaring the function's type in general? E.g. here 'as double'
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
For a normal variable it has tons of benefits, like less memory, easier to see typos, but why here?
Edit: so, it's good because we can avoid errors, like it giving back a different type of values than expected.
Functions RETURN something. That type is the type of the return.
In your function:
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
You are returning a decimal, so type Double make sense.
If you don't return anything, then you can declare it as a Sub.
And, for clarification, your function would throw a compile error. Unlike other languages, in VBA to return, we set the function name's value to the thing we want to return:
Function myFunction(ByVal j As Integer) As Double
myFunction=3.87 * j
End Function
Now we can call this function to get the Double value that it creates:
Sub testSub()
msgbox("This is the result of the function: " & myFunction(10))
End Sub
Which would launch a message box saying "This is the result of the function: 38.7"
Since I can't mark a comment the answer, let me quote:
#John Coleman
My opinion is that it a good thing to declare your return types because it increases the likelihood that the compiler will complain when you are doing something that really doesn't make sense.
Excel VBA is different from other programming languages in that it centers around a particular application: Excel.
Functions are useful in Excel VBA primarily because they can be typed directly into a cell on a sheet by an end user. User defined functions provide near infinite flexibility. The value the user defined function prints to Excel is formatted based on the function's type--and in a program which is about data visualization, formatting is a huge part.
For example, try putting these four functions into a blank worksheet module:
Function myInt(x, y) As Integer
myInt = x / y
End Function
Function myDouble(x, y) As Double
myDouble = x / y
End Function
Function myString(x, y) As String
myString = x / y
End Function
Function myVariant(x, y)
myVariant = x / y
End Function
Next, enter each of these functions into a different cell in the workbook. Use x=1 and y=2.
myInt produces "=0"
myDouble produces "=0.5"
myString produces "'0"
myVariant produces "=0.5"
If you're okay with Excel deciding how to format your result, that's your choice, but specifying the type offers an entire new level of control. For example, by simply declaring a function an integer, you can avoid having to devote a line of code to rounding. By declaring a function to be a string, you can avoid several lines of formatting code trying to get a number to be saved as text instead.

How to evaluate an arbitrary mathematical function the user inputs in a visual basic 6 exe?

I have made a program that plots a slope field for a given function.
It works correct if I plug in the function in the source code.
But I wan to turn this vb6 project into an exe file.
Because I knew this will happen before, I previously made a field for inputting the function. The function will be inputted in a special form. inspired by the language used in some add on in AutoCAD, I made this language and named it DiffSol.
So what the user is gonna do is to write a function in the field using DiffSol language.
The problem is that It needs to be a real mathematical function in vb to be evaluated for different x and y's. but I cannot find a strategy to turn that language into a vb math function which can be evaluated.
All I am gonna do is to evaluate the inputted function for 15*31 times.
The job looks like making a compiler. It seems a really hard job for me.
Any ideas?
Simple way is to transpile it in to VBScript or JScript.
Create a class containing your functions written in VB:
' +(a,b)
public function ADD(a as double, b as double) as double
add = a + b
end function
'/(a,b)
public function DIV(a as double, b as double) as double
div = a / b
end function
Add a reference to the Microsoft Script Control then:
Dim scr As ScriptControl: Set scr = New ScriptControl
scr.Language = "VBScript"
'// allow the script access to the class with the functions
scr.AddObject "DS", new diffsolClass
expr = " +(200, c(/(+(2,6), 2))) "
'//parse with simple substitution
parsed = expr
parsed = Replace$(parsed, "/", "DS.DIV")
parsed = Replace$(parsed, "+", "DS.ADD")
parsed = Replace$(parsed, "c", "cos") '//built in already
'//for some valid VB: DS.ADD(200, cos(DS.DIV(DS.ADD(2,6), 2)))
'//run it
MsgBox scr.Eval(parsed)
returns 199.346356379136

Evaluating Greater Than x But Less Than x VBA

I am building an application which must interpret a set of instructions set by the user, instructions which will not be known until runtime.
I am achieving this by using the Evaluate function, as so:
Evaluate("5>4")
Which returns True, as one would expect. My question is how do I evaluate the following statement:
5 is greater than 4, but less than 10
I will be substituting the numbers with variables, of course.
I realise I could split the string into an array of two parts and test individually, but there must be a way of passing a single argument to test.
Application.Evaluate evaluates Formulas so AND(5>A4,5<10) or (5>A4)*(5<10) (results in 0 or 1)
Another alternative could be ScriptControl Eval, but it can't access Excel addresses like Evaluate
With CreateObject("ScriptControl")
.Language = "VBScript" ' either VBScript or JScript
Debug.Print .Eval("5>4 and 5<10")
End With

Calculate (If A=B, then "" else A) in Excel without evaluating A twice

I'm intending to conduct a formula of the type:
=IF(VOL("Site";"Date")=0;"";VOL("Site";"Date"))
where VOL is a function I'm using through an Add-In. The limitations of this Add-In is, among others, that it is prohibited to call two Add-In function inside a single formula. I.e. the code I've written above is invalid and will result in an error.
Is there a way of achieving the following:
=IF(LHS=RHS;"Value if True";LHS) (2)
where LHS is Left hand side, RHS right hand side and the expression therefore checks if LHS is equal to RHS, and if so prints a corresponding value, else LHS, without having Excel evaluate LHS twice?
I haven't found any solution to this except importing the formula in one cell, and refer to that cell as the value to print if the logical expression in the IF statement is false, but this will become a quite extensive "double work". A solution like (2) would also become more readable, especially when LHS is of the type "'C:\pathtofile[filename]SheetName!'Cell".
Hope anyone has some clever solution to this
Here is one (rather ugly) way, just using formulas:
=IFERROR(1/IFERROR(1/vol("Site";"Date"),0),"")
This makes use of the IFERROR function, which kind of does what you want but only tests for errors. Division by zero results in an error, so the inner IFERROR returns zero if VOL is zero, and 1/VOL otherwise. Now we need to take the reciprocal again to return the original value, so we repeat the trick, this time returning "" if there is an error.
If you want to test for another value (e.g. 3), just use something like:
=IFERROR(3+1/IFERROR(1/(vol("Site";"Date")-3),0),"")
A much neater way would be to create a function in VBA which wraps the VOL function and does what you want:
Public Function MyVol(varSite As Variant, varDate As Variant) As Variant
MyVol = vol(varSite, varDate)
If MyVol = 0 Then MyVol = ""
End Function
Assuming you can call VOL from VBA.

Adding two numbers with a function

I am writing VBA code to add two numbers:
Option Explicit
Dim x As Integer, y As Integer
Function addtwo(x, y)
addtwo = x + y
End Function
This is not a good function since I have to manually input the values x and y to get for the result. How can I modify the codes so it can work with any two numbers, no matter integers or numbers with decimals?
You define your input variables in the FUNCTION declaration line, not above it. I would recommend the vartype DOUBLE for whole numbers and decimals.
Function ADDTWO(ByVal x As Double,ByVal y As Double) As Double
ADDTWO = x + y
End Function
Now this function can be used in a cell as:
=ADDTWO(3, 6)
or
=ADDTWO(A2, B7)
It can also be used in VBA.
Of course, the SUM() function does this same thing, so....I presume this is a learning exercise.
As far as working with any two numbers, try the Variant data type instead of Integer. Not sure what you mean by manually inputting the values - are you talking about getting them from the worksheet?

Resources