Whats the difference between WorksheetFunction.IsNumber() and IsNumeric() in VBA? - vba

Whats the different between using these two functions in VBA? Is there a speed advantage of using one over the other?
Application.WorksheetFunction.IsNumber(myVar)
IsNumeric(myVar)

IsNumeric is a VBA standard library function located in the VBA.Information module, like IsArray, IsDate, IsError, Err, VarType, and more:
As part of the VBA standard library, it works regardless of the host application: the standard library is safe to presume as existing on a machine that runs VBA.
Application.WorksheetFunction.IsNumber, on the other hand, is found in the Microsoft Excel object model library:
As such, the code can only work if the Excel type library is referenced. If you're already in Excel, that's not a problem. But if you're in Word and you would like to use a function that returns a Boolean when the given String is numeric, you don't want to reference the whole Excel type library just for that.
The WorksheetFunction interface is a gateway to Excel's calculation engine: with it, you can write VBA code that can now use INDEX/MATCH against carefully crafted plain-VBA arrays - it's completely awesome. But there's no guarantee that Excel's calculation engine works exactly the same way VBA does. If you want to go by Excel's rules, use WorksheetFunction.
I would probably presume VBA.Information.IsNumeric to be faster than Excel.WorksheetFunction.IsNumber though: I could be wrong, but it seems like fewer moving parts are involved with just the VBA standard library.
But the only way to find out:
You have two horses - race them!
;-)

TL;DR Unless you have a specific reason to use Excel's semantics for the function (i.e. replicating the result it would give in a cell, don't use IsNumber if you care about performance.
First of all, the result (and performance) is going to differ based on the data type that is passed into the function. If you're pulling values directly from the Worksheet, you have to be aware of the fact that Excel is going to cast the value differently depending on how you access it:
Public Sub Foo()
Debug.Print IsNumeric("1e12") 'True
Debug.Print Application.WorksheetFunction.IsNumber("1e12") 'False
[A1] = "1e12"
'The next 2 are the same, but the first is an implicit call to .Value
Debug.Print Application.WorksheetFunction.IsNumber([A1]) 'True
Debug.Print Application.WorksheetFunction.IsNumber([A1].Value) 'True
Debug.Print Application.WorksheetFunction.IsNumber([A1].Text) 'False
End Sub
Next, there is a small overhead of the dereferencing calls against WorksheetFunction, so I'll control for that in the benchmarks below by wrapping it in a With block. Note also the difference in performance (and return value o_O) between handling a string and a number:
Private Const ITERATIONS As Long = 1000000
Private Sub RunAll()
Test 1000
Test "1000"
End Sub
Private Sub Test(testValue As Variant)
IsNumericBenchMark testValue
IsNumberBenchMark testValue
End Sub
Private Sub IsNumericBenchMark(inputValue As Variant)
Dim start As Single, i As Long
start = Timer
For i = 1 To ITERATIONS
IsNumeric inputValue
Next
Debug.Print "IsNumeric" & vbTab & Timer - start & vbTab & IsNumeric(inputValue)
End Sub
Private Sub IsNumberBenchMark(inputValue As Variant)
Dim start As Single, i As Long
start = Timer
With WorksheetFunction
For i = 1 To ITERATIONS
.IsNumber inputValue
Next
End With
Debug.Print "IsNumber" & vbTab & Timer - start & vbTab & WorksheetFunction.IsNumber(inputValue)
End Sub
Results:
IsNumeric 0.09375 True
IsNumber 5.664063 True
IsNumeric 0.6796875 True
IsNumber 6.796875 False
It's official, IsNumber is not exactly a top performer.

Related

defining code on vba excel to simplify code writing process

I am attempting to reduce the amount of clutter on my code by creating "shortcuts" if you will
For instance, I always have to type
ThisWorkBook.ActiveSheet.Range
Is there a way for me to define the above to create a less wordy macro? I have tried convert to range and string and the former returns an error (but I could still get intellisense recognize and attempt to autofill) while the string version doesnt work.
Just like in any programming language, you can use variables to store data
For example:
Dim myrange As Range: Set myrange = Sheets("Sheet1").Range("B5")
Alternatively, if you will be working with the same object multiple times, you can use the With keyword
For example. instead of writing you want to work with table every time on every new line you can do
With Sheets("Sheet1").ListObjects("Table1")
.ListRows.Add
.ListColumns(2).Range(3) = "Hello World!"
' ... and so on
End With
Also, please on a sidenote: Avoid using Select/ActiveSheet/ActiveWorkbook and so on!
More info on how to here
You can create functions or customized properties, which are always evaluated when called
Property Get pARng As Range
Set pARng = ThisWorkBook.ActiveSheet.Range
End Property
Function fARng As Range
Set fARng = ThisWorkBook.ActiveSheet.Range
End Function
'Usage
Sub xxx
'...
pARng.Rows(1).Activate
'Same as ThisWorkBook.ActiveSheet.Range.Rows(1).Activate
fARng.Rows(1).Activate
'using function instead achieves same result
End Sub

How do I pass a range obj variable to a sub in Excel VBA (2016) [duplicate]

This question already has an answer here:
Array argument must be ByRef
(1 answer)
Closed 6 years ago.
Given the following code:
I can not seem to successfully pass a Range Object Variable from one sub-function to another. I spent an entire day researching, and experimenting before I swallowed pride and came here.
Please read the comments below, and reply with any ideas you have regarding why the LAST two lines will not behave.
Public Sub doSomethingToRows(ROI As Range)
*'do Something with the cell values within the supplied range*
End Sub
'
Public Sub testDoAltRows()
Dim RegionOfInterest As Range 'is this an object or not?
'*The following yields: Class doesn't support Automation (Error 430)*
'*Set RegionOfInterest = New Worksheet 'this just gives an error*
Set RegionOfInterest = Worksheets("Sheet1").Range("A1")
RegionOfInterest.Value = 1234.56 '*okay, updates cell A1*
Set RegionOfInterest = Worksheets("Sheet1").Range("B5:D15")
RegionOfInterest.Columns(2).Value = "~~~~~~" '*okay*
'doSomethingToRows (RegionOfInterest) 'why do I get "OBJECT IS REQUIRED" error?
doSomethingToRows (Worksheets("Sheet1").Range("B5:C15")) 'but this executes okay
End Sub
From the msdn documentation of the Call keyword statement,
Remarks
You are not required to use the Call keyword when calling a procedure.
However, if you use the Call keyword to call a procedure that requires
arguments, argumentlist must be enclosed in parentheses. If you omit
the Call keyword, you also must omit the parentheses around
argumentlist. If you use either Call syntax to call any intrinsic or
user-defined function, the function's return value is discarded.
To pass a whole array to a procedure, use the array name followed by
empty parentheses.
From a practical standpoint, even though Subs can be called with or without the "Call" keyword, it makes sense to pick one way and stick with it as part of your coding style. I agree with Comintern - it is my opinion, based on observation of modern VBA code, that using the "Call" keyword should be considered deprecated. Instead, invoke Subs without parenthesis around the argument list.
And now the answer to the important question:
Why does your code throw an error?
Take for example the following Subroutine:
Public Sub ShowSum(arg1 As Long, arg2 As Long)
MsgBox arg1 + arg2
End Sub
We have established that, if not using the Call keyword, Subs must be invoked like so:
ShowSum 45, 37
What happens if it were instead called like ShowSum(45, 37)? Well, you wouldn't even be able to compile as VBA immediately complains "Expected =". This is because the VBA parser sees the parenthesis and decides that this must be a Function call, and it therefore expects you to be handling the return value with an "=" assignment statement.
What about a Sub with only one argument? For example:
Public Sub ShowNum(arg1 As Long)
MsgBox arg1
End Sub
The correct way to call this Sub is ShowNum 45. But what if you typed this into the VBA IDE: ShowNum(45)? As soon as you move the cursor off of the line, you'll notice that VBA adds a space between the Sub name and the opening parenthesis, giving you a crucial clue as to how the line of code is actually being interpreted:
ShowNum (45)
VBA is not treating those parenthesis as if they surrounded the argument list - it is instead treating them as grouping parenthesis. MOST of the time, this wouldn't matter, but it does in the case of Objects which have a default member.
To see the problem this causes, try running the following:
Dim v As Variant
Set v = Range("A1")
Set v = (Range("A1")) '<--- type mismatch here
Notice that you get a "Type Mismatch" on the marked line. Now add those two statements to the watch window and look at the "Type" column:
+-------------+-----+--------------+
| Expression |Value| Type |
+-------------+-----+--------------+
|Range("A1") | |Object/Range |
|(Range("A1"))| |Variant/String|
+-------------+-----+--------------+
When you surround an Object with grouping parenthesis, its default property is evaluated - in the case of the Range object, it is the Value property.
So it's really just a coincidence that VBA allowed you to get away with "putting parenthesis around the argumentlist" - really, VBA just interprets this as grouping parenthesis and evaluates the value accordingly. You can see by trying the same thing on a Sub with multiple parameters that it is invalid in VBA to invoke a Sub with parenthesis around the argument list.
#PaulG
Try this:
Public Sub Main()
Debug.Print TypeName(Range("A1"))
Debug.Print TypeName((Range("A1")))
End Sub
okay, I knew after I posted this question I'd be struck by lighting and receive an answer.
When passing an object VARIABLE to a sub-function and wishing to use parentheses "()", one must use CALL! Thus the correction to my code sample is:
**CALL doSomethingToRows(RegionOfInterest)**
Thank you!
Maybe we're talking about different things, but here's an example to make it a bit clearer what I mean.
Option Explicit
Sub TestDisplay()
Dim r As Range
'Create some range object
Set r = Range("A1")
'Invoke with Call.
Call DisplaySomething(r)
'Invoke without Call.
DisplaySomething r
End Sub
Sub DisplaySomething(ByVal Data As Range)
Debug.Print "Hi my type is " & TypeName(Data)
End Sub
Both calls work perfectly. One with Call and the other without.
Edit:
#Conintern. Thanks for explaining that. I see what is meant now.
However, I still respectively disagree.
If I declare the following:
Function DisplaySomething(ByVal Data As String)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
and invoke it:
Debug.print DisplaySomething(Range("A1"))
I believe that Excel has been clever and converted to a string. It can do that by invoking the Default Parameter and can convert to a string.
However, as in the original parameter example, If I declare the following:
Function DisplaySomething(ByVal Data As Range)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
There is no call on the Default Parameter, however it is called, because Excel was able to resolve it to that type.
Function DisplaySomething(ByVal Data As Double)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
will return a double because it was able to coerce to a double.
Indeed in those examples the Default was called.
But in this example we are defining as Range. No Default called there however it is invoked - brackets or no brackets.
I believe this is more to do with Excel and data coercion.
Similar to the following:
Public Function Test(ByVal i As String) As Integer
Test = i
End Function
and invoking with:
Debug.print Test("1")
BTW, yes I know this isn't an object without a Default parmeter. Im pointing out data coercion. Excel does its best to resolve it.
Could be wrong mind you...

Disable ROUND function in Excel

As an extention to this question, I'm trying to engineer some controls to prevent users in my lab from using Excel's ROUND function (operating procedures require us to use the round-to-even/banker's rounding method).
I found it rather simple to write the function to get the correct rounding
Function RoundEven(num, precision)
RoundEven = Round(num, precision)
End Function
What I would like to do in addition to this is disable the ROUND function altogether. I tried adding
Function Round()
Round = "Bad Rounding Technique"
End Function
But this overwrote the rounding method in RoundEven, and so any rounding I attempt ends up returning the "Bad Rounding Technique" message.
What's the proper way to assign Round a new local value while still having RoundEven pull VBA's default rounding method.
I don't think you can write your own ROUND function and have it replace the Excel's. I just tried it in 2010. Both mine and Excel's show up in intellisense, but regardless of the one I pick, it evaluates as built-in ROUND.
You could use a change event to change any instance of ROUND to `ROUNDEVEN'
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rCell As Range
For Each rCell In Target.Cells
Application.EnableEvents = False
rCell.Formula = Replace$(rCell.Formula, "ROUND(", "ROUNDEVEN(")
Application.EnableEvents = True
Next rCell
End Sub
This is in the sheet's module if you only care about one sheet. You'd need the ThisWorkbook module or a custom class module with an Application variable declare WithEvents depending on how broad a scope you need.
This will definitely work for simple stuff, but I'm quite sure there are circumstances that will break it.
Another thing you could do is validate the formulas before some critical moment. Instead of real-time fixing the problem, just check every cell in the worksheet prior to it being submitted (or whatever the critical event is). You could use the ThisWorkbook BeforeSave event to prevent the user from saving, for instance, if there are any formulas with ROUND( in them.
I'm not sure this will work, but try using: Application.WorksheetFunction.Round to get the original function. (And hope what you do doesn't override it).
Why don't you simply override the Round function and forget about RoundEven?
Will this work for you?
dim callingFunctionName as string ' we need a global variable to let Round know who is calling it
Function RoundEven(num, precision)
callingFunctionName = "RoundEven"
RoundEven = Round(num, precision)
End Function
'' Disable Round function from callers other than RoundEven (In fact you can have a safelist of functions that can call this function now)
Function Round()
if callingFunctionName = "RoundEven" then
'' allow Round here
else
Round = "Bad Rounding Technique"
end if
End Function

Execute a user-defined function into another cell VBA Excel

I need to automatize a process which execute various (a lot) user-defined function with different input parameters.
I am using the solution of timer API found in I don't want my Excel Add-In to return an array (instead I need a UDF to change other cells) .
My question is the following: "Does anybody can explain to me HOW IT IS WORKING?" If I debug this code in order to understand and change what I need, I simply go crazy.
1) Let say that I am passing to the public function AddTwoNumbers 14 and 45. While inside AddTwoNumber, the Application.Caller and the Application.Caller.Address are chached into a collection (ok, easier than vectors in order not to bother with type). Application.Caller is kind of a structured object where I can find the function called as a string (in this case "my_function"), for example in Application.Caller.Formula.
!!! Nowhere in the collection mCalculatedCells I can find the result 59 stored.
2)Ok, fair enough. Now I pass through the two UDF routines, set the timers, kill the timers.
As soon as I am inside the AfterUDFRoutine2 sub, the mCalculatedCell(1) (the first -- and sole -- item of my collection) has MAGICALLY (??!?!?!!!??) obtained in its Text field exactly the result "59" and apparently the command Set Cell = mCalculatedCells(1) (where on the left I have a Range and on the right I have ... I don't know) is able to put this result "59" into the variable Cell that afterward I can write with the .Offset(0,1) Range property on the cell to the right.
I would like to understand this point because I would like to give MORE task to to inside a single collection or able to wait for the current task to be finished before asking for a new one (otherwise I am over-writing the 59 with the other result). Indeed I read somewhere that all the tasks scheduled with the API setTimer will wait for all the callback to be execute before execute itself (or something like this).
As you can see I am at a loss. Any help would be really really welcomed.
In the following I try to be more specific on what (as a whole)
I am planning to achieved.
To be more specific, I have the function
public function my_function(input1 as string, Field2 as string) as double
/*some code */
end function
I have (let's say) 10 different strings to be given as Field2.
My strategy is as follow:
1)I defined (with a xlw wrapper from a C++ code) the grid of all my input values
2)define as string all the functions "my_function" with the various inputs
3)use the nested API timer as in the link to write my functions IN THE RIGHT CELLS as FORMULAS (not string anymore)
3)use a macro to build the entire worksheet and then retrieve the results.
4)use my xlw wrapper xll to process further my data.
You may wonder WHY should I pass through Excel instead of doing everything in C++. (Sometime I ask myself the same thing...) The prototype my_function that I gave above has inside some Excel Add-In that I need to use and they work only inside Excel.
It is working pretty well IN THE CASE I HAVE ONLY 1 "instance" of my_function to write for the give grid of input. I can even put inside the same worksheet more grids, then use the API trick to write various different my_functions for the different grids and then make a full calculation rebuild of the entire worksheet to obtain the result. It works.
However, as soon as I want to give more tasks inside the same API trick (because for the same grid of input I need more calls to my_function) I am not able to proceed any further.
After Axel Richter's comment I would like to ad some other information
#Axel Richter
Thank you very much for your reply.
Sorry for that, almost surely I wasn't clear with my purposes.
Here I try to sketch an example, I use integer for simplicity and let's say that my_function works pretty much as the SUM function of Excel (even if being an Excel native function I could call SUM directly into VBA but it is for the sake of an example).
If I have these inputs:
input1 = "14.5"
a vector of different values for Field2, for instance (11;0.52;45139)
and then I want to write somewhere my_function (which makes the sum of the two values given as input).
I have to write down in a cell =my_function(14.5;11), in the other =my_function(14.5;0.52) and in a third one =my_function(14.5;45139).
These input changes any time I need to refresh my data, then I cannot use directly a sub (I think) and, in any case, as far as I understand, in writing directly without the trick I linked, I will always obtain strings : something like '=my_function(14.5;0.52). Once evaluated (for example by a full rebuild or going over the written cell and make F2 + return) will give me only the string "=my_function(14.5;0.52)" and not its result.
I tried at the beginning to use an Evaluate method which works well as soon as I write something like 14.5+0.52, but it doesn't work as soon as a function (nor a user-defined function) is used instead.
This is "as far as I can understand". In the case you can enlighten me (and maybe show an easier track to follow), it would be simply GREAT.
So far the comments are correct in that they repeat the simple point that a User-Defined Function called a worksheet can only return a value, and all other actions that might inject values elsewhere into the worksheet calculation tree are forbidden.
That's not the end of the story. You'll notice that there are add-ins, like the Reuters Eikon market data service and Bloomberg for Excel, that provide functions which exist in a single cell but which write blocks of data onto the sheet outside the calling cell.
These functions use the RTD (Real Time Data) API, which is documented on MSDN:
How to create a RTD server for Excel
How to set up and use the RTD function in Excel
You may find this link useful, too:
Excel RTD Servers: Minimal C# Implementation
However, RTD is all about COM servers outside Excel.exe, you have to write them in another language (usually C# or C++), and that isn't the question you asked: you want to do this in VBA.
But I have, at least, made a token effort to give the 'right' answer.
Now for the 'wrong' answer, and actually doing something Microsoft would rather you didn't do. You can't just call a function, call a subroutine or method from the function, and write to the secondary target using the subroutine: Excel will follow the chain and detect that you're injecting values into the sheet calculation, and the write will fail.
You have to insert a break into that chain; and this means using events, or a timer call, or (as in RTD) an external process.
I can suggest two methods that will actually work:
1: Monitor the cell in the Worksheet_Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strFunc As String strFunc = "NukeThePrimaryTargets" If Left(Target.Formula, Len(strFunc) + 1) = strFunc Then Call NukeTheSecondaryTargets End If End Sub
Alternatively...
2: Use the Timer callback API:
However, I'm not posting code for that: it's complex, clunky, and it takes a lot of testing (so I'd end up posting untested code on StackOverflow). But it does actually work.
I can give you an example of a tested Timer Callback in VBA:
Using the VBA InputBox for passwords and hiding the user's keyboard input with asterisks.
But this is for an unrelated task. Feel free to adapt it if you wish.
Edited with following requirements: It is necessary to run a user defined worksheet function, because there are addins called in this function and those work only within a Excel sheet. The function has to run multiple times with different parameters and its results have to be gotten from the sheet.
So this is my solution now:
Public Function my_function(input1 As Double, input2 As Double) As Double
my_function = input1 + input2
End Function
Private Function getMy_Function_Results(input1 As Double, input2() As Double) As Variant
Dim results() As Double
'set the Formulas
With Worksheets(1)
For i = LBound(input2) To UBound(input2)
strFormula = "=my_function(" & Str(input1) & ", " & Str(input2(i)) & ")"
.Cells(1, i + 1).Formula = strFormula
Next
'get the Results
.Calculate
For i = LBound(input2) To UBound(input2)
ReDim Preserve results(i)
results(i) = .Cells(1, i + 1).Value
Next
End With
getMy_Function_Results = results
End Function
Sub test()
Dim dFieldInput2() As Double
Dim dInput1 As Double
dInput1 = Val(InputBox("Value for input1"))
dInput = 0
iIter = 0
Do
dInput = InputBox("Values for fieldInput2; 0=END")
If Val(dInput) <> 0 Then
ReDim Preserve dFieldInput2(iIter)
dFieldInput2(iIter) = Val(dInput)
iIter = iIter + 1
End If
Loop While dInput <> 0
On Error GoTo noFieldInput2
i = UBound(dFieldInput2)
On Error GoTo 0
vResults = getMy_Function_Results(dInput1, dFieldInput2)
For i = LBound(vResults) To UBound(vResults)
MsgBox vResults(i)
Next
noFieldInput2:
End Sub
The user can input first a value input1 and then input multiple fieldInput2 until he inputs the value 0. Then the results will be calculated and presented.
Greetings
Axel

Excel Visual Basic call function as stand-alone routine

I'll get straight to the point; I'm trying to define a Function in Visual Basic which can simply be called without having to have something on the 'other side of the equation' as it were. Essentially I want to be able to define a routine which can be passed a series of variables and executes a routine based on those variables.
I currently have the following Function defined:
Function ImportData(WebAddress As String, OutputCell As Range)
With ActiveSheet.QueryTables.Add(Connection:= _
"URL;" & WebAddress & _
"bin/excelget.exe?TICKER=msft", _
Destination:=OutputCell)
.BackgroundQuery = True
.TablesOnlyFromHTML = True
.Refresh BackgroundQuery:=False
.SaveData = True
End With
End Function
What I want to be able to do is simply call this Function but not necessarily make something equal to or use this Function to manipulate something. So, for example, I'd like to be able to do something like the following:
Private Sub ExampleButton_Click()
ImportData("http://www.exampleURL.com/examplejsonhtmlformat","A3")
End Sub
When this Function is called, it simply steps through the Function using the variables defined. There is already an output defined in OutputCell so a cell doesn't need to 'equal' the output of this Function.
If anybody has any input, it would be much appreciated.
Thanks.
You want to make it a Sub - it is exactly what you describe: code that can be called but that doesn't return a value. Note that you don't put the parameters of a sub in parentheses when you call it. If you have
Sub myTest(a,b)
Then you call it with
myTest thing1, thing2
And NOT with
myTest(thing1, thing2)
Update based on excellent comments from #hnk and #Ioannis:
It is possible to call a Sub with
Call myTest(thing1, thing2)
But there is a subtlety, which has to do with the difference between passing a variable by value or by reference. When you pass by value, you make a copy: changing the parameter in the program does not change the original. However, when you pass by reference, it becomes possible to change the value of the parameter inside the sub - and that becomes the new value of the parameter after the sub returns. I'd the prototype says you expect the value to be passed by reference:
Sub MyTest(ByRef a)
Then you can override this behavior as follows:
Call with Passing by
MyTest a Reference
MyTest (a) Value
Call MyTest(a) Reference
Call MyTest((a)) Value
In general it is better to be explicit in the function prototype - specify if you want byVal or byRef and if the calling program gets it wrong you get warned bu the compiler. More info at Hidden features of VBA
If you are not at least a little bit confused or at least annoyed at Microsoft after this, you were not paying attention...
afterword it was pointed out by Rory that it is possible to call functions without assigning their return value. So you can have either
X = myFunc(y)
Or
myFunc y
Both will call the function - but note that when you don't expect a return value you don't use parentheses. Oh Microsoft, what were you thinking...