1

In VBA, I am used to calculate values of a column, which is the function of another column, with Evaluate() in order not to use a loop. For instance, to assess column 2, whose values are the exponential of column 1's values, I would write (here limited to the first 10 rows):

Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Worksheets("Sheet1").Range(Cells(1,1),Cells(10,1))
Set rng2 = Worksheets("Sheet1").Range(Cells(1,2),Cells(10,2))

rng2 = Evaluate("EXP(" & rng1.Address & ")")

I would now like to proceed on the same way with my own functions. In order to make a test, I created a function that returns the exponential value of the input:

Function TestExpo(alpha) As Double
    TestExpo = EXP(alpha)
End Function

and I proceeded in the same way as previously stated with Evaluate():

rng2 = Evaluate("TestExpo(" & rng1.Address & ")")

I did not get an error, but nothing happened on the Excel sheet. I tried to trigger an error by having a typing error in the function name for instance:

rng2 = Evaluate("TestEx(" & rng1.Address & ")")

and I actually got a "Name" error at each cell of rng2 on the sheet. Consequently, the calling function "TestExpo" works. I also tried to use a real number instead of rng1.Address:

rng2 = Evaluate("TestExpo(" & 2 & ")")

and I got the correct value of exp(2) in all cells of rng2 on the sheet, so calling the function and getting back the value in the cells works as such.

I tried to specify differently the type of the input and output. For instance:

Function TestExpo(alpha) ' No error, but no values on the sheet
Function TestExpo(alpha) As Range ' No error, but no values on the sheet
Function TestExpo(alpha) As Variant ' No error, but no values on the sheet
Function TestExpo(alpha As Range) ' No error, but no values on the sheet
Function TestExpo(alpha As Variant) ' No error, but no values on the sheet
Function TestExpo(alpha As Double) ' Value error for each cell of the range rng2

Except for the last test with alpha As Double, I did not get any error but also no values on the Excel sheet.

I am running out of ideas... I tried to find the code for the EXP() function of VBA to see how the input and output are specified, thinking that if I use the same types and arguments in the function "TestExpo", it may work, but I could not find the code of the EXP() function, it may not be public.

Would anyone have had a similar situation and solved it ?

Kind Regards Xavier


Update:

Thanks for your contributions! I moved on!

When I used, as suggested by @FaneDuru

rng2 = Evaluate("""=TestExpo(" & rng1.Address & ")""")

I got a Value error in each cell. Checking the cells in the worksheet, I could see that every cells got attributed the formula:

"=@TestExpo($A$1:$A$35)"

I compared with writing

rng2 = Evaluate("""=EXP(" & rng1.Address & ")""")

which worked and attributed to each cells the formula

"=EXP(@$A$1:$A$35)"

So the @ appeared at a different location. I manually corrected the formula "=@TestExpo($A$1:$A$35)" into "=TestExpo(@$A$1:$A$35)", and it worked !

So the problem is the location of @, and I do not understand why @ gets by default placed in front of the range within the brackets when the function EXP is used but in front of the function name when my own function is used.

Nevertheless, correcting the formula in the cells manually is not handy. Inspired by the proposal of @FaneDuru, I corrected my code by adding .Cells(1,1).Address(0,0) after the range name in the following way:

rng2 = Evaluate("""=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")""")

and this worked !

Now, the formula in the first cell looks like this:

"=@TestExpo(A1)"

And the cell address is adapted to each line (line 2 the formula is "=@TestExpo(A2)", line 3 "=@TestExpo(A3)", etc)

The @ still appears in front of the function name, but there is one cell name within the brackets instead of the range, and this makes the formula work.

Now I have to admit that I do not really understand how the code syntax works. In fact, adding .Cells(1,1).Address(0,0), I expected that all cells of rng2 would refer to the Cell(1,1) of the range rng1, but instead the formula got adapted to each cell in the proper way.

So, my initial problem has been solved using:

rng2 = Evaluate("""=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")""")

or even more simple:

rng2 = "=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")"

Nevertheless, I would be keen on understanding how the code syntax works - i.e. if the code explicitly refers to cell(1,1) in rng1.Cells(1,1).Address(0,0), how is it possible that all the cells of rng2 do not refer to cell(1,1) but refer to the proper cells (cell(2,1), cell(3,1), etc) ?

And I would also like to understand why @ gets by default placed in front of the range when the function EXP is used but in front of the function name when my own function is used. Would anyone have an idea ?

Xav
  • 51
  • 6
  • 1
    are you sure that `rng2 = Evaluate("EXP(" & rng1.Address & ")")` works as expected? I just tried it and it enters the first value in all the cells. – Gravitate Sep 07 '21 at 12:20
  • 1
    Ok... I will have another look. Not sure if the result might vary depending upon Excel version. In the mean time, you might want to try my answer. It might do what you want after all. – Gravitate Sep 07 '21 at 12:42
  • 1
    That's really odd... I just copied and pasted your exact code and I'd entered numbers 1-10 in A1:A10. The result in B1:B10 was first result (2.71828182845905) in all ten cells. – Gravitate Sep 07 '21 at 12:46
  • Thanks for your feedback! Here is my code and it really works for me. See table below: Column1: input to the function, Col2: Output using the Sub TestFR2 (VBA), Col3: Output using Excel formula =EXP(A1) copied to other cells. Col 2 and 3 are equal. Sub TestdFR2() Worksheets("sheet1").Activate Dim rng1 As Range Dim rng2 As Range Set rng1 = Worksheets("sheet1").Range(Cells(1, 1), Cells(10, 1)) Set rng2 = Worksheets("sheet1").Range(Cells(1, 2), Cells(10, 2)) rng2 = Evaluate("exp(" & rng1.Address & ")") End Sub 0 1 1 0.1 1.11 1.11 0.2 1.22 1.22 0.3 1.35 1.35 0.4 1.49 1.49 – Xav Sep 07 '21 at 12:46
  • @Gravitate The Excel version I use is called 2102. Maybe the different behaviors of the code depend upon Excel version as you have suggested. I will see how I can use your answer in my code, thanks for your input ! – Xav Sep 07 '21 at 12:56
  • 1
    It's unusual (but not unknown) for such a breaking change like that to be made (at least intentionally). But perhaps, they considered it so subtle that it wouldn't cause a problem for the vast majority of users. I am sorry I can't be more help. I hope my suggestion, at least gives you some more options to try. – Gravitate Sep 07 '21 at 13:01

2 Answers2

2

Unfortunately, I don't think you can do what you want in this way. I think that your example isn't doing what you expect.

rng2 = Evaluate("EXP(" & rng1.Address & ")")

Will return an array of values, but will only enter the first value into each of the cells in your rng2. So, even if you could mimic what EXP is doing, it still won't do what you want.

However, if you still want to mimic the (possibly incorrect) functionality you get with the EXP function:

The difference between EXP and your UDF is that EXP function can handle and return arrays, where as your UDF does not.

This is a very quick function that can (and should) be improved significantly, but it might give you a starting point.

Public Function test(a As Variant) As Variant
    Dim arr() As Double
    If IsNumeric(a) Then
        ReDim arr(1 To 1)
        arr(1) = Exp(a)
    Else
        ReDim arr(1 To a.Count)
        Dim i As Long
        For i = 1 To a.Count
            arr(i) = Exp(a(i))
        Next i
    End If
    test = arr
End Function

UPDATE:

Apparently, there also appears to be a difference between the EXP function used in a formula on a worksheet and the VBA version.

The VBA version only take a cell or value, whereas the worksheet version is able to accept a range/array of values. Normally, you could access the worksheet version using Application.WorksheetFunction, but unfortunately this doesn't seem to be available for EXP.

Gravitate
  • 2,885
  • 2
  • 21
  • 37
1

The function to be evaluated must be a string (between double quoted)...

Please, try

rng2 = Evaluate("""=TestEx(" & rng1.Address & ")""")

Edited:

Please, try the next (tested) example:

Function myFuncX(x As Long) As Long
   myFuncX = x + x * 2
End Function

And the testing sub evaluating it. Of course, you must feel "J2:J4" with appropriate long values...

Sub testEvaluateCustFunc()
    Dim rng As Range, rng2 As Range
    Set rng = Range("I2:I4"): Set rng2 = Range("J2:J4")
    rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address(0, 0) & ")""")
End Sub

Edited:

Please, try understanding what will happen in the next testing Sub. The method is able to evaluate formulas, names, ranges, objects and their properties, returning objects, values or arrays:

Sub testEvaluate()
  Dim rng As Range, rng2 As Range, testArr As Variant
  Set rng = Range("I2:I4"): Set rng2 = Range("J2:J4")

  'Application.Evaluate can simple be replaced by using []:
  testArr = [J2:J4]: Debug.Print Join(Application.Transpose(testArr), "||") 'Transpose is necessary to convert in 1D array
                                                                                                 'able to be shown in Immediate Window (IW)...
  testArr = Application.Evaluate("Index(" & rng2.Address(0, 0) & ",)")  'in this way an array of the range values is built
  Debug.Print Join(Application.Transpose(testArr), ",")                       'here the array can be visualized in IW
  testArr = Evaluate("Row(1:5)")                         'A nice way of making an array of numbers using the Row property
  Debug.Print Join(Application.Transpose(testArr), "|")  'See the resulted array in IW
  'the next line evaluates the function for all rng range, incrementing the function argument addresses:
  'I did not see your function and I used this way ONLY TO USE THE FIRST CELL IN THE RANGE TO BE INCREMENTED
  rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address(0, 0) & ")""")
  Debug.Print rng2.Address, rng2.Address(0, 0) ' See here the difference between absolute and relative address
                                                               'Only a relative address/reference can be incremented!
  'trying the absolute reference, will produce a wrong result, the formula using only the first cell of the range (absolute address):
  rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address & ")""")
  testArr = rng.Value
  Debug.Print Join(Application.Transpose(testArr), " ") 'the function is using only the first range cell
  'Now, the same thing using the first cell addres. Because I know what parametes the function uses...
  rng.Value = Application.Evaluate("""=myFuncX(" & Range("J2").Address(0, 0) & ")""")
  testArr = rng.Value
  Debug.Print Join(Application.Transpose(testArr), "|") 'if using the absolute address, the result will allways refer only
                                                                        'the first range cell, not being able to increment an absolute ref...
End Sub

If your function should accept a range of many cells, it could work as I initially suggested. For instance, using the next function:

Function myFunc2(rng As Range) As Long
   myFunc2 = rng.cells.count * 2 + Len(rng.cells(1, 1).Value)
End Function

You could use the next construction in a testing Sub:

Sub testmyFunc2()
    Dim rng3 As Range: Set rng3 = Range("F2:F4")
    Dim rng2 As Range: Set rng2 = Range("J2:J4")
    Debug.Print myFunc2(Range("J2:J4"))
    rng3.Value = Evaluate("""=myFunc2(" & rng2.Address & ")""")
End Sub

I hope it is a little clearer now... If still something foggy, do not hesitate to ask for clarifications. But punctually, on a specific aspect. Evaluate method is very complex regarding the ways to be used...

FaneDuru
  • 38,298
  • 4
  • 19
  • 27
  • Thanks for your answer ! It actually did not solve the problem. Doing as you suggested, all the cells of the rng2 range get attributed the following text "TestExpo($A$1:$A$10)", meaning the function's name and the rng1 range between brackets. – Xav Sep 07 '21 at 12:24
  • @Xav Then, test the updated answer. VBA my not understand that it is a formula to be evaluated. I only added a "=" character in front of the function. It should work. I used `Evaluate` for custom functions many times in a similar way... – FaneDuru Sep 07 '21 at 12:26
  • I tried this as well, but it produces a Value error in each cell as each cell gets attributed the following string "=@TestExpo($A$1:$A$35)", which Excel cannot understand as the TestExpo isn't a build-in function, I assume. I also tried to remove the "@" from the formula but it did not help. – Xav Sep 07 '21 at 13:01
  • @Xav I cannot get you. Theoretically, VBA should use the `rng.Value` property. No any formula should be written. I must leave my office now. I will post a working example. If you need to feel not a range (like `rng2`)... – FaneDuru Sep 07 '21 at 14:11
  • I added .Cells(1,1).Address(0,0) after the range name, so rng1.Cells(1,1).Address(0,0) instead of rng1.Address, and it worked ! Thanks for your proposal ! I did further investigations that I mentioned in my post update, but your solution has worked. Nevertheless I do not understand the syntax as explicitly referring to cell(1,1) in the code suggested to me that every cells of rng2 would refer to it, while each cell of rng2 refers in fact to the proper cell within rng1, i.e. rng1.cell(2,1), rng1.cell(3,1) etc for the 2nd, 3rd and so on cells of rng2. Could you please explain? – Xav Sep 07 '21 at 20:42
  • 1
    @Xav Well, `Evaluate` method is powerful, but only if it is well understood... I will try editing the answer and write a more complex testing `Sub`, which I hope to be more elocvent regarding the way `Evaluate` works... – FaneDuru Sep 08 '21 at 08:25