Avoiding overflow with large numbers when using Mod - vba

I began toying with project euler.
I have completed problem #3, but I was only able to complete it by setting my endNumber to 71 * 486847 (manually calculated). When trying to set endNumber = 600851475143 I receive the overflow error.
I even set endNumber as Long, and even Double which I think should be plenty of digits to calculate.
I can't post the whole code here, so I'll post half in hopes someone can show me how to edit it in order to process larger numbers.
Also I noticed that "#" pops up when my code has a very large number like below.
endNumber = 600851475143#
countFactor = 0
For i = 2 To endNumber
If endNumber Mod i = 0 Then

The overflow isn't due to assigning the number 600851475143 to the Double variable it is that the Mod function does not seem to handle numbers longer than Long very well (in fact seemingly not at all).
An alternative can be to use the manual calc for Mod:
endNumber = 600851475143#
countFactor = 0
For i = 2 To endNumber
If endNumber - (Int(endNumber / i) * i) = 0 Then
If you are going to use Mod for large numbers on a regular basis then Chip Pearson's function would be a useful one (which is where I learnt about this subject matter in the first place). It is posted as a problem in Excel but works perfectly in Access.

The highest possible number to work with in a long is: 2.147.483.647
A Double is for decimal purposes, so thats not what you are looking for.
The # that pops up is because a number is to large to be shown inside that cell.
Also I don't know what you want to do with that loop. But that one is going to take a very long while to run.
on this link you can find about handling large numbers: Large Number Arithmetic
Hope that helps

Related

VBA Division of integers

It seems improbable that this is not a duplicate of a question that already has an answer but I can't find that question or answer so here I go...
In VBA, in the immediate window, if I type:
?8/7
I get the result:
1.14285714285714 which if I then multiply by 7 gives me a number that is slightly LESS than 8, i.e. 7.99999999999998. My pocket calculator provides more decimal places, so it's better than VBA? Right? ;-)
However, if I add 0.000000000000003 to the RESULT 1.142... before I multiply I get 8 (which, by the way, is also incorrect); but my question is:
How can I control the precision of the answer with respect to the number of decimal places shown for ?8/7?
I have seen this answer relating to the accuracy of floating points but it is a different question, it explains the reasons for the inaccuracies whereas I am interested in getting a few extra digits.
To this end I have tried writing a function that returns a double but it does not return enough decimal places... for example I'd like to see: 1.142857142857142857. I also found references to a decimal data type but I think the article is for VB6 (instead of VBA) think I have exhausted the available data types in VBA... what am I missing?
Function test() As Double
Dim a As Double
Dim b As Double
Dim c As Double
a = 8
b = 7
c = a / b
test = c
End Function
Graphing and scientific calculators are not built for speed. They can afford to use software implementation of decimal floats for their calculations rather than IEEE doubles. So it is quite possible that your pocket calculator has greater precision than VBA, but at a cost. What they do is likely to be more than an order of magnitude slower.
VBA lacks a built-in arbitrary precision floating point library, but its decimal type supports higher precision than doubles. Unfortunately, it isn't a full-fledged data type but is rather a subtype of Variant. To create them, you need to use CDec():
Sub test()
Dim x As Variant
x = CDec(8) / 7
Debug.Print x
End Sub
This displays 1.1428571428571428571428571429
When using Decimal, you need to be aware of the fact that if you are not careful, they can pop back to e.g. a Double depending on how you use them:
Sub test2()
Dim x As Variant
x = CDec(x)
Debug.Print TypeName(x)
x = 8 / 7
Debug.Print x
Debug.Print TypeName(x)
End Sub
Output:
Decimal
1.14285714285714
Double

Mid() usage and for loops - Is this good practice?

Ok so I was in college and I was talking to my teacher and he said my code isn't good practice. I'm a bit confused as to why so here's the situation. We basically created a for loop however he declared his for loop counter outside of the loop because it's considered good practice (to him) even though we never used the variable later on in the code so to me it looks like a waste of memory. We did more to the code then just use a message box but the idea was to get each character from a string and do something with it. He also used the Mid() function to retrieve the character in the string while I called the variable with the index. Here's an example of how he would write his code:
Dim i As Integer = 0
Dim justastring As String = "test"
For i = 1 To justastring.Length Then
MsgBox( Mid( justastring, i, 1 ) )
End For
And here's an example of how I would write my code:
Dim justastring As String = "test"
For i = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
Would anyone be able to provide the advantages and disadvantages of each method and why and whether or not I should continue how I am?
Another approach would be, to just use a For Each on the string.
Like this no index variable is needed.
Dim justastring As String = "test"
For Each c As Char In justastring
MsgBox(c)
Next
I would suggest doing it your way, because you could have variables hanging around consuming(albeit a small amount) of memory, but more importantly, It is better practice to define objects with as little scope as possible. In your teacher's code, the variable i is still accessible when the loop is finished. There are occasions when this is desirable, but normally, if you're only using a variable in a limited amount of code, then you should only declare it within the smallest block that it is needed.
As for your question about the Mid function, individual characters as you know can be access simply by treating the string as an array of characters. After some basic benchmarking, using the Mid function takes a lot longer to process than just accessing the character by the index value. In relatively simple bits of code, this doesn't make much difference, but if you're doing it millions of times in a loop, it makes a huge difference.
There are other factors to consider. Such as code readability and modification of the code, but there are plenty of websites dealing with that sort of thing.
Finally I would suggest changing some compiler options in your visual studio
Option Strict to On
Option Infer to Off
Option Explicit to On
It means writing more code, but the code is safer and you'll make less mistakes. Have a look here for an explanation
In your code, it would mean that you have to write
Dim justastring As String = "test"
For i As Integer = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
This way, you know that i is definitely an integer. Consider the following ..
Dim i
Have you any idea what type it is? Me neither.
The compiler doesn't know what so it defines it as an object type which could hold anything. a string, an integer, a list..
Consider this code.
Dim i
Dim x
x = "ab"
For i = x To endcount - 1
t = Mid(s, 999)
Next
The compiler will compile it, but when it is executed you'll get an SystemArgumenException. In this case it's easy to see what is wrong, but often it isn't. And numbers in strings can be a whole new can of worms.
Hope this helps.

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

Funny Behavior of Excel VBA Random number routine

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.

converting string to array of integers fast

What is the fastest way to convert a String into an array of Short integers representing the character codes?
I am now using this but it can probably be much faster:
Dim shortsarray(mystring.Length - 1) as Short
For i As Integer = 0 To mystring.Length - 1
shortsarray(i) = AscW(mystring.Chars(i))
Next
Thanks.
I do not know what kind of problem you are trying to solve. However, as an alternative, you could convert the string to an array of characters
Dim chars() As Char = mystring.ToCharArray()
Dim shortsarray(mystring.Length - 1) as Short //One calculation
For i As Integer = 0 To mystring.Length - 1 //One calculation, repeats n times
shortsarray(i) = AscW(mystring.Chars(i)) //Three calculations, repeats n times
Next
As a result, this should run in O(3n+1), or O(n). This is linear on the length of the input, and there isn't much improvement you can expect from that since you're doing a character-wise conversion. I think this is probably as good as you can expect, though there may be a library that will just convert the whole word in one go, making your code cleaner.
If you need performance increases, you want to first profile your whole program. This snippet may not be the problem.