Static 'function' in vba/excel? - vba

I just have a very simple question regarding vba.
I understand that a static variable in vba keeps its previous value rather then reseting it each time the function is called. However, I was wondering how could I have a "static function" rather than a variable. Let me clarify this with a simple code:
Function Alt(Soc1 As Double)
Static D As Integer
If Soc1 >= 40 Then
Alt = 0
ElseIf Soc1 <= 30 Then
Alt = 1
End If
End Function
Here I would like the variable-function Alt to keep its previous value so that I get a sort of "hysteresis" behaviour. However if I add:
Static Alt As Integer
I get an error when calling the function Alt().
Also if I add a line such as:Alt = D while D is decalred as static I get an alternating value of Alt (1,0,1,0,1,0 ..) instead of (1,1,1,1) if active or (0,0,0,0) if not.
How should I tackle this simple issue? Any help would be appreciated.
Thank you in advance!

If I've got you, not sure it does what you need. :)
Option Explicit
Public intReturnToHold As Double
Sub call_function()
Debug.Print Alt(2, intReturnToHold)
End Sub
Function Alt(Soc1 As Double, Optional ByRef intHoldAlt)
Static D As Integer
Alt = intReturnToHold
If Soc1 >= 40 Then
Alt = 0
ElseIf Soc1 <= 30 Then
Alt = 1
End If
intHoldAlt = Alt
End Function

Thank you Nathan for your answer. Altough I don't really understand your code I believe I get the idea of it but it did not work.
I have tried my previous idea again and for some reason it worked this time, here is the code;
Function Alt(Soc1 As Double)
Static D As Integer
If Soc1 >= 40 Then
D = 0
ElseIf Soc1 <= 30 Then
D = 1
End If
Alt = D
End Function
I have previously tired the EXACT same code and the results were as I said in my question "alternating" between 1 and 0 (when active) but now are constant 1 (when active).

I'm not 100% sure what you are asking, but if you want a function to compute just once and then retain its value, you could do something like this:
Function Alt(Soc1 As Double) As Long
Static D As Long
Static Called As Boolean
If Called Then
Alt = D
Exit Function
End If
If Soc1 >= 40 Then
D = 0
ElseIf Soc1 <= 30 Then
D = 1
End If
Called = True
Alt = D
End Function
On the other hand, if your goal is to have a function which, once it takes on the value 1 never resets to 0, something like this would work:
Function Alt(Soc1 As Double) As Long
Static D As Long
If D = 1 Then
Alt = D
Exit Function
End If
If Soc1 >= 40 Then
D = 0
ElseIf Soc1 <= 30 Then
D = 1
End If
Alt = D
End Function

Related

Problem with Bisection method on Visual Basic [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Here is my code for a bisection method. If I input 4 and 5 the program loops infinitely. There is a problem with it running.
Sub TheBisectionMethod1()
Dim a, b As Double 'Taking two variables, A and B
Console.Write(vbLf & "Input A: ")
a = Double.Parse(Console.ReadLine()) 'This is where user inputs and value stored for A
Console.Write(vbLf & "Input B: ")
b = Double.Parse(Console.ReadLine()) 'This is where user inputs and value stored for B
line1:
Dim c As Double
c = (a + b) / 2 'declearing variable c for the sum of half of the user entered values
If ((Math.Abs(func(c))) < 0.0001) Then 'in flow chart the value of C remians unchange so the program will not run, so it will run if i is >0.0001
Console.Write(vbLf & "C : {0}", c)
ElseIf (Math.Sign(func(c)) = Math.Sign(func(a))) Then
Console.WriteLine("Hello")
a = c
GoTo line1
ElseIf (Math.Sign(func(c)) <> Math.Sign(func(a))) Then
b = c
GoTo line1
End If
End Sub
Function func(x As Double) As Double
Dim y As Double
y = x ^ 2 - 2
Return y
End Function
Don't use a GoTo. There's no need. Also, remove the user interaction from the method that does the actual work. Read the data in one place, pass it to a method (usually a Function rather than a Sub) that does the work and returns a result, and then show the result to the user after the function ends.
That makes this question tricky, because the only result we see in the original code is writing "Hello" to the Console, and that's clearly just a debugging statement. What do you want this code to do? (I'm gonna assume you mean this)
Function Bisect(a as Double, b As Double) As Double
Dim c As Double = (a + b) / 2
Dim d As Double = func(c)
While Math.Abs(d) >= 0.0001
If Math.Sign(d) = Math.Sign(func(a)) Then
a = c
Else
b = c
End If
c = (a + b) / 2
d = func(c)
End While
Return c
End Function
Function func(x As Double) As Double
Return x ^ 2 - 2
End Function
And really, it should look like this:
Function Bisect(a as Double, b As Double, f As Function(Of Double, Double)) As Double
Dim c As Double = (a + b) / 2
Dim d As Double = f(c)
While Math.Abs(d) >= 0.0001
If Math.Sign(d) = Math.Sign(f(a)) Then
a = c
Else
b = c
End If
c = (a + b) / 2
d = f(c)
End While
Return c
End Function
and be called like this:
Bisect(a, b, Function(x) x ^ 2 - 2)
Also, the algorithm here is slightly off based the wikipedia article. This is more precise:
Function Bisect(a as Double, b As Double, f As Function(Of Double, Double)) As Double
Dim TOL As Double = 0.0001
Dim MaxSteps As Integer = 1000
Dim c As Double = (a + b) / 2
While Math.Abs(f(c)) >= TOL AndAlso (b-a)/2 >= TOL AndAlso MaxSteps > 0
If Math.Sign(f(c)) = Math.Sign(f(a)) Then
a = c
Else
b = c
End If
MaxSteps -= 1
c = (a + b) / 2
End While
If MaxSteps = 0 Then
Throw New ArgumentException("The bisection fails to converge within the allowed time for the supplied arguments.")
End If
Return c
End Function
I bring it up, because the complaint in the original question is this:
the program loops infinently[sic]
and one of the tenants of the algorithm is it's not guaranteed to converge, hence the step counter.
Finally, this looks to me like it might do well as a recursive function. Recursion can improve things here because we can rely on the call stack overflowing rather than needing to implement a step counter:
Function Bisect(a as Double, b As Double, f As Function(Of Double, Double)) As Double
Dim c As Double = (a + b) / 2
If Math.Abs(f(c)) < TOL OrElse (b-a)/2 < TOL Then Return c
If Math.Sign(f(c)) = Math.Sign(f(a)) Then
Return Bisect(c, b, f)
Else
Return Bisect(a, c, f)
End If
End Function
Of course, catching that StackOverflowException is a trick in itself, so you may still want that step counter. But I need to leave something for you to do yourself.
This also helps demonstrate part of why I recommend removing all user I/O from the Bisect() method. If this method were also responsible for talking to the end user, it would not be possible to even consider the recursive option, where the code is clearly far shorter and simpler than any of the others.

How to return 2 values and rounding them? Excel VBA

I am working on a code that should calculate simple foundations, and in order to do that I have to return 2 values with my function -preferably in two different columns.
Function FundacaoSimples(b, l, carga) As Variant
tensao = Sheets("Tabelas e Constantes").Range("tensao").Value
Dim area As Double
Dim Bs As Single
Dim Ls As Single
Dim Resultado(1 To 2) As String
If b = l Then
area = (1.1 * carga) / tensao
Bs = Sqr(area)
Ls = Bs
ElseIf b <> l Then
area = (1.1 * carga) / tensao
Bs = Sqr((2 * area) / 3)
Ls = (3 * Bs) / 2
End If
Resultado(1) = Round(Bs, 2)
Resultado(2) = Round(Ls, 2)
FundacaoSimples = (Resultado(1) & " / " & Resultado(2))
End Function
This rounding I am using it just to get a value rounded with 2 decimals, e.g: 2,73 to 2,75; 0,89 to 0,90.
I tried working with ActiveCells.Offset(0,1), but the statement isn't valid.
Is it possible to to just jump one column to the right?
You could use ActiveCell.Offset(0, 1).value = SomeValue, however - That's when writing a regular Sub. You're writing a Function / User Defined Function.
Within a UDF it is not possible to alter different cells.
However, a workaround is to have the UDF and when it's entered in a cell, you can then use the Worksheet_Change event to alter the cell next to the Target parameter of that event.
Edit:
Some sample code:
In a regular module:
Public Function MyUDF(param1 as integer, param2 as integer) as Integer
MyUDF = param1 + param2
End Function
In the Worksheet where you want the offset:
Private Sub Worksheet_Change(Byval Target as Range)
If Left(Target.Formula, 6) = "=MyUDF" Then
Target.Offset(0, 1).value = "somevalue at the offset cells"
End If
End Sub
In general, functions should not be writing values or accessing values from a spreadsheet. They should access their parameters and return result.
Try like this, an oversimplified version of what you need:
Option Explicit
Public Sub TestMe()
ActiveCell = FundacaoSimples(0)
ActiveCell.Offset(0, 1) = FundacaoSimples(1)
End Sub
Function FundacaoSimples() As Variant
ReDim varResult(1)
varResult(0) = 55
varResult(1) = 100
FundacaoSimples = varResult
End Function
Then you can edit the function a bit with your own parameters and use it further.

How to enable/disable button with a function

I have a problem with my university project
It's a little game, 6 buttons for each players and 2 players so 12 buttons
There is number in each buttons, if a player has his 6 buttons at 0, he can't play
I have try some Public Function and i'm actually working with a very simple one but i think this is not the problem
My function is here
And in my form, the problem is here, i've tried many things but i don't know how do to that ... I read my lesson and I'm searching on the internet, i have no idea ..
If possible is True you don't re-enable the button.
You can simplify things.
Public Function PeutJouer(ByVal joueur As Integer) As Boolean
Dim sum As Integer
Dim start As Integer = (joueur - 1) * 7
For i As Integer = start To start + 5
sum += tableau(i)
Next
Return sum <> 0
End Function
Then
Btn1P1.Enabled = PeutJouer(1)
Did you show all the relevant code? You are declaring Dim tableau(12) As Integer but the array is never filled with values. Probably tableau should be declared at the form level and not locally in this function. If you already have both, remove the local declaration, because it hides the one at form level. You also need to return the result from the function. I don't see this in your function.
Note that this
If x <> 0 Then
booleanVariable = True
Else
booleanVariable = True
End If
can be simplified to
booleanVariable = x <> 0
i.e., the condition is an expression yielding the Boolean result True or False already and you can use this value directly. When working with numeric values you don't write If x + y = 1 Then r = 1 Else If x + y = 2 Then r = 2 .... You simply write r = x + y.

Checking if a number has an integer cubic root

I am trying to check whether a given number is cuberoot or not in VBA.
The following code works only for 2 and 3 as answers, it does not work after that.
I am trying to figure out what is wrong in the code.
Sub cuberoot()
Dim n As Long, p As Long, x As Long, y As Long
x = InputBox("x= ")
If Iscube(x) Then
MsgBox ("Is cube")
Else
MsgBox ("No cube")
End If
End Sub
Private Function Iscube(a As Long) As Boolean
b = a ^ (1 / 3)
If b = Int(b) Then
Iscube = True
Else
Iscube = False
End If
End Function
Since you are passing in a Long I'll assume that you won't have a number bigger than roughly 2*10^9 so this should always work. It's a slight variation where you truncate the double and then compare to the two nearest integers to make sure you catch any rounding errors.
Edit: In VBA the truncating would always round so it's only neccessary to check the 3rd root value:
Public Function Iscube(a As Long) As Boolean
Dim b As Integer
b = CInt(a ^ (1# / 3#))
If (b ^ 3 = a) Then
Iscube = True
Else
Iscube = False
End If
End Function
If you need a number larger than a Long you'll need to change your input type and you might want to consider an iterative method like a binary search or a Newton-Raphson solver instead.
Existing Code
Your code will work if you add a
dim b as long
If you debug your code you will see that feeding in 125 gives you
b = 5
Int(b) = 4
Updated Code
You can shorten your boolean test to this
Function Iscube(lngIn As Long) As Boolean
Iscube = (Val(lngIn ^ (1 / 3)) = Int(Val(lngIn ^ (1 / 3))))
End Function
Note that if you call it with a double, it will opearte on the long portion only (so it would see IsCube(64.01)as IsCube(64))

vb.net loop through and assign values if statement is true

i am working in vb.net and trying to organise what i'm calling a meal/entree break .
the code i have looks right to me but it just isn't working.
it is supposed to find entree's then mains then all else and print in that order but
it prints the results in order of clicked instead of in the order i've defined.
to keep in mind, i am using a reference to a vb6 code "powerpack" to use old school print codes that i'm more comfortable.
i have assigned some public shared variables:
Public Shared item(25) As String
Public Shared bar(25) As String
Public Shared price(25) As Integer
Public Shared barprice(25) As Integer
Public Shared EntreeBreak(25) As Boolean
Public Shared MainBreak(25) As Boolean
Public Shared Print_Items(25) As String
Public Shared Print_price(25) As Integer
i then have created a global class that i call public shared subs from, the sub i am calling is
Dim i2 As Integer
i = 1
i2 = 1
Do Until i = 26
If item(i) <> "" Then
If EntreeBreak(i) = True And i2 < 26 Then
Print_Items(i2) = item(i)
Print_price(i2) = price(i)
i2 = i2 + 1
ElseIf MainBreak(i) = True And i2 < 26 Then
Print_Items(i2) = item(i)
Print_price(i2) = price(i)
i2 = i2 + 1
ElseIf EntreeBreak(i) = False And MainBreak(i) = False And i2 < 26 Then
Print_Items(i2) = item(i)
Print_price(i2) = price(i)
i2 = i2 + 1
End If
End If
i = i + 1
Loop
i just can't find what i'm doing wrong
it is supposed to find entree's then mains then all else but
it prints the results in order of clicked instead of in the order i've defined.
You are just looping through item in order from 1 to 26
I am assuming item has a mix of entrees, mains and all else, so perhaps what you need to do is loop through the item three times, firstly extracting the entrees, then the mains, then all else.
Having said that i would encourage you to use a better data structure, possible an ordered dictionary and meal class/structure or something. and as said above you should be looping from 0 to 25, or if it is a dictionary or a list , just a for each will do.