Table of Contents
Generating pseudo-random numbers
by Richard Russell, August 2006
BBC BASIC supplies pseudo-random numbers in three formats:
- RND supplies a pseudo-random 32-bit integer
- RND(N) supplies a pseudo-random positive integer in the range 1 to N
- RND(1) supplies a pseudo-random floating-point number in the range 0.0 to 1.0
This article contains routines to provide similar facilities for your assembly-language programs. They are short and very fast, yet the pseudo-random numbers they supply are of an equivalent quality to those supplied by RND.
RND
The subroutine below provides a direct equivalent to RND. Each time it is called it returns a pseudo-random 32-bit integer in the eax register:
.seed
dd 0
db 1
;
; Rnd - return a pseudo-random 32-bit integer
; Inputs - None
; Outputs - eax = result
; Destroys - eax, cl, edx, flags
;
.Rnd
mov eax,[seed]
mov cl,[seed+4]
mov edx,eax
shr cl,1
rcr edx,1
rcl cl,1
shl eax,12
xor edx,eax
mov eax,edx
shr eax,20
xor eax,edx
mov [seed+4],cl
mov [seed],eax
ret
This subroutine will generate the same sequence of numbers every time it is used. You are likely to want to randomise the sequence in order to make the results less predictable. You can simply do that using the following code, which uses the time as a seed:
.Randomise
call "GetTickCount"
mov [seed],eax
ret
You can test the code from BASIC as follows:
CALL Randomise FOR I% = 1 TO 20 PRINT ~ USR(Rnd) NEXT
RND(N)
The subroutine below provides a direct equivalent to RND(N), where N is a positive integer greater than 1. Each time it is called it returns a pseudo-random integer in the range 1 to N, in the eax register:
;
; RndRange - return a pseudo-random integer in the range 1 to N
; Inputs - ebx = N
; Outputs - eax = result
; Destroys - eax, cl, edx, flags
;
.RndRange
call Rnd
xor edx,edx
div ebx
mov eax,edx
inc eax
ret
The parameter N is passed to the subroutine in the ebx register. If you prefer to return a number in the range 0 to N-1 just delete the inc eax instruction.
You can test the code from BASIC as follows:
CALL Randomise FOR I% = 1 TO 20 B% = 100 PRINT USR(RndRange) NEXT
RND(1)
The subroutine below provides a direct equivalent to RND(1). Each time it is called it returns a pseudo-random 64-bit floating-point value (double) in the range 0.0 to 1.0:
;
; RndFloat - return a pseudo-random float in the range 0.0 to 1.0
; Inputs - ebx = memory address of 64-bit float (double)
; Outputs - result stored at [ebx]
; Destroys - eax, ecx, edx, flags
;
.RndFloat
call Rnd
bsr ecx,eax
ror eax,cl
add ecx,991
shld ecx,eax,20
shl eax,20
mov [ebx],eax
mov [ebx+4],ecx
ret
The value is returned in memory at the address passed in the ebx register.
You can test the code from BASIC as follows:
CALL Randomise FOR I% = 1 TO 20 B% = ^R# CALL RndFloat PRINT R# NEXT