# Pseudo random number generator problem

I would like to preface this by saying that I know the code is not very sophisticated and it’s probably not a very good number generator, I’m still fairly new to Python as a whole so please cut me some slack. I keep having a problem with my code where sometimes the function just won’t work. It won’t return the number, but it doesn’t end it’s action. It just gets stuck somewhere in the process and I have to manually end the process. I’m hoping someone here can give me some insight into what might be causing the problem.

``````import time
import calendar

def number():
epoch = calendar.timegm(time.gmtime())
x = epoch * 1000
y = (438*x + 163) % 256
first = (827*x + 378) % 31
second = (900*x + 259) % 269
third = (929*x + 811) % 613
amount = y
while amount <= 1000000:
z = (first*amount + second) % third
amount += z
return amount - 1000000
``````

You have a while loop that only exits if `amount` changes. That only happens if `z` is non-zero. I think you have nothing to prevent the case where that can become zero. As an example, if epoch is 1643242837, then this happens. And once stuck, there’s no way out.

I believe that is what was causing the problem, so I changed the while loop to depend on True. While testing I ran into a unique error that I didn’t expect where third was equal to 0 and it gave me a division error, so I had to add a try-except block to fix that. Here’s what it looks like now, I think it’s all good.

``````import time
import calendar

def number():
epoch = calendar.timegm(time.gmtime())
x = epoch * 1000
y = (438*x + 163) % 256
first = (827 * x + 378) % 31
second = (900 * x + 259) % 269
third = (929 * x + 811) % 613
amount = y
while True:
try:
z = (first*amount + second) % third
except ZeroDivisionError:
continue
else:
amount += z
if amount > 1000000:
break
return amount - 1000000
``````

I know that you want to develop your very own version of a pseudo-random number generator. Is there, however, any specific reason you don’t want to use Python’s `random` module from its Standard Library?

I don’t.

Part of the trickiness with a pseudorandom (or, worse, actually random)
sequence is that without special arrangements you will be testing
different sequences on every run. This means that if you encountered a
problem such as division by zero and you change the code (your
try/except) and rerun and the problem doesn’t show up, it does not mean
that the problem is fixed. It may mean you just got a different sequence
which would not have shown the problem anyway.

Ideally you would test the same sequence you used before after your code
change. Let’s look at your function:

``````def number():
epoch = calendar.timegm(time.gmtime())
... compute a number from this starting point ...
``````

For purposes of testing you might benefit by splitting this into 2
functions:

``````def number():
epoch = calendar.timegm(time.gmtime())
return _number(epoch)

def _number(epoch):
... compute a number from this starting point ...
``````

With this structure you could test the `_number()` function directly,
and feed it the same `epoch` value which caused the problem you’re
trying to fix. Obviously, you’d need to know that number, by you can
just put a `print(epoch)` call at the start of `_number()` to see that.

``````while True:
try:
z = (first*amount + second) % third
except ZeroDivisionError:
continue
else:
amount += z
if amount > 1000000:
break
return amount - 1000000
``````

Can you see that if `third` is zero the `except ZeroDivisionError`
branch will run and continue to the next loop iteration. But no values
have been changed, so the next iteration will also perform a division
by zero. This will repeat forever, essentially the same situation you
were in before.

You need to do something in the `except` branch to change the situation,
maybe setting `third=1` or something. Although `1` itself has problems
in than `%1` always returns `0`, which means `z` will be `0`, which
means `amount` will not change. And your loop will again run forever
because `amount` never reaches your cutoff point.

Ideally you should be certain that your loop will finish. One way to do
that is with a bound: exit the loop when some threshold is reached. You
have that: `amount>1000000`. Normally that would be your `while`
condition as you originally had it:

``````while amount <= 1000000:
``````

Instead of trying to be tricky with the condition, a different approach
is to ensure that every loop iteration advances closer to the bound.
That might mean that `amount` must always increase. And that should mean
that `z` is always `>0`. Compute `z` in a way so that that is always
true.

For example, knowing that `%` in Python always returns a nonnegative
value, you might compute:

``````z = (first*amount + second) % third + 1
``````

Your zero division clause might simply set `z=1` instead of continuing.
In that way you always advance `amount` and you `will` eventually
reach the loop bound.

An approach like this means that you can prove to yourself that the loop
will always finish instead of trying things until you no longer
encounter problems. The latter approach just means you have not tried a
problematic starting point, not that you know that there are no
problematic starting points.

Testing can show the presence of bugs, but not their absence. - Dijkstra

Cheers,
Cameron Simpson cs@cskk.id.au

Implementing something which already exists is often a good way to learn
about how that kind of thing works, or may work, or - importantly - does
not work.

Cheers,
Cameron Simpson cs@cskk.id.au

I picked up on the fact that my changes weren’t working shortly after. I did solve the problem, however I did it in a different way. With the try except block, I changed the except from continue to instead return a value of -1. I did the same thing when z = 0. I then edited the program that runs the function to filter through any -1 that were returned by the function, since the function (shouldn’t) ever return -1 as a value except when an error occurs. Although I do appreciate your suggestions, especially changing the division to `% third + 1`
Here’s what the code looks like now, including the parts that run the function.

``````import time
import calendar
from printf import printf

def number():
epoch = calendar.timegm(time.gmtime())
x = epoch * 1000
y = (438*x + 163) % 256
first = (827 * x + 378) % 31
second = (900 * x + 259) % 269
third = (929 * x + 811) % 613
amount = y
while amount <= 1000000:
try:
z = (first*amount + second) % third
except ZeroDivisionError:
return -1
else:
if z == 0:
return -1
else:
amount += z
return amount - 1000000

lst = []
num = int(input('How many random numbers would you like? '))
loop = 0
while True:
final = number()
if final == -1:
continue
else:
lst.append(final)
loop += 1
if loop == 1:
printf(f'{loop} number in your list. \n')
elif loop > 1:
printf(f'{loop} numbers in your list. \n')
if len(lst) == num:
break
time.sleep(.5)
Please ignore the fact that I used `printf` instead of `print`
This is pulling from a function that I found online that writes out each letter one by one, I thought it made the whole thing have a much more user friendly interface. I will edit the code to include the `% third + 1`
I have no opposition to using Pythons `random` module. In fact, I do use it in other programs. I only made this as a way to: 1. Help me understand how the `random` module works. 2. To help me develop my skills further by making a project and 3. Because I was bored in class and wanted to write a program that wouldn’t take a long time to write. At this point, I’m satisfied with how it is, but I’m just looking to perfect it, by my own standards.
Yeah, that’s true. Then, perhaps, at least look at how Python implements `random.random()`. Just a suggestion.