Weird Math Error in VBA for Excel
Hi all, would love feedback on unsusual error I'm getting.
Very strange. I have a simple formula that works great if I
only use it in a normal sheet cell and copy it down by columns,
but if I try to do a simple iteration in vba code to perform the same function I get the wrong values.
Description : A number is squared, then divided by another value
between 0.99 to 1.99, next the modulus is taken and then
the number is squared again and the whole formula repeated.
If I copy the formula statement down column wise it calcs fine,
including reasonable decimal accuracy.
There are four inputs ;
base value (inputx)
decx = divisor
mod value
The first formula placed at (E2) looks like ; =MOD(((B2^2)/$B$3),$B$4)
In (E3) this statement is placed ; =MOD(((E2^2)/$B$3),$B$4)
Then this exact same statement is copied down, columnwise to the next 98 cells.
All great, no problem. It seems accurate value wise, right to decimal
precision, with values past the decimal point showing in all column cells.
Some sample input values for testing ;
INPUTX --> 231
DECX 1.010101
MOD 400
LOOPTIMES 100
But when I try to implement this is Excel VBA code (Excel 2007)
I often get the wrong values and absolutely no values past the decimal point ever show.
Have tried using all kinds of different data types ; single, double, variant, etc... but all values returned by the VBA function I made always returns
whole numbers, and is often wrong and certainly does not agree with the
values returned by the simple column based statements.
Have tried to find ways around this or to fix this, came across "CDEC", tried
this and nothing changed. Totally stumped and would love some insight into
if this can be fixed so that the function loop returns the same values with
same kind of decimal precision as the column based statements and
would greatly appreciate feedback on how mthis can be done.
Am including my sample code below ;
Public Function SQRD(inputx As Variant, looptime As Variant, decx As Variant) As Variant
Application.Volatile
Dim Count As Integer
SQRD = CDec(inputx)
'Dim decx As variant
Count = 1
For Count = 1 To looptime
SQRD = CDec(SQRD ^ 2) '+ looptime
SQRD = CDec(SQRD Mod 400 / decx)
Next Count
End Function
I will only address your use of the VBA Mod operator. It is NOT equivalent to the Excel MOD function. In particular, the VBA Mod operator will round floating point numbers to integers before performing the operation.
To use a VBA function that mimics the Excel MOD function, use something like:
Function xlMOD(a As Double, b As Double) As Double
xlMOD = a - (b * (a \ b))
End Function
EDIT
There seems to be a bug in VBA (or a documentation error). In the formula above, the \ operator is supposed to be the integer division operator. It does return an integer result. However, it does not truncate, rather it rounds. Actually, what it may be doing, is performing VBA type rounding on the number and divisor, before returning the result.
Therefore, a proper vba function to mimic the Excel MOD function would be:
Function xlMOD(a As Double, b As Double) As Double
xlMOD = a - Int(a / b) * b
End Function
Lots amiss with your code. No need for looping as far as I can see, and you're dividing after the mod not before
This seems to do the trick
Public Function NuFunc(InputX As Variant, DecX As Variant) As Variant
NuFunc = ((InputX ^ 2) / DecX) Mod 400
End Function
Related
As suggested by the title, I have run into some trouble trying to implement a variant of the standard deviation function into excel through VBA. I have defined two columns, one being the set of values (Val) under investigation and one being a multiplier column (Multi) that shows how often the value occurs. The idea was that this would save me some time/space as I would not have to repeat the values y number of times. Through the use of the SUMPRODUCT function, I managed to find the mean of the values (taking into account their multipliers). This value is also used as an input (Mn) for the function. What I want now is to also find the standard deviation of my values, again taking into account the multiplier. I have no experience in VBA, so I sort of scrambled together this code, but it is currently not working. Any help would be greatly appreciated!
Function SWSTDEV(Multi As Variant, Val As Variant, Mn As Double)
Dim x As Long
Dim Results
For Each MultiEl In Multi
For Each ValEl In Val
x = x + 1
Results(x) = MultiEl * (ValEl - Mn) ^ 2
Next ValEl
Next MultiEl
SWSTDEV = WorksheetFunction.SQRT(WorksheetFunction.Sum(Results) / WorksheetFunction.Sum(Multi))
End Function
I'm working on a research related to frequencies.
I want to sum all the numbers in each cell and reduce them to single number only.
some cells have 2 numbers, others have 13 numbers. like these..
24.0542653897891
25.4846064424057
27
28.6055035477009
I tried several formulas to do that. the best ones have me 2 digits number, that I couldn't sum it again to get a single result.
like these Formulas:
=SUMPRODUCT(MID(SUBSTITUTE(B5,".",""),ROW(INDIRECT("1:"&LEN(B5)-1)),1)+0)
=SUMPRODUCT(1*MID(C5,ROW(INDIRECT("1:"&LEN(C5))),1))
any suggestion?
Thank you in advance.
EDIT
Based on your explanation your comments, it seems that what you want is what is called the digital root of the all the digits (excluding the decimal point). In other words, repeatedly summing the digits until you get to a single digit.
That can be calculated by a simpler formula than adding up the digits.
=1+(SUBSTITUTE(B5,".","")-1)-(INT((SUBSTITUTE(B5,".","")-1)/9)*9)
For long numbers, we can split the number in half and process each half. eg:
=1+MOD(1+MOD(LEFT(SUBSTITUTE(B5,".",""),INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)+1+MOD(RIGHT(SUBSTITUTE(B5,".",""),LEN(SUBSTITUTE(B5,".",""))-INT(LEN(SUBSTITUTE(B5,".",""))/2))-1,9)-1,9)
However, the numbers should be stored as TEXT. When numbers are stored as numbers, what we see may not necessarily be what is stored there, and what the formula (as well as the UDF) will process.
The long formula version will correct all the errors on your worksheet EXCEPT for B104. B104 appears to have the value 5226.9332653096000 but Excel is really storing the value 5226.9333265309688. Because of Excel's precision limitations, this will get processed as 5226.93332653097. Hence there will be a disagreement.
Another method that should work would be to round all of the results in your column B to 15 digits (eg: . Combining that with using the long formula version should result in agreement for all the values you show.
Explanation
if a number is divisible by 9, its digital root will be 9, otherwise, the digital root will be n MOD 9
The general formula would be: =1+Mod(n-1,9)
In your case, since we are dealing with numbers larger than can be calculated using the MOD function, we need to both remove the dot, and also use the equivalent of mod which is n-(int(n/9)*9)
Notes:
this will work best with numbers stored as text. Since Excel may display and/or convert large numbers, or numbers with many decimal places, differently than expected, working with text strings of digits is the most stable method.
this method will not work reliably with numbers > 15 digits.
If you have numbers > 15 digits, then I suggest a VBA User Defined Function:
Option Explicit
Function digitalRoot(num As String) As Long
Dim S As String, Sum As Long, I As Long
S = num
Do While Len(S) > 1
Sum = 0
For I = 1 To Len(S)
Sum = Sum + Val(Mid(S, I, 1))
Next I
S = Trim(Str(Sum))
Loop
digitalRoot = CLng(S)
End Function
You could use a formula like:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(A1," ","</s><s>")&"</s></t>","//s"))
You might need an extra SUBSTITUTE for changing . to , if that's your decimal delimiter:
=SUMPRODUCT(FILTERXML("<t><s>"&SUBSTITUTE(SUBSTITUTE(A1,".",",")," ","</s><s>")&"</s></t>","//s"))
However, maybe a UDF as others proposed is also a possibility for you. Though, something tells me I might have misinterpreted your question...
I hope you are looking for something like following UDF.
Function SumToOneDigit(myNumber)
Dim temp: temp = 0
CalcLoop:
For i = 1 To Len(myNumber)
If IsNumeric(Mid(myNumber, i, 1)) Then temp = temp + Mid(myNumber, i, 1)
Next
If Len(temp) > 1 Then
myNumber = temp
temp = 0
GoTo CalcLoop
End If
SumToOneDigit = temp
End Function
UDF (User Defined Functions) are codes in VBA (visual basic for applications).
When you can not make calculations with Given Excel functions like ones in your question, you can UDFs in VBA module in Excel. See this link for UDF .. If you dont have developer tab see this link ,, Add a module in VBA in by right clicking on the workbook and paste the above code in that module. Remember, this code remains in this workbook only. So, if you want to use this UDF in some other file your will have to add module in that file and paste the code in there as well. If you are frequently using such an UDF, better to make add-in out of it like this link
In addition to using "Text to Columns" as a one-off conversion, this is relatively easy to do in VBA, by creating a user function that accepts the data as a string, splits it into an array separated by spaces, and then loops the elements to add them up.
Add the following VBA code to a new module:
Function fSumData(strData As String) As Double
On Error GoTo E_Handle
Dim aData() As String
Dim lngLoop1 As Long
aData = Split(strData, " ")
For lngLoop1 = LBound(aData) To UBound(aData)
fSumData = fSumData + CDbl(aData(lngLoop1))
Next lngLoop1
fExit:
On Error Resume Next
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fSumData", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Then enter this into a cell in the Excel worksheet:
=fSumData(A1)
Regards,
The UDF below will return the sum of all numbers in a cell passed to it as an argument.
Function SumCell(Cell As Range) As Double
Dim Fun As Double ' function return value
Dim Sp() As String ' helper array
Dim i As Integer ' index to helper array
Sp = Split(Cell.Cells(1).Value)
For i = 0 To UBound(Sp)
Fun = Fun + Val(Sp(i))
Next i
SumCell = Fun
End Function
Install the function in a standard code module, created with a name like Module1. Call it from the worksheet with syntax like =SumCell(A2) where A2 is the cell that contains the numbers to be summed up. Copy down as you would a built-in function.
So what I am trying to do is take the difference between two values(x) and (y) to get (n). I then want to run (x) through a formula (n) times incrementing (x) each time. Then I want to output the sum all of those results into a cell. Cant figure out how to do it neatly within one cell like normal, so I've turned to VBA for once.
Here is what I have so far:
Sub Math()
'
'Math
'
Dim i As Integer
i = 0
Do While i < ((E42) - (d42))
cell(h42).Value = ((((d42) + i) ^ 2) * 100) / 3
End Sub
What I'm stuck on is how to get the result of each loop and sum them all together. I expect to have an i value that can range anywhere from 1-100. The only way I can think that would definitely work is messy where i would have a large number of cells in a column set aside that will calculate each of the iterations individually, then sum all of those together.
Alternatively, if theres a way to write a function that can calculate the sum(n) of ((x+n)^2)*100/3 then that would be much nicer but I cant think of how it would be written.
Here is how you can make a function (which can be used directly in worksheet formulas) to form a sum:
Function eval_sum(n As Long, x As Double) As Double
Dim s As Double, i As Long
For i = 0 To n - 1
s = s + (x + i) ^ 2
Next i
eval_sum = s * 100 / 3
End Function
This function evaluates:
100/3 * (x^2 + (x+1)^2 + (x+2)^2 + ... + (x+(n-1))^2)
It wasn't completely clear if this is what you were trying to do. If not, you can tweak the code to fit your needs.
Small point: I used Long rather than Integer. Forget that Integer exists. It is really legacy from the days of limited memory and 16-bit computers. In modern Excel an Integer is stored with 32 bits anyway. You are just risking overflow for no good reason.
I have a total data set that is for 4 different groupings. One of the values is the average time, the other is count. For the Total I have to multiply these and then divide by the total of the count. Currently I use:
=SUM(D32*D2,D94*D64,D156*D126,D218*D188)/SUM(D32,D94,D156,D218)
I would rather use a SumProduct if I can to make it more readable. I tried to do:
=SUMPRODUCT((D2,D64,D126,D188),(D32,D94,D156,D218))/SUM(D32,94,D156,D218)
But as you can tell by my posting here, that did not work. Is there a way to do SumProduct like I want?
I agree with the comment "It might be possible with masterful excel-fu, but even if it can be done, it's not likely to be more readable than your original solution"
A possible solution is to embed the CHOOSE() function within your SUMPRODUCT (this trick actually is pretty handy for vlookups, finding conditional maximums, etc.).
Example:
Let's say your data has eight observations and is in two columns (columns B and C) but you don't want to include some observations (exclude observations in rows 4 and 5). Then the SUMPRODUCT code looks like this...
=SUMPRODUCT(CHOOSE({1,2},A1:A3,A6:A8),CHOOSE({1,2},B1:B3,B6:B8))
I actually thought of this on the fly, so I don't know the limitations and as you can see it is not that pretty.
Hope this helps! :)
It might be possible with masterful excel-fu, but even if it can be done, it's not likely to be more readable than your original solution. The problem is that even after 20+ years, Excel still borks discontinuous ranges. Naming them won't work, array formulas won't work and as you see with SUMPRODUCT, they don't generally work in tuple-wise array functions. Your best bet here is to come up with a custom function.
UPDATE
You're question got me thinking about how to handle discontinuous ranges. It's not something I've had to deal with much in the past. I didn't have the time to give a better answer when you asked the question but now that I've got a few minutes, I've whipped up a custom function that will do what you want:
Function gvSUMPRODUCT(ParamArray rng() As Variant)
Dim sumProd As Integer
Dim valuesIndex As Integer
Dim values() As Double
For Each r In rng()
For Each c In r.Cells
On Error GoTo VBAIsSuchAPainInTheAssSometimes
valuesIndex = UBound(values) + 1
On Error GoTo 0
ReDim Preserve values(valuesIndex)
values(valuesIndex) = c.Value
Next c
Next r
If valuesIndex Mod 2 = 1 Then
For i = 0 To (valuesIndex - 1) / 2
sumProd = sumProd + values(i) * values(i + (valuesIndex + 1) / 2)
Next i
gvSUMPRODUCT = sumProd
Exit Function
Else
gvSUMPRODUCT = CVErr(xlErrValue)
Exit Function
End If
VBAIsSuchAPainInTheAssSometimes:
valuesIndex = 0
Resume Next
End Function
Some notes:
Excel enumerates ranges by column then row so if you have a continuous range where the data is organized by column, you have to select separate ranges: gvSUMPRODUCT(A1:A10,B1:B10) and not gvSUMPRODUCT(A1:B10).
The function works by pairwise multiplying the first half of cells with the second and then summing those products: gvSUMPRODUCT(A1,C3,L2,B2,G5,F4) = A1*B2 + C3*G5 + L2*F4. I.e. order matters.
You could extend the function to include n-wise multiplication by doing something like gvNSUMPRODUCT(n,ranges).
If there are an odd number of cells (not ranges), it returns the #VALUE error.
Note that sumproduct(a, b) = sumproduct(a1, b1) + sumproduct(a2, b2) where range a is split into ranges a1 and a2 (and similar for b)
It might be helpful to create an intermediate table that summarizes the data that you are using to calculate the sum product. That would also make the calculation easier to follow.
On the Daily Dose of Excel website, written by the late Frank Kabel, there are some formulae which can stand in for ATP functions. Not being an Excel guru, I'm struggling with converting one (so far!) to VB6. (Why I'm doing this I may relate once the NDA runs out.)
The problem I'm having is with the code standing in for SERIESSUM, namely,
=SUMPRODUCT(coefficients,x^(n+m*(ROW(INDIRECT("1:"&ROWS(coefficients)))-1)))
Now the SUMPRODUCT and ROWS functions I've been able to render fairly simply with
Public Function SUMPRODUCT(a1 As Variant, a2 As Variant) As Double
Dim dRes As Double
Dim dVal As Double
Dim i As Long
If LBound(a1) = LBound(a2) And UBound(a1) = UBound(a2) Then
For i = LBound(a1) To UBound(a1)
dVal = a1(i) * a2(i)
dRes = dRes + dVal
Next
End If
SUMPRODUCT = dRes
End Function
Public Function ROWS(a1 As Variant)
ROWS = UBound(a1) - LBound(a1) + 1
End Function
What I don't 'get' yet is
how x^(n+m*(ROW(INDIRECT("1:"&ROWS(coefficients)))-1)) evaluates to an array
and what that array might contain
Any Excel gurus out there?
ROW(INDIRECT("1:"&ROWS(coefficients)))-1
If coefficients has 5 rows, this will return the array {1,2,3,4,5}. The rest of the progression is
{1m, 2m, 3m, 4m, 5m)
{n+1m, n+2m, n+3m, n+4m, n+5m)
{x^n+1m, x^n+2m, x^n+3m, x^n+4m, x^n+5m)
That resulting array gets 'series summed' against coeffecients.
You can see the progression in Excel's formula bar by using Ctrl+= on highlighted parts of the formulas. There is a limit on how many characters you can display in the formula bar, so if coefficients has a lot of rows, you may get the error "formula too long"
In the formula bar, select ROW(INDIRECT("1:"&ROWS(coefficients)))-1 and press Ctrl+=. Then select another portion of your formula, making sure you match opening and closing parentheses, and hit Ctrl+=. You can iterate this until you have the whole formula calculated. When you're done, be sure to ESCAPE out of the cell so you don't lose your original formula.
See also Episode 474 here.