What does the unary plus operator do in Excel formulas? - excel

This seemingly trivial operation can be useful in a number of situations within formulas:
A. functions that would otherwise throw an error:
QUOTIENT(+A1:A3,4)
WORKDAY(+A1:A3,7)
B. converting a range to numbers - i.e. any text to zero:
N(+A1:C3)
C. returning an array of mixed data from different sheets:
CELL("contents",IF(1,+INDIRECT({"Sheet1!A1","Sheet2!B2","Sheet3!C3"})))
I have found very little on this - perhaps this is a new finding.
This question is in part for interests' sake and in part to see if anyone can shed further light or find other possible applications - excel or vba related?

In some of those examples, e.g. for WORKDAY and QUOTIENT, the + is converting a range to an array
Many of the old Analysis ToolPak functions like WORKDAY, NETWORKDAYS, WEEKNUM etc. won't accept a range as an argument, but in Excel 2007 or later excel versions they will accept an array as an argument - using +0 or -- (or apparently +) will convert the range to an array, so in Excel 2007 if you use a formula like this:
=AVERAGE(WEEKNUM(A1:A3))
where A1:A3 contain dates
....it will return an error
but this version (array entered with CTRL+SHIFT+ENTER) will work to give you the average of the week numbers:
=AVERAGE(WEEKNUM(+A1:A3))

Various tests confirm the range to array explanation put forward by Barry Houdini. I'm including some results from the VBE watch window here for reference:
The watch window returns valuable details of the data types returned which are not available in the normal formula evaluation window. Note that [+A1:A3] returns a variant array whereas [A1:A3] returns a range reference. This method also shows that OFFSET and INDIRECT with array argument return arrays of range references. To force array evaluation you can wrap the formula in TRANSPOSE.
As a general technique copy the formula to the immediate window and enclose in square brackets then select the formula in the immediate window and drag to the watch window. No separate code is generally required for this but the VBA Evaluate method (or [] shortcut) doesn't like CELL or INDIRECT. A workaround for examining such formulas is to use the code below pasted into a new module:
Dim v
Function eval(formula As String)
v = Empty
With Application
.Goto "SetV(" & .ConvertFormula(formula, xlA1, xlR1C1) & ")"
End With
eval = v
End Function
Public Function SetV(Value) As Range
v = Value
Set SetV = Selection
End Function

Interesting question. It is clearly some sort of behind the scenes type conversion. I haven't tested your example in C, but it is interesting to note that in the first two cases the unary + can be replaced by either + 0 or Value():
QUOTIENT(A1:A3 + 0,4)
WORKDAY(A1:A3 + 0,7)
N(A1:C3 + 0)
or
QUOTIENT(Value(A1:A3),4)
WORKDAY(Value(A1:A3),7)
N(Value(A1:C3))
As to why exactly this happens -- I don't know. Some functions seem to pass their arguments to Excel as strings when you use them in an array formula and adding 0 (which is what the unary plus does) coerces them to numbers. In VBA I don't think that this trick should be used since it would lead to obscure code, but it is useful to know for array formulas.

Related

Return an Excel Array Constant from VBA User-Defined Function

I have a spreadsheet like this:
A1: APPLE
A2: BANANA
A3: ORANGE
A4: APPLE
A5: BANANA
A6: ORANGE
(This repeats until A20)
So this formula would count all instances of "APPLE":
=COUNTIF(A1:A20, "APPLE")
And This formula does the same, except with a non-contiguous range:
=SUM(COUNTIF(INDIRECT({"A1:A6", "A8:A20"}), "APPLE"))
The INDIRECT function is taking an array constant with the ranges defined as strings. Note the curly braces and the quotes around each range. (That's based on the technique described here.)
However, if I define a VBA function that returns an array of strings and pass that into INDIRECT, it seems that only the first range is respected.
This is my VBA function:
Function TestFn() As Variant
TestFn = Array("A1:A6", "A8:A20")
End Function
This is my formula:
=SUM(COUNTIF(INDIRECT(TestFn()), "APPLE"))
If I use Excel's "Evaluate Formula" button, I can see that TestFn() resolves to a single string "B1:B6". However, if I use the VBA debugger, I see that both strings are in there.
Expression Value Type
TestFn Variant/Variant(0 to 1)
- TestFn(0) "B1:B6" Variant/String
- TestFn(1) "B8:B20" Variant/String
How can I return an array of strings from an Excel VBA function that can be passed into INDIRECT in the same manner?
I think this comes down to: what internal object is created by using curly braces when constructing the formula without VBA, and can I create that same internal object from within VBA?
I've also tried returning an array of strings, rather than a variant containing a string array.
Function TestFn() As String()
TestFn = Split("B1:B6,B8:B20", ",")
End Function
These are the types in the debugger:
Expression Value Type
TestFn String(0 to 1)
- TestFn(0) "B1:B6" String
- TestFn(1) "B8:B20" String
But the result is the same. I think I need to use some type of container type, but I'm not sure what to use or how to figure out what to use.
I don't actually need to use the SUM...INDIRECT hack if there is a better way, and I'd be happy to use an array formula if that's appropriate.
If I could let my function return ranges directly, rather than strings representing ranges, that would be even better than my current approach.
If I could set up an array formula something like this (doesn't have to be exactly like this):
{=COUNTIF(TestFn(), "APPLE")}
Then let my UDF look something like this:
Function TestFn() As Range()
TestFn = Array(Range("A1:A6"), Range("A8:A20"))
End Function
Or maybe something like this:
Function TestFn() As Range
TestFn = Union(Range("A1:A6"), Range("A8:A20"))
End Function
That would suit my needs.
The key here is that I just want my VBA to only define which cells are operated on, not what the operation is, and i want to deal with non-contiguous ranges. I want the actual operations to be defined in the excel formulas, because these will be modified by people who aren't expected to understand how to read and write VBA code. (Heck, I don't want to ever look at VBA code again either.) In this case, I'm using COUNTIF, but my VBA function will be used with other excel formula functions as well.
Surely someone can explain this better, but let me try. You have used both:
=SUM(COUNTIF(INDIRECT({"A1:A6","A8:A20"}), "APPLE"))
And...
=SUM(COUNTIF(INDIRECT(TestFn()), "APPLE"))
About the first formula; you have basically used the INDIRECT() functions to translate text strings into a real range. It's actually a very nice use of INDIRECT() I must say :).
But doing so using the curly brackets you told Excel that it is more than just one string, you have fed the function with multiple data, an array! Typically , an array in Excel is a simple set of data. This data can be text, numbers, or both. You have used text.
In the second formula you have fed the formula with an array through an UDF but it's lacking the curly brackets. Excel doesn't know you want to compare multiple ranges and will only evaluate the first item in your array.
However using CtrlShiftEnter you telling Excel you want to feed the formula with an array, being a set of data/ranges to compare.
So:
{=SUM(COUNTIF(INDIRECT(TestFn()), "APPLE"))}
Will work :)
I'm sure someone else is better in explaining ;)

Excel VBA should run excel function - RIGHT LEN

I would like to put the below coding into a vba like a function. There is a bunch of data created already by VBA, and when the VBA does its work, then the following function should be run, but i dont know how to add to my vba so that the function always runs as long as data contains. The macro i created already puts the datasheet together, now instead of creating the below with lenthy codings, i just want my macro to run the below, like a man who clicks on the below right hand corner of the cell which contains the below function.
It should be something: Activesheet.ForulaR1C1 = "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))" something. Can someone help me? Thanks
ORIGINAL FUNCTION TO BE RUN "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))"
This is where I am at now:
Sub Project_numbers()
Dim j As Integer
Zorro = Range("AY" & Rows.Count).End(xlUp).Row
o = 4
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(AE4,LEN(AE4)-FIND(".",AE4))"
o = o + 1
End Sub
You have a couple of problems here. The biggest is that you've got quotation marks in your formula. VBA reads these as the end of the string, so it's interpreting your formula as two separate text strings: =Right(AE4,LEN(AE4)-FIND( and ,AE4)), separated by a .. This isn't a structure VBA can do anything with, so it's going to fail at that point.
When you're inserting a formula with VBA that contains quotation marks, you need to use two quotes together to indicate that it's a literal quote mark that's part of the string, rather than the end of the string:
"=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The second problem is that you're using the FormulaR1C1 method, which expects cell references to be given in R1C1 (row#column#) notation, rather than A1 notation, but then passing it a formula that uses A1 notation. Again, this is going to confuse the issue and produce errors.
I'm guessing you used the macro recorder to get the syntax, then inserted your own formula? The macro recorder, for some weird reason, loves to use the R1C1 reference style, but we can use a different method for written code.
The full line you need is:
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
EDITED TO ADD:
With further information, specifically that you need the range referenced to change as you loop, you have some options on how to do it.
1. Use the R1C1 reference style
This allows you to include relative references in formulae easily. You'll use R to designate the formula's row, and C to designate its column; so a cell that referred to itself would simply be =RC. You can follow the R and C with numbers to designate specific rows and columns, so cell B2 would be =R2C2 - row 2, column 2. More usefully, you can use =R[#]C[#] to offset your formula by a certain amount.
In your formula, assuming it's always going to be looking at column AE but whichever row the formula is entered into, your line would be:
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(RC31,LEN(RC31)-Find(""."",RC31))"
2. Build your formula from variables.
You already have a variable you can use, o, so we can combine that with the rest of the string to get the appropriate references. It's harder to read, though...
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE" & o & ",LEN(AE" & o & ") - FIND(""."",AE" & o & "))"
Personally, I find this method rather cumbersome to work with, but it's an option.
3. Assign the formula to your entire range as a single operation
Personally, I prefer this option; I find it to be the neatest one. I'm assuming, from your formula, that your data starts on row 4, and you want the formula to go into every cell between AE4 and the end of your data, which is stored in Zorro. You can use this line to add the formula in one go:
Worksheets("MJE").Range("AF4","AF" & Zorro).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The cell references will update automatically for each row. There's no need for a loop with this method - of course, if you're looping anyway, that may be no great saving.

In Excel, how can I avoid repeating a big part of the formula just to check if the return value is blank?

I have a situation where I am referencing cells in a different worksheet and returning the values of cells from that worksheet. Although it works, I find my current method inefficient because I have to repeat the formula in the logical test part of the IF statement:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **EXTREMELY LONG COMPLICATED FORMULA**, "")
As you can see, I must repeat the main part of the formula just to check if it is blank first. If I do not do this, I get a zero in the cell (for blank values in the referenced worksheet). I'm looking for something more like:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **RETURN VALUE**, "")
This looks cleaner to me because I won't have to repeat myself. Also, if we ever have to update the formula, I won't have to duplicate my changes to the repeated parts. Is there a way to do this?
The above is actually a simplified version of my problem, but the answer should get me where I need to go. My actual formula has nested IF statements checking along the way for blanks. For reference, here it is:
=IFERROR(IF(SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))<>"",IF(INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2)))<>"",INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))),""),""),"")
The most obvious solution is to use a helper column or cell. Just put EXTREMELY LONG COMPLICATED FORMULA somewhere in your spreadsheet, then refer to that cell in your IF formula.
Edit
To avoid a helper column, here is a trick I've used on occasion:
=IFERROR(VALUE(long_formula&""),"")
What this does is, concatenate the result of long formula with an empty string (which converts it to a string), then take the value of all that (which converts it back to a number if possible), then substitute any errors with a blank. (An error would occur if you attempt to take the value of something that's not numerical.)
This will only work if you either have a numerical result or an empty result. It will fail if you have a text result.
As of March 2020, Excel includes the LET function. You can write:
=LET(ELCF,**EXTREMELY LONG COMPLICATED FORMULA**,IF(ELCF <> "", ELCF, ""))
Where the three parameters are:
the name you will use to refer to your calculation,
the calculation itself, and
the final formula using the calculation.
The function also allows for multiple names to be defined. The general syntax is:
=LET(name1, name_value1, calculation_or_name2, [name_value2, calculation_or_name3...])
https://support.microsoft.com/en-us/office/let-function-34842dd8-b92b-4d3f-b325-b8b8f9908999
Do you need the blank in the cell for further calulations or if-functions or Do you just dont want to see the 0s?
Second case:
Just use a number format for the column like
0,00;-0,00;"";#
First case:
Put the following code in a module:
Option Explicit
Public Function IfEmpty(LongFormula As String) As String
If LongFormula = "" Then
IfEmpty = ""
Else
IfEmpty = LongFormula
End If
End Function
And use it in your worksheet like
=IfEmpty(**EXTREMELY LONG COMPLICATED FORMULA**)

Print equations components in a text box - excel vba

I'm trying to build a toll to make data analysis. I want to code a text box that will show the values that compound the result of each cell I select. For instance: the cell A1 contains the value "2", the cell B1 contains the value "3" and the cell C1 is =A1*B1 , so "6". I want to add a text box that when I select the cell C1 will bring the text "2*3" instead of "A1*B1" as it shows in the formula bar. Does anyone knows how to do it?
The challenge you're going to face is this: there is no such thing as a "formula" object in the Excel object model, because a formula is a string of essentially arbitrary length and construction, arbitrary levels of nesting and cell references/precedents, each of which may contain a formula (which would require some recursion) or a constant, etc. There is no "general" way to parse these things, although as the linked answer indicates, there are some tools out there which may do something similar.
If you have specific cases, you can probably hack something that will work for those cases, but will not be generally applicable to other formulae. Using a range object's .DirectPrecendents, you can trace cells which are referred to in the formula, and using some string functions you may be able to reverse-engineer the formula to constant expressions, and recombine with the operators. HOWEVER, my own testing shows this to be fraught with problems because each of these formula will return the same list of DirectPrecedents:
=$A1*$B1
=$A$1*$B$1
=$A$1*B1
=A1*B1
In those cases (and others), the precedents will be the ranges A1 and B1, respectively, but each of those variants would require that you can identify whether the row and/or column in each precedent is "absolute", at runtime, otherwise, any attempts to use string functions like Mid, Left and Instr will likely fail or return false positives.
If your formula are exceedingly simple, and you know you are always dealing with ONLY references, each of which contain constant expressions (not other formula refernces), e.g.:
=$A$1*B$1 'Where A1 contains a constant and so does B1
You could do something like:
Function parseFormula(cl as Range, operator as String)
Dim arr, i As Long
Dim ret As String
ret = "="
arr = Split(Mid(cl.Formula, 2), operator)
' yields an array like {$A$1, $B1}
For i = 0 To UBound(arr)
If (i = 0) Then
ret = ret & Range(arr(i)).Value
Else
ret = ret & operator & Range(arr(i)).Value
End If
Next
parseFormula = ret
End Function
Of course this will not work for for formula with mixed operators like:
=A1+B1/C1
Nor will it work for formula which mix constants and references like:
=65+B2/C1

Count number of numbers in a single cell

I want to count the number of numbers in a single cell.
Example:
In cell A1, I have the following addition:
=12+45+23+51+10 which totals (and shows) 141.
In cell B1, I would like the see how many numbers have been added together, which means that there should be 5 (12 is a number, 45 another one, etc... and all together, there are 5 numbers in cell A1).
I know it seems to be a ridiculous question, but I scanned all the platforms for this issue and did not find any suitable solution. Tried all the LEN and LEN SUBSTITUTE alternatives out there, but somehow it does not work.
Thank you upfront for your help. Optimal solution would be a excel formula, alternatively VBA would also work.
Excel 2013 has a new function Formulatext() which returns the, well, formula text. Using that the Len() and Substitute() approach works.
=LEN(FORMULATEXT(A1))-LEN(SUBSTITUTE(FORMULATEXT(A1),"+",""))+1
Hit Alt+F11 and Insert->Module with the following VBA:
Function NumCount(Rng As Range)
x = Split(Rng.Formula, "+")
NumCount = UBound(x) + 1
End Function
Then you can call =NumCount(A1) on any cell to get the number of numbers in the formula for that cell.
Use this VBA:
Function GetFormula(Cell as Range) as String
GetFormula = Cell.Formula
End Function
and then to get the number of numbers...
=LEN(GetFormula(D99))-LEN(SUBSTITUTE(GetFormula(D99),"+",""))
on this as my D99 contents...
=45+46+47+50+100
The one major drawback here is that I'm assuming everything is + if you have -, *,/ or other operators this gets more challenging. you might be able to use replace for each but you'd always be limited to the 4 basic operators... if someone used exponents or mod, this would be harder.
Also possible in earlier Excel versions without VBA - subject to (i) there is always at least one value in the cells, (ii) the operator is always + and (iii) the cells are in a column.
Replace = with '= in that column, apply the substitution, say:
=LEN(A1)-LEN(SUBSTITUTE(A1,"+",""))+1
in a different column and Paste Special, Value the results for that other column. Then apply Text To Columns on the original column with ' as the delimiter.
*There is no way to do this without using a User Defined Function (UDF) written in Excel VBA. Something like this should work:
Public Function numsAdded(cell1 As Range)
Dim formulaString As String
formulaString = cell1.Formula
numsAdded = Len(formulaString) - Len(Replace(formulaString, "+", "")) + 1
End Function
Once you've added that to a VBA module, you can use it as a function in any cell in your spreadsheet.
*Edit - Apparently there is a way if you have Excel 2013, as teylyn suggests. If you use 2010 or earlier like me, you'll need VBA.
Try this:
=LEN(SUBSTITUTE(F16,"+","+"))
Note: F16 is only an example name for the cell you want to do the counting on.
Example:
F16=45+65+76 # Gives 186
F17=LEN(SUBSTITUTE(F16,"+","+")) # Gives 3

Resources