visual basic calculator without using decision structures or loops - visual-studio

1st year coding student and have a project in my visual basics class.
Create a simple calculator that will multiply, divide, subtract and add. Thing is we are not allowed to use any decision structures or loops, only sequential style coding structure.
I am struggling with storing, passing, and utilizing the selected mathematical operator later in the program.
When coding equations, what data type are operators considered?
For example in 4-1=3 is - considered a string?
How would one go about storing this value and passing it to another section of the program then converting that to a form you could plug into a formula, all without using a decision structure or loop?
for example:
1) User clicks on "1" button a string value of 1 is stored in a label as a string.
2) The user clicks the the "+" button and a + is stored (not sure what data type to use here for later needs) in a label.
3) Then user clicks "1" button a 1 is stored in a label as a string.
4) User clicks the "=" button.
5) The = button event handler executes code converting both the "1" strings to integer variables and the formula should represent IntResult= IntvariableOne + intVariableTwo. but since the operator may not always be + and no decision structure can be used. how can this be coded in a way that uses a variable to store the operator and complete the processing correctly in a formula?
In the most simple terms it would be equivalent to something like:
intResult= intvariableOne, Operatorvarible, intVariableTwo
Like I said I am new to coding so I apologize if this is a dumb question or completely wrong approach.
any info is appreciated.
thanks

It's a cheat, but try using Eval:
Eval("4-1") ' returns 3

Reading your post I was thinking about using the "CallByName" function.
But you can also try something more elegant with some kind of "oop" (reaching some of the limits offered by the old VB6) :
Create a file "ICalculator.cls" :
'
' Defines the "ICalculator" interface.
'
Option Explicit
Public Function CalcProcess(value1 As Integer, value2 As Integer)
End Function
Create a file named "CalculatorAdd.cls" that will take "additions" in charge:
'
' This "class" will implement "ICalculator" inteface to manage additions.
'
Option Explicit
Implements ICalculator
Private Function ICalculator_CalcProcess(value1 As Integer, value2 As Integer)
ICalculator_CalcProcess = value1 + value2
End Function
An then, an example of how it works :
Sub Test()
Dim value1 As Integer
Dim value2 As Integer
value1 = 1
value2 = 2
' Global object :
Dim objCalculator As ICalculator
...
' The object is set to "Addition" in the onclick event of the "add" button :
Set objCalculator = New CalculatorAdd
...
' The process is done in the onclick event of the "equal" button
value1 = objCalculator.CalcProcess(value1, value2)
...
' You can also have :
'Set objCalculator = New CalculatorSub
'value1 = objCalculator.CalcProcess(value1, value2)
'etc...
End Sub
You can create classes for each operation "add, sub, divide, multiply" and set your global variable objCalculator according to the button pressed by the user.
That's just a beginning, you'll have to put the whole logic of a calculator in place...

Related

Elegant way to pass as an optional parameter to make the subroutine work as if it was omitted?

In VB6, the function Mid(string, start, [length]) has an optional parameter length. If omitted, the whole characters after the start bound will be passed.
Say I want this default behaviour only in a certain condition:
s = Mid(s, i, IIf(condition, j, TheValue)) ' What could be TheValue?
Since length is of Variant type, I tried Empty. It didn't work. Neither did -1 and Nothing.
I didn't want to duplicate to Mid call in an If-Then-Else clause or somehow else. Is this possible?
Here is a working sample with OP's s = Mid(s, i, IIf(condition, j, TheValue)) line
Option Explicit
Property Get TheValue(Optional RetVal As Variant)
TheValue = RetVal
End Property
Private Sub Form_Load()
Dim s As String
Dim i As Long
Dim j As Long
Dim condition As Boolean
s = "test test test"
i = 6: j = 3
condition = False
s = Mid(s, i, IIf(condition, j, TheValue)) '<--- this works!
Debug.Print s
End Sub
Notice how TheValue returns a "missing" Variant i.e. one which tests positive for IsMissing and can be used in place of optional parameters instead of not passing actual argument.
No such value exists. When you omit the length parameter, the compiler chooses a different path through the VBRT -- it produces different code. If you want to emulate that, you need to do the same thing, using an If-Else or similar construct to handle the two cases, like #ÉtienneLaneville suggests
As an alternative to #Étienne's solution, VB provides the IsMissing method:
Public Function Mid(p_sString As String, p_iStart As Integer, Optional p_iLength As Integer) As String
If IsMissing(p_iLength) Then
Mid = VBA.Mid(p_sString, p_iStart)
Else
Mid = VBA.Mid(p_sString, p_iStart, p_iLength)
End If
End Function
And as this wrapper method returns a string, I suggest using the String verions of Mid, which is Mid$. The later is slightly faster than the Variant version (Mid)
This was nicely explained at this site, but at the time of this posting, the request times out. Not sure if gone forever or just a temporary problem.
You could define your own Mid function:
Public Function Mid(p_sString As String, p_iStart As Integer, Optional p_iLength As Integer = -1) As String
If p_iLength < 0 Then
Mid = VBA.Mid(p_sString, p_iStart)
Else
Mid = VBA.Mid(p_sString, p_iStart, p_iLength)
End If
End Function
This should work with the code from your question, using -1 (or any negative integer) as TheValue.
In c++, std::string these optional arguments are represented by either 0 when the default effect is zero position or length or std::string::npos when it is "infinite" length. You can explicitly supply that value and get the same behaviour.
I don't know what the equivalent constant is in m/s strings [In fact it is a different function definition, so there isn't one]. The alternative would be to pass in the string length, as that is the longest length currently possible.
The ?: ternary operator is an easy way to present 2 values with a condition to choose between them.

Invalid Control loop error with array

I have a fairly complex look of code where I am looking through multiple control variables.
I am getting an error 'Invalid 'for' loop control variable
the line in questions is
for w(1) = 32 to 127
I am more familiar with VBA where I would have zero problem with this statement.
I'm guessing it has something to do with the fact that i will be looping through w(1),w(2),w(3) etc. in the same tree. I initialize the variable as dim x(10) but have also tried dim w() , dim w() redim w(10)
Any thoughts? its a fairly critical aspect of the script; as such I am unwilling to swap out all my w 1,2... for individual variables
Thoughts?
EDIT:
As per comments I should clarify a Few things:
Essentially there is a alpha numeric association with an ID in a system that I am working with which I was not handed down the key too. So I have a multi-dimensional array of rates that are used for multiplying out costs.
What I am doing is working backwards through invoices and matching a material with very subtle differences that have different pricings.
For simplicity sake, say theres a 2 dimensional material where AA, AB, ... A9 are all priced through several multiplication factors in what would just be a 2x2 grid. So maintaining a pivot point based on the position in string is very important. For this code you could take tier to mean how many characters in the string (aka how complex the composition of the material):
dim x(), w()
for tier = 1 to 2
for w(1) = 32 to 127
x(1)= chr(w(1))
If tier = 2 then
for w(2)= 32 to 127
X(2)=chr(w(2))
next
end if
str = ""
for y = 1 to (tier)
str = trim(str & x(y))
next
'''msgbox str 'debug
next
end if
str = ""
for y = 1 to (tier)
str = trim(str & x(y))
next
'' msgbox str ' debug
next 'tier
This is just an excerpt i pulled to get a basic idea of the structure w/o any calculations. this is in essence what is not working
The error is quite clear, you cannot use an Array as the control variable. The definition in For...Next Statement is even clearer;
Numeric variable used as a loop counter. The variable cannot be an array element or an element of a user-defined type.
This is one of the key differences between VBA and VBScript.
You won't loop through x(1),x(2)...on what you write it's going like this 32(1),33(1)....what type it's your w(1) and how you define him?

asp classic FormatNumber bidding script

Afternoon,
Im playing around with a little bidding script im trying to write. But im having trouble with formatNumber function.
currentBid = 50.51 'from database dataType double(16,2)
yourBid = isNumeric(Request("bid"))
If FormatNumber(yourBid,2) > FormatNumber(currentBid,2) Then
Response.Write"bid successful... woop woop"
else
Response.Write"you cant bid below the current asking price"
end if
But if i was to bid 1000 is writes "you cant bid below the current asking price"
Please advise
Regards
Shane
'Changed as advised
currentBid = 50.51 'value from database
If IsNumeric(Request.Form("bid")) Then
yourBid = CDbl(Request.Form("bid"))
end if
You have two issues here:
As Ekkehard mentioned, IsNumeric() returns a boolean. To test if the value is numeric and then store to your variable, use:
If IsNumeric(Request("bid")) Then yourBid = CDbl(Request("bid"))
FormatNumber() returns a string representation of a number. So you're comparing one string against another instead of one number against another. If you need to round your numbers to two decimals, use the Round() function instead:
If Round(yourBid,2) > Round(currentBid,2) Then
Edit: Proof.
MsgBox VarType(4) ' 2 = vbInteger
MsgBox VarType(FormatNumber(4)) ' 8 = vbString
The line
yourBid = isNumeric(Request("bid"))
does not store a valid number into yourBid, but the (booelan) result of the IsNumeric() function applied to Request("bid").
Change the line to
yourBid = CDbl(Request("bid"))
and see if your IF statement works as expected. Then add a proper validation for Request("bid").

Visual Basic Function Procedure

I need help with the following H.W. problem. I have done everything except the instructions I numbered. Please help!
A furniture manufacturer makes two types of furniture—chairs and sofas.
The cost per chair is $350, the cost per sofa is $925, and the sales tax rate is 5%.
Write a Visual Basic program to create an invoice form for an order.
After the data on the left side of the form are entered, the user can display an invoice in a list box by pressing the Process Order button.
The user can click on the Clear Order Form button to clear all text boxes and the list box, and can click on the Quit button to exit the program.
The invoice number consists of the capitalized first two letters of the customer’s last name, followed by the last four digits of the zip code.
The customer name is input with the last name first, followed by a comma, a space, and the first name. However, the name is displayed in the invoice in the proper order.
The generation of the invoice number and the reordering of the first and last names should be carried out by Function procedures.
Seeing as this is homework and you haven't provided any code to show what effort you have made on your own, I'm not going to provide any specific answers, but hopefully I will try to point you in the right direction.
Your first 2 numbered items look to be variations on the same theme... string manipulation. Assuming you have the customer's address information from the order form, you just need to write 2 separate function to take the parts of the name and address, take the data you need and return the value (which covers your 3rd item).
To get parts of the name and address to generate the invoice number, you need to think about using the Left() and Right() functions.
Something like:
Dim first as String, last as String, word as String
word = "Foo"
first = Left(word, 1)
last = Right(word, 1)
Debug.Print(first) 'prints "F"
Debug.Print(last) 'prints "o"
Once you get the parts you need, then you just need to worry about joining the parts together in the order you want. The concatenation operator for strings is &. So using the above example, it would go something like:
Dim concat as String
concat = first & last
Debug.Print(concat) 'prints "Fo"
Your final item, using a Function procedure to generate the desired values, is very easily google-able (is that even a word). The syntax is very simple, so here's a quick example of a common function that is not built into VB6:
Private Function IsOdd(value as Integer) As Boolean
If (value Mod 2) = 0 Then 'determines of value is an odd or even by checking
' if the value divided by 2 has a remainder or not
' (aka Mod operator)
IsOdd = False ' if remainder is 0, set IsOdd to False
Else
IsOdd = True ' otherwise set IsOdd to True
End If
End Function
Hopefully this gets you going in the right direction.

Check if the worksheet is updated before running the macro in VBA

I am writing a macro where there is a central input sheet - let us call this sheet - "Main Input sheet" where user inputs the variables concerned. In the "Main Input sheet" there are some inputs say - "Any More Input sheets?" - which when "Yes", a worksheet corresponding to the input is displayed (it was previously hidden) - Lets call it "Associated Input sheet". Now, I want to ensure that the user updates "Associated Input sheet" before he runs the macro. Is there a way I can do this - using event handlers that VBA provides or using any other way?
The Worksheet_Change event procedure is probably the way to go, unless you've got other stuff happening elsewhere on the sheet that makes lots of changes.
At that point, the your question can be rephrased: 'Has my range changed since I checked last?'
Grabbing a copy of the range and storing it somewhere, and checking the current range against the cached copy, cell-by-cell, is a brute force approach: it's OK if you're only doing it once, but if you're doing it repeatedly it's more efficient to store a hash - a short code or number generated by some kind of checksum function.
Checksum algorithms vary. Adler32 is simple and quick, but it performs badly - you get 'Hash Collisions' or failures to return differing hashes for data inputs that differ - on comparisons of (say) a pair of single words of 6-10 letters. However, it performs very well indeed when asked to detect changes to a column of 24 8-letter words, or to a table of a few thousand dates and numbers.
Look up other hashes - and keep up-to-date: your PC will have several libraries with hashes like MD5 and sha1, which should perform better than a hand-rolled hash in VBA.
Here's some demonstration code using the Adler-32 checksum. Read the code comments, there's stuff in there you'll need to know in order to adapt this to your project:
Public Function RangeHasChanged() As Boolean
' Demonstration function for use of the Checksum() function below.
' For more advanced users, I have a 'Watched Range' class on the website:
' http://excellerando.blogspot.com
' Author: Nigel Heffernan, May 2006 http://excellerando.blogspot.com
' Please note that this code is in the public domain. Mark it clearly, with
' the author's name, and segregate it from any proprietary code if you need
' to assert ownership & commercial confidentiality on that proprietary code
' Coding Notes:
' It is expected that this function will be saved in the host worksheet's
' module and renamed to indicate the range or table being monitored. It's a
' good idea to use a named range rather than a hardcoded address.
' You might also choose to edit the '1 To 255' to the width of your range.
' Initialising the static values so that the first check in your VBA session
' does not automatically register a 'change' is left as an exercise for the
' reader: but calling the function on opening the workbook works well enough
' This is intended for use in VBA, not for use on the worksheet. Use the
' setting 'Option Private Module' to hide this from the function wizard.
Dim rngData As Excel.Range
Dim arrData As Variant
Dim lngChecksum As Long
Static lngExisting As Long
' Note that we capture the entire range in an Array, then work on the array:
' this is a single 'hit' to the sheet (the slow operation in any interaction
' with worksheet data) with all subsequent processing in VBA.
' BS 10/11/2021: Modified to look at the current selection if the hard-coded worksheet does not exist.
If Evaluate("ISREF('DataEntryMain'!A1)") Then
Set rngData = ThisWorkbook.Names("DataEntryMain").RefersToRange
Else
Set rngData = Intersect(Selection, Selection.parent.UsedRange) ' Reduce the range so it is never bigger than the UsedRange.
End If
arrData = rngData.Value2
RangeHasChanged = False
lngChecksum = CheckSum(arrData)
If rngData.count > 1 Then
' The passed range is more than one cell. Release the dynamic-array storage space.
Erase arrData
End If
' lngExisting is zero when the file opens, and whenever the
' VBA project is reinitialised, clearing all the variables.
' Neither of these events should be reported as a 'change'.
If lngExisting <> lngChecksum And lngExisting <> 0 Then
RangeHasChanged = True
End If
lngExisting = lngChecksum
Debug.Print RangeHasChanged, "The Adler-32 for " & rngData.Address & " is " & lngChecksum, Hex(lngChecksum)
End Function
' I could've sworn I posted this here, years ago, but here's an implementation of Adler-32 in
' 32-bit VBA.
'
' There 's a horrible hack in it: Adler-32 returns a 32-bit integer, and the VBA Long is a signed
' integer with a range ± (2^31) -1, so I've implemented a 'wrap around' of the overflow at +2^31,
' restarting at -2^31 +1. And done something I really, really shouldn't have done with a
' floating-point variable. Eventually everyone, everywhere, will have 64-bit Office and this'll
' be kind of quaint and unnecessary... Right?
'
' Of course, the real question is: why bother?
'
' It boils down to the common question of checking for changes: if you don't want to use the 'on
' change' event, or you're dealing with data directly in VBA before it hits the sheet, large data
' sets need something better than an item-by-item brute force approach. At least, if you're doing
' it more than once: the cost of rolling each item into your hash is always more than the cost of
' the one-by-one comparison...
'
' ...And that's still true if you're importing a fast hashing algorithm from MySQL or one of the
' web API libraries (try MDA5, if you can get at an exposed function), unless you can find
' something that reads VBA variant arrays directly and relieve your VBA thread of the task of
' enumerating the list values into the imported function.
'
' Meanwhile, here's a hash algorithm that's within reach of VBA: Adler32. The details are in
' Wikipedia’s article on Adler32: http://en.wikipedia.org/wiki/Adler-32 and an hour's testing
' will teach you some lessons about hashing:
'
' 'Hash collisions' (differing data sets returning the same hash code) are more common than you
' expected, especially with data containing repeated patterns (like dates);>
' Choice of hashing algorithm is important;
' ...And that choice is more of an art than a science;
' Admitting that you really shouldn't have bothered and resorting to brute force is often the
' better part of valour.
'
' Adler-32 is actually more useful as a tool to teach those lessons, than as a workaday checksum.
' It's great for detecting changes in lists of more than 100 distinct items; it's tolerable, on a
' list of 24 randomly-generated 8-letter words (hash collisions at 1 in 1800 attempts) and it
' starts giving you single-digit percentage occurrences of the hash collision error in a list of
' 50 not-so-distinct option maturities, where the differences are mostly in the last 10 chars and
' those ten chars are recurring 3-month maturity dates.
'
' By the time you're comparing pairs of 6-letter strings, more than 10% of your changes will be
' missed by the checksum in a non-random data set. And then you realise that might as well be
' using string comparison for that kind of trivial computation anyway.
'
' So the answer is always: test it.
'
' Meanwhile, here 's the algorithm, horrible hacks and all:
Public Function CheckSum(ByRef ColArray As Variant) As Long
Application.Volatile False
' Returns an Adler32 checksum of all the numeric and text values in a column
' Capture data from cells as myRange.Value2 and use a 32-bit checksum to see
' if any value in the range subsequently changes. You can run this on multi-
' column ranges, but it's MUCH faster to run this separately for each column
'
' Note that the VBA Long Integer data type is not a 32-bit integer, it's a
' signed integer with a range of ± (2^31) -1. So our return value is signed
' and return values exceeding +2^31 -1 'wraparound' and restart at -2^31 +1.
' Coding Notes:
' This is intended for use in VBA, and not for use on the worksheet. Use the
' setting 'Option Private Module' to hide CheckSum from the function wizard
' Author: Nigel Heffernan, May 2006 http://excellerando.blogspot.com
' Acknowledgements and thanks to Paul Crowley, who recommended Adler-32
' Please note that this code is in the public domain. Mark it clearly, with
' the author's name, and segregate it from any proprietary code if you need
' to assert ownership & commercial confidentiality on your proprietary code
Const LONG_LIMIT As Long = (2 ^ 31) - 1
Const MOD_ADLER As Long = 65521
Dim a As Long
Dim b As Long
Dim i As Long
Dim j As Long
Dim k As Long
Dim arrByte() As Byte
Dim dblOverflow As Double
If TypeName(ColArray) = "Range" Then
ColArray = ColArray.Value2
End If
If IsEmpty(ColArray) Then
CheckSum = 0
Exit Function
End If
If (VarType(ColArray) And vbArray) = 0 Then
' single-cell range, or a scalar data type
ReDim arrData(0 To 0, 0 To 0)
arrData(0, 0) = CStr(ColArray)
Else
arrData = ColArray
End If
a = 1
b = 0
For j = LBound(arrData, 2) To UBound(arrData, 2)
For i = LBound(arrData, 1) To UBound(arrData, 1)
' VBA Strings are byte arrays: arrByte(n) is faster than Mid$(s, n)
arrByte = CStr(arrData(i, j)) ' Is this type conversion efficient?
For k = LBound(arrByte) To UBound(arrByte)
a = (a + arrByte(k)) Mod MOD_ADLER
b = (b + a) Mod MOD_ADLER
Next k
' Terminating each item with a 'vTab' char constructs a better hash
' than vbNullString which, being equal to zero, adds no information
' to the hash and therefore permits the clash ABCD+EFGH = ABC+DEFGH
' However, we wish to avoid inefficient string concatenation, so we
' roll the terminating character's bytecode directly into the hash:
a = (a + 11) Mod MOD_ADLER ' vbVerticalTab = Chr(11)
b = (b + a) Mod MOD_ADLER
Next i
' Roll the column into the hash with a terminating horizontal tab char:
a = (a + 9) Mod MOD_ADLER ' Horizontal Tab = Chr(9)
b = (b + a) Mod MOD_ADLER
Next j
' Using a float in an integer calculation? We can get away with it, because
' the float error for a VBA double is < ±0.5 with numbers smaller than 2^32
dblOverflow = (1# * b * MOD_ADLER) + a
If dblOverflow > LONG_LIMIT Then ' wraparound 2^31 to 1-(2^31)
Do Until dblOverflow < LONG_LIMIT
dblOverflow = dblOverflow - LONG_LIMIT
Loop
CheckSum = 1 + dblOverflow - LONG_LIMIT
Else
CheckSum = b * MOD_ADLER + a
End If
End Function
There is a Worksheet_change event that would probably do what you want:
Private Sub Worksheet_Change(ByVal Target As Range)
End Sub
Place this in the code for the "Main Info Sheet" and it will run everytime the sheet is changed.
However, if you don't want the spreadsheet to run every single time the sheet is updated, but only want to check if it has been updated... what you can do is create a global variable like this (the declaration must be placed in a standard module:
Global MainSheetHasChanged as Boolean
Then you would simply put this line of code in the worksheet_changed macro:
Private Sub Worksheet_Change(ByVal Target As Range)
MainSheetHasChanged = True
End Sub
Just make sure to always set the variable back to false after running your other macro. Is this what you're looking for?

Resources