Funny Behavior of Excel VBA Random number routine - vba

I am trying to generate a bunch of random permutations via the the following algorithm through vba:
Function RandNumber(Bottom As Integer, Top As Integer, _
Amount As Integer) As Integer()
Dim iArr As Variant
Dim i As Integer
Dim r As Integer
Dim temp As Integer
Dim bridge() As Integer
'Application.Volatile
ReDim iArr(Bottom To Top)
For i = Bottom To Top
iArr(i) = i
Next i
Randomize
For i = Top To Bottom + 1 Step -1
r = Int(Rnd() * (i - Bottom + 1)) + Bottom
temp = iArr(r)
iArr(r) = iArr(i)
iArr(i) = temp
Next i
ReDim Preserve bridge(1 To Amount)
For i = Bottom To Bottom + Amount - 1
bridge(i - Bottom + 1) = iArr(i)
Next i
RandNumber = bridge
End Function
What RandNumber essentially does is that it randomly gives a permutation based on the bottom and top values provided by the user. Example RandNumber(1,2,1) will either be 1 or 2 randomly.
Now I am testing this function through the following routine
Sub RandNos()
Dim z() As Variant
ReDim Preserve z(1 To 2560)
For i = 1 To 2560
z(i) = RandNumber(1, 2, 1)
Next i
For i = 1 To 2560
ThisWorkbook.Sheets("Sheet2").Range("A1").Offset(i - 1, 0) = z(i)
Next i
End Sub
To my utter amazement the 'random' numbers are repeating exactly after 256 runs! (Ok, the value 2560 for i above is not exactly a co-incidence. I was suspecting that there is some pattern going on around 256 rows and thus took i as 2560)
Moreover, when I test the above function with slightly modified subroutine:
Sub RandNos2()
For i = 1 To 2560
ActiveSheet.Range("A1").Offset((i - 1) Mod 256 + 1, Int((i - 1) / 256)) = RandNumber(1, 2, 1)
Next i
End Sub
the pattern is gone. (At least the pattern of repeating after 256 values is gone. Not sure if another pattern emerges).
Now based on my knowledge the Randomize is supposed to control the randomness by generating appropriate seeds and Randomize in vba takes the seed from the timer. So my hypothesis is that in the 1st RandNos() subroutine the updates happen so quickly that the seeds don't get updated fast enough. The fact that pattern is gone when I test with the second subroutine, since in the second routine excel takes longer to write the code in the worksheet and hence the gives the code some chance to update the timer and with it the seed of the Random numbers - supports my hypothesis.
So my question here is
Is my hypothesis correct.
Should I still hope to generate a 'random' pattern in Excel VBA.
Am I making a wrong use of Randomize here
Thanks in advance for your suggestions on the issue.
EDIT: One of the suggestions in the comment was that we should call Randomize only once. I tried doing that and it seems to work. However, I would still like to know what goes wrong using Randomize as above.

in answer to your edit, your hypothesis is correct.
Randomize takes the time and generates a seed. A seed is just a starting point for Rnd.
However, note that Randomize is not one-to-one which means that for any given input into Randomize (i.e. the time) it doesn't generate the same seed every time. You appear to have discovered that Randomize has a sequence of 256 seeds for every given input. Therefore, you get a repeating sequence of 256 numbers which were supposed to be random but which clearly are not.
Reference: The VBA help page for Randomize and CS classes

You should call Randomize once only. If RandNos() is called more than once, use Randomize in the method that calls RandNos().

just for future reference to anyone else who encounters this problem, I decided to just try "slowing down" the subroutine enough to allow the system timer to reset. Application.wait did not work well, but I found that by including a simple debug.print line above the randomize call, it slowed the execution down just enough to get it to not repeat every 256. This not dramatically increase the overall run time of the subroutine. Just a thought for folks who would not mind sacrificing a little bit of optimization for a very simple fix on the pseudo-randomness.

Related

Reverse number array multiline textboxes

basically, I want to reverse the numbers. (in the textbox there will be only 2-digit numbers)
if I have Textbox1.text:
12
2
41
71
70
I want to display in the box (Textbox1.text)
21
2
14
17
70
Function:
Public Shared Function Reverse(num As Integer) As Integer
Dim _reverse As Integer = 0
While num <> 0
_reverse *= 10
_reverse += num Mod 10
num \= 10
End While
Return _reverse
End Function
it should work, it actually works, but I don't know how to arrange it to work in all lines.
For Each lines In TextBox1.Lines
Dim rev = Reverse(lines)
lines.Replace(lines, rev)
Next
This is a perfect example of what happens when people try to write code without knowing what the code is supposed to. What the code is supposed to do is not just the end result but the steps to get there. If you don't know what the steps are then you shouldn't be writing any code because it's unlikely that what you write will do anything useful. Code is simply an implementation of logic so you should be getting the logic down first. It doesn't take any programming experience to work out the logic because we could all do this if it was a manual process and that would be the same logic.
So, what are the steps involved?
Get the lines of the text.
Loop over the lines.
Reverse the current line.
Replace the original line with the result of reversing.
Replace the text with the complete results.
If you actually consider each of those steps, it should be obvious that you cannot use a For Each loop because that will only let you get data out of a list, not put data into it. That would make it obvious that a For loop is the right choice, because will let you get data out and put it in. Now you can write code that actually does something useful.
Dim lines = TextBox1.Lines
For i = 0 To lines.GetUpperBound(0)
Dim line = lines(i)
Dim number = CInt(line)
Dim result = Reverse(number)
lines(i) = result.ToString()
Next
TextBox1.Lines = lines
Simple stuff but, again, if you don't know what the code has to actually do, writing code to do it is a challenge. Always break the problem down into smaller parts first, so you can work on each part individually, and always work out the logic you're trying to implement - and test that logic manually - before trying to write code to implement it.

Difference in performance: two loops vs one loop

Say I need to do two operations i times. Would it be better performance-wise to put both operations in a single for loop or create two separate for loops and but one operations in each?
Using two loops:
Sub Test()
Dim i As Long
Dim numbers() As Integer
Dim numbersMultiplied() As Integer
ReDim numbers(0 To 9) As Integer
ReDim numbersMultiplied(0 To 9) As Integer
For i = 0 To 9
numbers(i) = i
Next
For i = 0 To 9
numbersMultiplied(i) = numbers(i) * i
Debug.Print numbersMultiplied(i)
Next
End Sub
One loop:
Sub Test2()
Dim i As Long
Dim numbers() As Integer
Dim numbersMultiplied() As Integer
ReDim numbers(0 To 9) As Integer
ReDim numbersMultiplied(0 To 9) As Integer
For i = 0 To 9
numbers(i) = i
numbersMultiplied(i) = numbers(i) * i
Debug.Print numbersMultiplied(i)
Next
End Sub
Which one would be faster? Why? Or the performance of both ways is the same?
You can think about the fillowing parameters and assist your code
some CPUs contains hardware loops. This loop works as hardware cache for loop instructions but this requires that your loop don't exceed certain number of CPU instructions. Using hardware loops may increase your CPU performance.
Cache friendly code can increase the performance. In your code the single loop solution can make use of the value cached in this iteration - and it may be also in a register which is a more better thing - and this will reduce memory access time and enhance your performance
compiler optimization settings can also do things that you can't expect. In some cases the 2 loop solution may produce the same code as the single loop operation as the optimizer will detect that they are equivelant.
Those are only a few things to think about.

VBA Integer vs Long for counting tries on check if random number is equal to picked number at a range of 1 to 100

Last week I had to take a test in VBA. I had to code a little "game". These were the rules:
Pick a number between 1 and 100 (1 and 100 included)
Call function Randomize
Generate a random number between 1 and 100 (1 and 100 included) -> Int (100 * Rnd + 1)
Check if random number = chosen number.
5.1 If true, add +1 to counter, print counter, game is finished
5.2 If false, add+1 to counter, go back to Step 3.
I hope you get the aim of the "game".
I initialized the counter as an Integer (16 bit). My teacher told me that it is possible, that the counter might overflow. He recommends using a Long (32 bit) so that the chance to overflow is smaller.
I told him that it is nearly impossible to reach 32000 tries on the counter, because the chance that the picked number is equal to the generated number is 1:100.
He replied: But it's still possible.
My question:
Is it possible that the counter might overflow if the datatype is Integer? If yes, what's the chance? If no, how can I proof it?
Why is this question on stackoverflow and not on statistics?
It's simple. Because you guys know the Rnd-function and VBA behind the scenes, the guys on statistics don't.
Something like this I used in the past for file sampling, bringing the question is a Byte sufficient (1-255)?
Public dicGeneratedAlready As Scripting.Dictionary
Public Function GENERATE_RANDOM(intFromNumber As Integer) As Integer
Randomize
If dicGeneratedAlready Is Nothing Then Set dicGeneratedAlready = New Scripting.Dictionary
GenerateRand:
GENERATE_RANDOM = Int((Rnd * intFromNumber) + 1)
If dicGeneratedAlready.Exists(CStr(GENERATE_RANDOM)) Then
GoTo GenerateRand
Else
dicGeneratedAlready.Add CStr(GENERATE_RANDOM), CStr(GENERATE_RANDOM)
End If
End Function
From your description above there seems to be nothing stopping the code from trying numbers it has already tried before, so theoretically a long doesn't help, that could overflow as well, i.e. this could go on to infinity

generating random numbers in visual basics

I am using a random number generator in my program, however it keeps returning the same value (0.71) every time i run the program.
code:
number = FormatNumber(Rnd(1), 2)
rdmlabelTxt.Text = number.ToString
is there a way to produce a different random number when starting the program?
thanks.
According to Microsoft "the same number sequence is generated" when you don't give a parameter. The article also suggests to "Before calling Rnd, use the Randomize statement without an argument to initialize the random-number generator with a seed based on the system timer."
I think this will solve your issue - let us know.
You are required to write a for loop to be able to generate different numbers
For i = 1 to 100
number = FormatNumber(Rnd(1), 2)
Cells(i, "A").Value = number
next i
You just have to use Randomize() call before your codes.
Randomize()
Dim number As Double = 0
number = FormatNumber(Rnd(1), 2)
rdmlabelTxt.Text = number.ToString

Vb Guessing Game Random generator broken

In my code,I have now realised I have to use the New Random function, my code was working before with the Randomize and then the numbers but now it comes up with loads of errors and wont even let me run the program. I think it is only a small error but I just need some help to get the final bit going
Heres the code and thanks for any help :)
I cannot get the code to work with the randomly generated number and I have to use the New Random function I cannot use randomize() Does anybody know how to help here is the code.
Dim timestook As Int32 = 1
Dim usersguess As Integer
Dim value = New Random(0 - 19)
Console.WriteLine("You have to guess this number. It is between 1 and 20. Good Luck !")
usersguess = Console.ReadLine()
'keep looping until they get the right value
While usersguess <> value
'now check how it compares to the random value
If usersguess < value Then
timestook = timestook + 1
Console.WriteLine("You're too low. Go higher ")
ElseIf usersguess > value Then
Console.WriteLine("You're too high. Go Lower.")
timestook = timestook + 1
End If
'If they are wrong the code will run again,after telling the user if they are too high or too low.
usersguess = Console.ReadLine()
End While
' Console.WriteLine("You're correct. Well Done")
If usersguess = value Then
Console.WriteLine("You took,{0}", timestook)
End If
Console.ReadLine()
End Sub
You'll want to do some googling on how to use random numbers. Your problem is that you aren't creating a Random object to handle the random number generation.
Here's how you can fix your code:
Dim randNumGen As New Random() 'Create Random object
Dim value As Integer = randNumGen.Next(0, 20) 'set value equal to a new random number between 0-19
Please note that this code could be further refactored for readability and simplicity (like changing timestook = timestook + 1 to timestook += 1 and selecting better variable names like numberOfGuesses as opposed to timestook, etc.
The expression New Random(0-19) does not do at all what you think it does, name it does NOT return an integer. Instead, it creates an instance of a Random object, which is a type that knows how to create new random values. The 0-19 part of the expression is the seed for the Random object's constructor, and is the same as just passing the value -19.
This looks like it's either homework or personal practice, so I feel like you will be better served in this case with a separate example using the Random type for reference than you would if I fixed the code sample in the question for you:
Dim rnd As New Random()
For i As Integer = 0 To 10
Console.WriteLine(rnd.Next(0, 20))
Next i
It's also worth mentioning here that you typically only want one Random object for your entire program, or at least only one Random object for each logical part of your program. Creating new Random objects resets the seeds, and for best results you want to follow the same seed on subsequent calls to the same instance for a while.