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.

Now your loop:

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)
printf(f'Your list: ')
time.sleep(.5)
print(*lst)

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
Thank you for your suggestions.

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.