Can't get vba function to return correct answer - vba

I'm learning VBA from a book, and the sample function in it is:
Function CubeRoot(number)
CubeRoot = number ^ (1 / 3)
End Function
If I call that function from a sub procedure, I get the correct result, but when I try to use it directly in Excel, I get a "FALSE" result in the cell. I followed the book step-by-step and it does not say to do anything that I haven't done. What am I missing/doing wrong or maybe I need to change some option? I am using Excel 2016.
Edit:
This is what I write in an excel cell: =CubeRoot(8) The result I get is FALSE. However, if I click the fX button and the function arguments box pops up, the formula result shown there is calculated correctly.

I write in an excel cell: =CubeRoot(8) The result I get is FALSE
At first try declaring the variable types properly:
Function CubeRoot(number As Variant) As Double
CubeRoot = number ^ (1 / 3)
End Function
But I do not believe that this will help because the error result then would be #VALUE! but not a boolean FALSE.
But the ^ operator can be overloaded. Maybe by some AddIn you are using. So it seems that it was overloaded to be some boolean operator.
Try the following:
Function CubeRoot(number As Variant) As Double
CubeRoot = Application.Power(number, (1 / 3))
End Function
If that helps, then my suspicion is true and you should go through your installed AddIns to determine which one is doing the weird overload.
If even the using Application.Power(number, (1 / 3)) does not help, then the cell containing the formula seems to be formatted using a weird number format. Try formatting it using the General number format.
According to your mention: "if I click the fX button and the function arguments box pops up, the formula result shown there is calculated correctly.", I suspect it is the weird number format rather than the first two suspicions. So do selecting the cell containing the formula =CubeRoot(8) and set number format General to it using Home tab and Number group.
Finally the problem was that the worksheet functions had been inputted into Excel4 macro sheets instead of worksheets. Probably the Excel4 macro sheets were added using Sheets.Add Method using the type xlExcel4MacroSheet. But in Excel4 macro sheets only Excel4 macro code is possible not default formulas as in worksheets. Excel4 macro is ancient type of macro language from Excel 4.0 (1992). Since Excel 5.0 (1993) VBA is used for Excel macros.

Declare all variables as double, and also the function return type as double.
By default 8 is treated as integer, so 8*X will result integer even if x is single or double.

Related

Excel VBA: Insheet function code can not access other cells on sheet

I'm having some issues with an insheet function that I am writing in VBA for Excel. What I eventually am trying to achieve is an excel function which is called from within a cell on your worksheet, that outputs a range of data points underneath the cell from which it is called (like the excel function =BDP() of financial data provider Bloomberg). I cannot specify the output range beforehand because I don't know how many data points it is going to output.
The issue seems to be that excel does not allow you to edit cells on a sheet from within a function, apart from the cell from which the function is called.
I have created a simple program to isolate the problem, for the sake of this question.
The following function, when called from within an excel sheet via =test(10), should produce a list of integers from 1 to 10 underneath the cell from which it is called.
Function test(number As Integer)
For i = 1 To number
Application.Caller.Offset(i, 0) = i
Next i
End Function
The code is very simple, yet nothing happens on the worksheet from which this formula is called (except a #Value error sometimes). I have tried several other specifications of the code, like for instance:
Function test(number As Integer)
Dim tempRange As Range
Set tempRange = Worksheets("Sheet1").Range(Application.Caller.Address)
For i = 1 To number
tempRange.Offset(i, 0) = i
Next i
End Function
Strangely enough, in this last piece of code, the command "debug.print tempRange.address" does print out the address from which the function is called.
The problem seems to be updating values on the worksheet from within an insheet function. Could anybody please give some guidance as to whether it is possible to achieve this via a different method?
Thanks a lot, J
User defined functions are only allowed to alter the values of the cells they are entered into, because Excel's calculation method is built on that assumption.
Methods of bypassing this limitation usually involve scary things like caching the results and locations you want to change and then rewriting them in an after calculate event, whilst taking care of any possible circularity or infinite loops.
The simplest solution is to enter a multi-cell array formula into more cells than you will ever need.
But if you really need to do this I would recommend looking at Govert's Excel DNA which has some array resizer function.
Resizing Excel UDF results
Consider:
Public Function test(number As Integer)
Dim i As Long, ary()
ReDim ary(1 To number, 1 To 1)
For i = 1 To number
ary(i, 1) = i
Next i
test = ary
End Function
Select a block of cells (in this case from C1 through C10), and array enter:
=test(10)
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key.

How do I switch off recalculation when using Solver in VBA?

I tried to use Solver on a working sheet, say sheet A. I also have some cells with "=rand()" on sheet A, or say any formula on the sheet using custom functions with Application.Volatile written inside. I'd like these volatile cells to stop recalculate when I am doing solver, so I used Application.Calculation = xlCalculationManual in my Solver program, but turned out after I ran the Solver, I found that those volatile cells have changed. Where have I done wrong?
There are 2 ways to avoid this:
Do not use solver! If you write an own sub to do the stuff you can use Range.Calculate while the calculation is manual to just calculate this cells (all other cells will be ignored).
Change the formulas and go with iteration options -> formulas -> enable iterative calculation. Now use a helper-cell which holds true/false (or 1/0 or whatever) and the change the formulas you do not want to calculate as follows(helper-cell A1 / formula A2):
=IF(A1,A2,[your old formula])
This way, as long as A1 is true it will output the value from before the calculation.
As far as I know, there is no other way, because solver does a Calculate each time a new cycle is tested.
EDIT:
Having volatile UDF which do not need to be volatile the whole time, you can use a simple trick (again a helper-cell) is needed:
A1: [volatile function like =NOW() or =RAND()]
A2: =MY_UDF(A1)
In Module:
Public Function MY_UDF(a As Variant) As Double
a = a
MY_UDF = Now
End Function
As long as A1 holds something volatile, your UDF will recalculate every time. But if you empty out A1 (or make it at leas non-volatile) there will be no change anymore to the value submitted to your UDF and this way excel/vba assumes that also no change will happen and just skip it for recalculation. This way you also can build up your own RAND-UDF (with different name of course) to just stop ALL volatile functions in your workbook as long as your helper-cell is non-volatile.
As a note: after making A1 (in this example) non-volatile, the first calculation afterwards will still run 1 time like it is volatile. Also changing A1 from one value to another (like 0 to 1) will run one time.
Here is a workaround that might help if you want to have random values in a spreadsheet that can be recalculated at will and whose volatility can be turned on and off.
First, create a 1-cell named range, say "trigger"
Then, put the following code in a standard code module:
Function SemiVolatileRand(x As Variant) As Double
SemiVolatileRand = x - x + Rnd()
End Function
Sub ReSample()
Randomize
Range("trigger").Value = Range("trigger").Value + 0.01
End Sub
Attach the ReSample sub to a button. Replace all occurrences of =RAND() by
=SemiVolatileRand(trigger). They will recalulcate whenever the button is pressed. Also, if you ever want to turn full volatility back on, just put the formula =RAND() in the trigger cell. (Getting full volatility in this last case seemed to require that my code does something with the dummy variable x, hence the somewhat poinless x - x).
Randomize reseeds the random number generator from the system clock. It should be called at least once per session. If you don't, then each time you open an Excel workbook which uses VBA rnd, you will get the same sequence of random values. You can verify this by making a blank workbook and in the ThisWorkbook code module put:
Private Sub Workbook_Open()
MsgBox Rnd()
End Sub
The message block will display the same value each time you open the workbook. On the other hand if you put the line Randomize before the MsgBox then you will get different values each time you open it.
Note that the workbook open event is a natural place to put the statement Randomize if you are planning to use rnd.
The reason I didn't put Randomize in the function itself was both to save CPU cycles and also because of a nagging concern that a certain percentage of the time you will be reseeding the random number generator with exactly the same system time. That might be impossible with modern architectures running recent versions of Excel, but e.g. does sometimes happen if you had Randomize Timer (which you sometimes encounter when reading other's code) since the timer function has 1 millisecond resolution irrespective of the system clock.
The code I have does have the drawback that if you bypass the button and just change the trigger cell then you could miss the reseeding. If this is a concern, 1 possibility would be like this:
Public Initialized as Boolean
Function SemiVolatileRand(x As Variant) As Double
If Not Initialized Then
Randomize
Initialized = True
End If
SemiVolatileRand = x - x + Rnd()
End Function
This will prevent the function from running if rnd isn't properly seeded.
Excel itself takes care of seeding automatically with the worksheet function Rand(), so it is strictly a VBA complication.

Calling Worksheet functions from vba in foreign language versions of Excel

The following code snipet run OK in an English Language verion of Excel, however when attempting to run this code in the same workbook in a Portuguese version of Excel it errors out.
' Add color bars on every other row - attempt to make list
' easier to read.
' "PlaceAt" is a worksheet range passed into the function
With Range(PlaceAt.offset(1, 0), PlaceAt.offset(i + 1, 7))
.FormatConditions.Add Type:=xlExpression, Formula1:="=MOD(ROW(),2)=1"
.FormatConditions(1).Interior.ColorIndex = 20
End With
I believe that the problem is, in Portuguese, the ROW function is spelled LIN (not sure what the MOD function would be) and that since the function is inserted using vba, Excel's translation function does not have the opportunity to translate the function names as it normally would when opening the document.
Any ideas?
Yes FormatConditions formulas must use the local format.
My workaround is to write the wanted formula into a cell an then get the FormulaLocal of this cell which should be the exact translation in your language:
Dim tmpCell As Range
Set tmpCell = Range("IV1")
tmpCell.Formula = "=mod(row(),2)=0"
.FormatConditions.Add(xlExpression, Formula1:=tmpCell.FormulaLocal)
Don't know if there is a cleaner solution, but if so I'd like to know, so please share...
I found a cleaner solution in a different question:
Names.Add "translate", RefersTo:="=MOD(ROW(),2)=1" ' Generic separator (,) and function name (ROW).
.FormatConditions.Add Type:=xlExpression, Formula1:=Names("translate").RefersToLocal ' Local separator (;) and function name (LIN).
Names("translate").Delete
Excel 2019 - 365 version still has this issue present. I found it occurring when using the VBA in conjunction with MS Excel Named Range Manager.
Example of error I am facing, which I fixed modifying Pragmateek's 2013 answer.
In the Named Range Manager exists a named range that uses the OFFSET function.
When calling this named range and using it in VBA to set another range variable USING the Application.Intersect method results in object errors because in VBA the MS Excel named range is acquired as a string. The intersect method then attempts to determine the intersect of this string and another range. This of course fails due to OFFSET working in English MS Excel versions, however not in Portuguese / Spanish .... And I imagine other languages.
Fixing this involved:
' Names!XXXXX = "=OFFSET('DummySheet'!$AO$7,0,0,1,'DummySheet'!$AM$2)""
Sub evaluate()
...
xAxis = Names!XXXXXX
Dim tempvar As Range: Set tempvar = Range("C1")
tempvar.Formula = xAxis ' set the range to hold the formula
tempvar.Formula = tempvar.FormulaLocal ' set the range to hold the formula in the local language
Set rng = Application.Intersect(tempvar.EntireColumn, yAxis.RefersToRange.EntireRow)
This worked well. However if ANYONE has a cleaner solution, please post suggestions. Cheers!

Using Excel Formula functions (ERF, ERFC) in Excel VBA code?

I'm trying to use the error function and the complimentary error function in my program. Neither are working. I keep getting the error Compile Error: Sub or Function not defined. However, if I go into a cell and manually try to use the error function, it works.
Why is that? How can I use it in my actual code?
The error function is ERF(x) and the complimentary error function is ERFC(x).
Here's an example of things that don't work:
Sub SeriouslyWHYIsntThisWorking()
x = 3
Range("A1") = Erf(x)
End Sub
Even this doesn't work:
Sub PleaseWork()
Range("A1") = Erfc(1)
End Sub
But if I went into Excel and typed =ERF(3) or =ERFC(1) into a cell, it'll work.
I'm very new to this and probably missing something incredibly simple. Help would be greatly appreciated!
Do you have the Analysis Toolpak for VBA add-in installed/referenced? (Look for atpvbaen.xls)
The ERF function is part of that add-in, and there are two versions of it (one for use in Excel functions, and one for VBA), and you'll need the right one set up and referenced by your project to be usable.
The add-ins are standard from MSFT, but not always set up by default. If you can use it in Excel normally, then you've already set up at least the Excel version. So using it all like it looks like you want to do will require the add-in, regardless of how you implement/use that function. Meaning, if you want to share this with anyone else, they will need the add-in installed/activated.
To link together this answer with the others provided (which are equally accurate and correct), either setting a cell value with
Range("A1").value = Application.WorksheetFunction.ERF(x)
or setting a cell formula with
Range("A1").Formula = "=Erfc(" + x + ")"
will require the end-user using the add-in.
To use a worksheet formula in vba, you need to put Application.WorksheetFunction. in front of it.
Some functions do have vba equivalents, but (as far as I know) not in the case of erf and erfc
Try this:
Sub ThisShouldWorkNow()
x = 3
formula = "=Erfc(" + x + ")"
Range("A1").Formula = formula
End Sub
Totally untested, since I don't have Excel on my Linux machine... But I think I'm getting the point across -- you need to use the .Formula property of the Range object.
There's more information here:
http://msdn.microsoft.com/en-us/library/office/gg192736.aspx

VBA: How to get the last used cell by VBA code when the last error occured in a Workbook/Worksheet?

Eventually, I want to move the cell to the location where the last error occured. Edit: Forgot to say that I'm using Excel 2003.
As requested in comments...
Look up the 'Caller' property of the 'Application' object in the Excel VBA help. When you use it from a VBA routine, it will tell you where the call to the routine came from - what Range, Chart, etc.
An important thing to be aware of when using 'Application.Caller' is that it isn't always a Range object. Look at the help, but the property returns a Variant value that can be a Range, String, or Error. (It is a Range object in the case you're interested in, but you'll need to be aware of this.)
Because of the above, and the vagaries of VBA syntax when it comes to objects vs. values, it can be tricky to use 'Application.Caller'. Putting a line like:
Debug.Print Application.Caller.Address
in your code will fail when the caller isn't a Range. Doing something like:
Dim v
v = Application.Caller
will "compile", but will create circular references when the caller is a Range because you're trying to access the value of the calling Range.
This all means that it's probably best to write a little utility function for yourself:
Public Function currentCaller() As String
If TypeOf Application.Caller Is Range Then
Dim rng As Range
Set rng = Application.Caller
currentCaller = rng.Address(External:=True)
Else
currentCaller = CStr(Application.Caller)
End If
End Function
and then call it from your error handlers where you want to know where the call came from.
One more thing - obviously this can only tell you the caller once a VBA routine has actually been called. If you have errors in your calling formulas, Excel will return error values to your cells without ever calling your VBA routines.
Wrap your VBA function in another function that stores the cell location and value as variants. Keep this 'wrapper' function as basic as possible so it won't cause any additional errors.
If you're trying to debug app-crashing errors, the wrapper function could even store those values in a comma-delimited text file. Once stored, Excel can crash all it wants and you'll still know what the cell location and value were since you stored them outside of Excel beforehand.
Could this be done with an error handler?
An example of what I mean below:
sub code1()
on error goto cell A1
end sub