Finding Max of 3 Inputs VBA - excel

I am trying to find the maximum of 3 inputs. The problem is not in the algorithm, as when I made the same script in python it worked just fine. The problem is that it does not work as expected. I will write some scenarios and what the outcome was:
8 5 12 - Max: 12
5 8 12 - Max: 12
12 5 8 - Max: 8
12 8 5 - Max: 8
5 12 8 - Max: 8
8 12 5 - Max: 8
100 22 33 - Max: 33
22 3 100 - Max: 100
100 22 3 - Max: 22
It seems that it works for quite some combination, but not for each and every one. I haven't managed to find a pattern yet, and I can't figure out what is going wrong.
I am attaching the code:
Sub Maxthree()
'Calculates the maximum of three numbers'
Dim x, y, z As Single
x = InputBox("Enter the first number!")
y = InputBox("Enter the second number!")
z = InputBox("Enter the third number!")
MsgBox ("X: " & x & " Y: " & y & " Z: " & z)
If x > y Then
If x > z Then
MsgBox ("the maximum is : " & x)
Else
MsgBox ("the maximum is : " & z)
End If
Else
If y > z Then
MsgBox ("the maximum is : " & y)
Else
MsgBox ("the maximum is : " & z)
End If
End If
End Sub

Because they are input using an InputBox, it's comparing text values. So, for example "8" is greater than "12". Instead try converting to Longs like:
x = CLng(InputBox("Enter the first number!"))
You can also simplify your code to:
MsgBox WorksheetFunction.Max(x, y, z)

Here is the pattern you were looking for.
Since X and Y are Variant while Z is Single, this is how VBA will perform the comparisons:
X vs Y: string vs string (this is what is causing all the trouble)
X vs Z: numeric (X will be converted automatically)
Y vs Z: numeric (Y will be converted automatically)
Re-evaluate all 9 of your scenarios, with X and Y being compared as strings and (X or Y) being compared to Z as numbers. The results you observed, while unexpected, are correct.
Just feel fortunate that you aren't programming in PHP, where this is all much worse!
Microsoft are to blame for allowing Variant to be the default data type if no other type is specified. They support "Option Explicit" to force variables to be declared. They should go a step further, and have an option to require data types in all declarations.

Here is a function that returns the Largest element of any number of them:
Function Largest(ParamArray a() As Variant) As Variant
'returns the largest element of list
'List is supposed to be consistent: all nummbers or all strings
'e.g: largest(2,6,-9,7,3) -> 7
' largest("d", "z", "c", "x") -> "z"
'by Patrick Honorez --- www.idevlop.com
Dim result As Variant
Dim i As Integer
result = Null
For i = LBound(a) To UBound(a)
If result > a(i) Then
'nothing to do. This construct will properly handle null values
Else
result = a(i)
End If
Next i
Largest = result
End Function

Related

Find value in column where running total is equal to a certain percentage

In Excel I have a list of values (in random order), where I want to figure out which values that comprise 75% of the total value; i.e. if adding the largest values together, which ones should I include in order to get to 75% of the total (largest to smallest). I would like to find the "cut-off value", i.e. the smallest number to include in the group of values (that combined sum up to 75%). However I want to do this without first sorting my data.
Consider below example, here we can see that the cutoff is at "Company 6", which corresponds to a "cut-off value" of 750.
The data I have is not sorted, hence I just want to figure out what the "cut-off value" should be, because then I know that if the amount in the row is above that number, it is part of group of values that constitute 75% of the total.
The answer can be either Excel or VBA; but I want to avoid having to sort my table first, and I want to avoid having a calculation in each row (so ideally a single formula that can calculate it).
Row number
Amount
Percentage
Running Total
Company 1
1,000
12.9%
12.9%
Company 2
950
12.3%
25.2%
Company 3
900
11.6%
36.8%
Company 4
850
11.0%
47.7%
Company 5
800
10.3%
58.1%
Company 6
750
9.7%
67.7%
Company 7
700
9.0%
76.8%
Company 8
650
8.4%
85.2%
Company 9
600
7.7%
92.9%
Company 10
550
7.1%
100.0%
Total
7,750
75% of total
5,813
EDIT:
My initial thought was to use percentile/quartile function, however that is not giving me the expected results.
I have been trying to use a combination of percentrank, sort, sum and aggregate - but cannot figure out how to combine them, to get the result I need.
In the example I want to include Companies 1 through 6, as that summarize to 5250, hence the smallest number to include is 750. If I add Company 7 I get above the 5813 (which is where 75% is).
VBA bubble sort - no changes to sheet.
Option Explicit
Sub calc75()
Const PCENT = 0.75
Dim rng, ar, ix, x As Long, z As Long, cutoff As Double
Dim n As Long, i As Long, a As Long, b As Long
Dim t As Double, msg As String, prev As Long, bFlag As Boolean
' company and amount
Set rng = Sheet1.Range("A2:B11")
ar = rng.Value2
n = UBound(ar)
' calc cutoff
ReDim ix(1 To n)
For i = 1 To n
ix(i) = i
cutoff = cutoff + ar(i, 2) * PCENT
Next
' bubble sort
For a = 1 To n - 1
For b = a + 1 To n
' compare col B
If ar(ix(b), 2) > ar(ix(a), 2) Then
z = ix(a)
ix(a) = ix(b)
ix(b) = z
End If
Next
Next
' result
x = 1
For i = 1 To n
t = t + ar(ix(i), 2)
If t > cutoff And Not bFlag Then
msg = msg & vbLf & String(30, "-")
bFlag = True
If i > 1 Then x = i - 1
End If
msg = msg & vbLf & i & ") " & ar(ix(i), 1) _
& Format(ar(ix(i), 2), " 0") _
& Format(t, " 0")
Next
MsgBox msg, vbInformation, ar(x, 1) & " Cutoff=" & cutoff
End Sub
So, set this up simply as I suggested.
You can add or change the constraints as you wish to get the results you need - I chose Binary to start but you could limit to integer and to 1, 2 or 3 for example.
I included the roundup() I used as well as the sumproduct.
I used Binary as that gives a clear indication of the ones chosen, integer values will also do the same of course.
Smallest Value of a Running Total...
=LET(Data,B2:B11,Ratio,0.75,
Sorted,SORT(Data,,-1),MaxSum,SUM(Sorted)*Ratio,
Scanned,SCAN(0,Sorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
srIndex,XMATCH(0,Scanned)-1,
Result,INDEX(Sorted,srIndex),Result)
G2: =SORT(B2:B11,,-1)
H2: =SUM(B2:B11)*0.75
I2: =SCAN(0,G2#,LAMBDA(a,b,IF((a+b)<$H$2,a+b,0)))
J2: =XMATCH(0,I2#)
K2: =INDEX(G2#,XMATCH(0,I2#)-1)
The issue that presents itself is that there could be duplicates in the Amount column when it wouldn't be possible to determine which of them is the correct result.
If the company names are unique, an accurate way would be to return the company name.
=LET(rData,A2:A11,lData,B2:B11,Ratio,0.75,
Data,HSTACK(rData,lData),Sorted,SORT(Data,2,-1),
lSorted,TAKE(Sorted,,-1),MaxSum,SUM(lSorted)*Ratio,
Scanned,SCAN(0,lSorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
rSorted,TAKE(Sorted,,1),rIndex,XMATCH(0,Scanned)-1,
Result,INDEX(rSorted,rIndex),Result)
Note that you can define a name, e.g. GetCutOffCompany with the following part of the LAMBDA version of the formula:
=LAMBDA(rData,lData,Ratio,LET(
Data,HSTACK(rData,lData),Sorted,SORT(Data,2,-1),
lSorted,TAKE(Sorted,,-1),MaxSum,SUM(lSorted)*Ratio,
Scanned,SCAN(0,lSorted,LAMBDA(a,b,IF((a+b)<=MaxSum,a+b,0))),
rSorted,TAKE(Sorted,,1),rIndex,XMATCH(0,Scanned)-1,
Result,INDEX(rSorted,rIndex),Result))
Then you can use the name like any other Excel function anywhere in the workbook e.g.:
=GetCutOffCompany(A2:A11,B2:B11,0.75)

How to compare 2 integers in terms of how many times 1st Integer is greater than second

I am looking to find a formula in excel by which I can calculate how many times 1st integer is greater than second.
For Example:
A = 5
B = 1
Ans: (A/B) i.e 5/1 = 5
A is 5 times bigger than B. Simple Enough!!
Problem occurs when one integer is negative,
Example:
A = 10
B = -5
How do I calculate in excel, how much time A is bigger or smaller than B.
Condition: Any Integer could be negative (A or B)
Thank You!!
What I tried:
A = 10
B = -5
C = A / B
C = 10 / -5
C = -2 which is incorrect :(**
Maybe try:
Formula in A4:
=(MAX(A1:A2)-MIN(A1:A2))/MIN(ABS(A1:A2))+(OR(MIN(A1:A2)>0,MAX(A1:A2)<0))
Thank You# JvdV and Notus_panda: This works perfectly!!
=(MAX(B1:B2)-MIN(B1:B2))/MIN(ABS(B1:B2))+(OR(MIN(B1:B2)>0,MAX(B1:B2)<0))
="A is " & (MAX(B1:B2)-MIN(B1:B2))/MIN(ABS(B1:B2))+(OR(MIN(B1:B2)>0,MAX(B1:B2)<0)) & " times " & IF(B1<B2,"smaller","bigger") & " than B"

Shortcut criteria check with adding decimal as binary

good day,
I've made a program that has several prerequisites before proceeding and have written a scoring system to tally if all criteria are met without needing several if statements.
In addition i continue to write if statements if criteria are not met to alert the issue but this too is getting crazy with if a or b or c or d . . . statements is there a quicker mathematical way to summaries this? In some cases i have 6 or 7 prerequisites, isnt there a way i can count remainder or something of decimal to binary?
here is a dummy example of what i mean
dim points as integer
points = 0
if len(range("A1") ) = 6 then points = points + 1 'example checks length
if Cdbl(range("B1") ) >= Cdbl(Date) then points = points + 2 'example checks if date is in future
if not isempty(range("C1")) then points = points + 4 'example checks field is not blank
if points = 7 then
[do some code]
msgbox "criteria met"
else
' this is what I want summarized with 1 nice mathematical equation
if points = 1 or points = 3 or points = 5 or points = 7 then
msgbox "problem is A1"
end if
if points = 2 or points = 3 or points = 6 or points = 7 then
msgbox "problem is B1"
end if
if points = 4 or points = 5 or points = 6 or points = 7 then
msgbox "problem is C1"
end if
end if
Yes. You to just need to use And. points And 1 will return 1 (which will be equivalent to true if used that way) if points is odd.
points And 6 will return 6 if the the number in binary ends in 110 or 111.

For Loop Counter in VBA increments by one extra

I have a simple loop counter which I expected to msg me 1,2,3...10. Instead I get 1,3,5,7,9and I can't figure out where this extra increment is coming from. I'm a bit rusty after layoffs so bear with me if this seems simple.
here is my code:
Dim x As Integer
For x = 1 To 10
MsgBox x
x = x + 1
Next x
It is typical that a for loop in most programming languages will automatically increment or iterate through something. In most languages, it's also common to provide a way to specify the increment amount. This is the case in VBA—see VBA For...Next documentation. For example:
For i = 1 to 20 Step 2
'do something
next i
Your code is incrementing x, and For is also incrementing x. There are times when you want to increment the counter within the loop if some special condition is met, but not on every loop.
If you want to manually increment, then you should use a while loop instead.
The Next x line, when referring to an integer (x) in a For loop, will increment x by 1
If you would like to increment by more than one, or increment in a negative direction you could use step at the end of the For line, which provides direction to the Next portion. Example:
For x = 10 to 0 step -1
msgbox x
next x
will result in 11 consecutive msgbox's displaying:
10
9
8
7
6
5
4
3
2
1
0
Depending on how you would like to control X you have different options:
Option 1:
Let the for-loop control everything and use "step".
In this case x will raise by y at each iteration. And you should not alter x within the loop.
Dim y As Integer
y = 1
For x = 1 To 10 step y
MsgBox x
Next x
Option 2:
If X depends on something happening within the loop and you don't want the for loop to alter x you could set step to 0.
For x = 1 To 10 step 0
MsgBox x
If x Mod 2 = 0 Then
x = x + 3
Else
x = x - 1
End If
Next x
Alternativ option
Having a for loop as in option 2 not altering the x would be bad practice. It is not wrong as the code is working just fine but a while loop would be a better solution.
Dim x As Integer
x = 1
Do While x < 10
MsgBox x
If x Mod 2 = 0 Then
x = x + 3
Else
x = x - 1
End If
Loop

Monte Carlo simulation for tossing a coin to get a certain pattern

Inspired by this article: Statistics of Coin-Toss Patterns, I have conducted a Monte Carlo simulation for determining the expected number of tossing a coin to get a certain pattern by using Excel VBA. The following code is the Monte Carlo simulation for tossing a fair coin to get pattern HTH, where H is head (1) and T is tail (0).
Sub Tossing_Coin()
Dim Toss(1000000) As Double, NToss(1000000) As Double, AVToss(1000000) As Double
t0 = Timer
Sheet2.Cells.Clear
a = 0
For j = 1 To 1000000
p1 = Rnd()
If p1 <= 0.5 Then
Toss(1) = 1
Else
Toss(1) = 0
End If
p2 = Rnd()
If p2 <= 0.5 Then
Toss(2) = 1
Else
Toss(2) = 0
End If
i = 2
Do
p3 = Rnd()
If p3 <= 0.5 Then
Toss(i + 1) = 1
Else
Toss(i + 1) = 0
End If
i = i + 1
Loop Until Toss(i - 2) = 1 And Toss(i - 1) = 0 And Toss(i) = 1
NToss(j) = i
a = a + NToss(j)
AVToss(j) = a / j
b = AVToss(j)
Next j
MsgBox "The expected number of tossing is " & b & "." _
& vbNewLine & "The running time of simulation is " & Round(Timer - t0, 2) & " s."
End Sub
The output of the program is as shown below:
which agrees with the result as shown in the article. Other patterns for tossing a fair coin are also match. Despite the results, I'm still feeling uncertain whether the program I wrote is correct or not. My doubt arises when the coin is unfair, meaning p1, p2, and p3 are not equal to 0.5, since I don't have any information to check its accuracy. I also want to know how to write an efficient program in VBA Excel or R to perform simulation above for a longer pattern like THTHTHTHT, THTTHHTHTTH, etc. and its looping is more than 1,000,000 (maybe 100,000,000 or 1,000,000,000) but still pretty fast? Any idea?
To make it more efficient, you could use the bits of a variable by assigning a bit with a toss. Then for each toss, rotate the bits on the left and add the toss result at the first position until the bits on the right are a match with the pattern :
pattern "HTH" : 101
mask for "XXX" : 111
1 toss "H" : 1 And 111 = 001
2 toss "T" : 10 And 111 = 010
3 toss "T" : 100 And 111 = 100
4 toss "H" : 1001 And 111 = 001
5 toss "H" : 10011 And 111 = 011
6 toss "T" : 100110 And 111 = 110
7 toss "H" : 1001101 And 111 = 101 : "HTH" matches the first 3 bits
Note that VBA doesn't have a bit shift operator, but shifting 1 bit on the left is the same as multiplying by 2 :
decimal 9 = 1001 in bits
9 + 9 = 18 = 10010 in bits
18 + 18 = 36 = 100100 in bits
Here is an example to get the average number of toss to match a sequence:
Sub UsageExample()
Const sequence = "HTH"
Const samples = 100000
MsgBox "Average: " & GetTossingAverage(sequence, samples)
End Sub
Function GetTossingAverage(sequence As String, samples As Long) As Double
Dim expected&, tosses&, mask&, tossCount#, i&
Randomize ' Initialize the random generator. '
' convert the [TH] sequence to a sequence of bits. Ex: HTH -> 00000101 '
For i = 1 To Len(sequence)
expected = expected + expected - (Mid$(sequence, i, 1) = "T")
Next
' generate the mask for the rotation of the bits. Ex: HTH -> 01110111 '
mask = (2 ^ (Len(sequence) * 2 + 1)) - (2 ^ Len(sequence)) - 1
' iterate the samples '
For i = 1 To samples
tosses = mask
' generate a new toss until we get the expected sequence '
Do
tossCount = tossCount + 1
' rotate the bits on the left and rand a new bit at position 1 '
tosses = (tosses + tosses - (Rnd < 0.5)) And mask
Loop Until tosses = expected
Next
GetTossingAverage = tossCount / samples
End Function
You'll need one string to store pattern you want to find.
Then after each toss append latest result onto end of results string.
Then check if last n digits of results string == pattern where n = length of pattern.
If match then record numbers of tosses and blank results string and go again ...
You could probably do it in about 20 lines of code! Hope that helps.

Resources