Pages 64–66 · Markdown

Chapter 10 – Random Numbers

RANDOMIZE, RND and % RND

This chapter deals with the functions RND, RND () and % RND and the keyword RANDOMIZE. They are all used in connection with random numbers, so you must be careful not to get them mixed up.

As far as normal functions go, RND is quite unusual: although it does calculations and produces a result, it does not need an argument.

Each time you use it, its result is a new random floating point number Sbetween 0 and 1. (Sometimes it can take the value 0, but never 1.)

Try:

10 PRINT RND
20 GO TO 10

to see how the answer varies. Can you detect any pattern? You shouldn't be able to; random means that there is no pattern1.

% RND, which is – as seen on Chapter 8 – the version of RND available in integer expressions, behaves slightly differently. It takes a single argument (e.g. n) and returns a random integer in the range 0 to n-1. For example, %RND 10 will return a random integer between 0 and 9.

While RND returns, as discussed above, a random number between 0 and 1, you can easily get random numbers in other ranges. For instance, 5*RND is between 0 and 5, and 1.3+0.7*RND is between 1.3 and 2. For cases where we need to be in the standard expression evaluator, there is yet another version of RND, which is not an integer expression only function:

RND (n)

which returns a random integer between 0 and n-1 like %RND n. This is recommended over using the standard fractional floating-point function RND since it doesn't suffer from the biasing inherent in converting a fractional random number to an integer with multiply and truncation steps.

To get whole numbers with RND use INT (remembering that INT always rounds down) as in 1+INT (RND*6). If however your desired random values can stay within the range of 0 to 65535, it is better to use % RND or RND() which avoid the unnecessary conversions – and rather slow – floating point calculations involved.

To illustrate better what all version can do, let's use all three of them in a program to simulate dice throwing. RND*6 is in the range 0 to 6, but since it never actually reaches 6, INT (RND*6) is 0,1,2,3,4 or 5.

Here is the dice throwing program:

10 REM dice throwing program
20 CLS
30 FOR n=1 TO 2
40 PRINT 1+INT (RND*6);" ";
50 NEXT n
60 INPUT a$: GO TO 20

1 Actually, RND is not truly random, because it follows a fixed sequence of 65536 numbers. However, these are so thoroughly jumbled up that there are at least no obvious patterns so we say that RND is pseudo-random.

Press ENTER each time you want to throw the dice. To use % RND instead, change line 40 to read:

40 PRINT %1+ RND 6;" ";

and to use RND () you need to write line 40 as:

40 PRINT 1+ RND (6);" ";

Aren't the latter two more readable?

The RANDOMIZE statement, is used to make RND and % RND start off at a definite place in its sequence of numbers, as you can see with this program:

10 RANDOMIZE 1
20 FOR n=1 TO 5: PRINT % RND
   100,: NEXT n
30 PRINT: GO TO 10

After each execution of RANDOMIZE 1, the % RND sequence starts off again with 97 and if you use RND instead of % RND 100, you'll get 0.0022735596. You can use other numbers between 1 and 65535 in the RANDOMIZE statement to start the RND sequence off at different places.

If you had a program with RND, RND() or %RND in it and it also had some mistakes that you had not found, then it would help to use RANDOMIZE like this so that the program behaved the same way each time you ran it.

RANDOMIZE on its own (and RANDOMIZE 0 has the same effect) is different, because it really does randomise RND, RND() and % RND – you can see this in the next program:

10 RANDOMIZE
20 PRINT % RND 65535: GO TO 10

The sequence you get here is not very random, because RANDOMIZE uses the time since the computer was switched on. Since this has gone up by the same amount each time RANDOMIZE is executed, the next % RND does more or less the same. You would get better randomness by replacing GO TO 10 by GO TO 20. Here is a program to toss coins and count the numbers of heads and tails.

10 heads,tails=0
20 coin=% RND 2
30 ON coin: heads+=1:tails+=1
40 PRINT heads;",";tails,
50 IF tails<>0 THEN PRINT
   heads/tails;
60 PRINT: GO TO 20

The ratio of heads to tails should become approximately 1 if you go on long enough, because in the long run you expect approximately equal numbers of heads and tails.

Note that RANDOMIZE can also be written in short as RAND and it will expand to RANDOMIZE!

Exercises

  1. (For mathematicians only.)

    Let p be a (large) prime, and let a be a primitive root modulo p.

    Then if bᵢ is the residue of aᵢ modulo p (1 ≤ bᵢ ≤ p-1 ), the sequence:

    bᵢ-1
    p-1

    is a cyclical sequence of p-1 distinct numbers in the range 0 to 1 (excluding 1). By choosing a suitably, these can be made to look fairly random.

    65537 is a Fermat prime, 2¹⁶+1. Because the multiplicative group of non-zero residues modulo 65537 has a power of 2 as its order, a residue is a primitive root if and only if it is not a quadratic residue. Use Gauss' law of quadratic reciprocity to show that 75 is a primitive root modulo 65537 .

    The ZX Spectrum Next uses p=65537 and a=75, and stores some bᵢ-1 in memory. RND entails replacing bᵢ-1 in memory by bᵢ₊₁-1, and yielding the result (bᵢ₊₁-1) / (p-1).

    RANDOMIZE n (with 1 ≤ n ≤ 65535) makes bᵢ equal to n+1.

    RND is approximately uniformly distributed over the range 0 to 1.


ZX Spectrum Next User Manual, 3rd Edition (ISBN 978-1-5272-5496-1), written and illustrated by Phoebus R. Dokos. Copyright © 2020-2024 Phoebus Dokos / SpecNext Ltd. Licensed under CC BY-NC-SA 4.0. This is a transcription and can contain errors; check any doubt against the printed page.