by Jon Ripley, August 2006
BBC BASIC for Windows provides a pseudo-random number generator for use in BASIC programs but has no built-in support for generating pseudo-random numbers from assembly language programs. This article contains code to add support for generating pseudo-random numbers from assembly language that can be added to any program.
The following routine generates a 32-bit pseudo-random number when called, the pseudo-random number is returned in the eax register:
.seed dd 1 :dd 0 .Rand mov eax, &4C957F2D ; | mul dword [seed] ; | push edx ; | mov edx, [seed] ; | imul ebx, edx, &5851F42D ; |\ pop edx ; | - seed = seed * &5851F42D4C957F2D add edx, ebx ; |/ mov ebx, [seed+4] ; | imul ebx, ebx, &4C957F2D ; | add edx, ebx ; | add eax, 1 ; seed = seed + 1 adc edx, 0 mov [seed], eax ; store the new seed mov [seed+4], edx shrd eax, edx, 21 ; shift right 21 bits ret ; return
The following routine generates a pseudo-random number in the range 1 to N:
.RandRange call Rand ; get random number mov ebx, [esp+4] ; read range limit xor edx,edx ; clear edx register div ebx ; do integer division; eax / ebx mov eax,edx ; read remainder of division inc eax ; add one ret 4 ; restore stack and return
Call RandRange using code similar to the following:
push N% call RandRange
The pseudo-random number is returned in eax. Here N% can be a register eax, read from a pointer to a 4 byte block in memory [addr], a BASIC constant N% defined at assemble time, a BASIC variable [^N%] or a numeric constant 1234. If the assembler reports a “Size needed” error add the dword prefix to the pushed parameter.
To change the range of numbers returned to 0 to N remove the 'inc eax' instruction.
The following routine generates a pseudo-random number in the range 0.0 to 1.0, exclusive of 1.0:
.RandFloat call Rand ; get random number and eax, &7FFFFFFF ; clear top bit push eax ; push random number on stack call Rand ; get random number push eax ; push random number on stack push &80000000 ; \ Put &8000000000000000 push 0 ; / on the stack fild qword [esp] ; load &8000000000000000 fild qword [esp+8] ; load random number fdivrp st1,st0 ; divide random number by &8000000000000000 fabs ; convert result to absolute value mov eax,[esp+20] ; read location to store result fstp qword [eax] ; store result add esp,16 ; free local variables ret 4 ; restore stack and return
To call this routine use code similar to the following:
push ^N# call RandFloat
The result is stored in the memory pointed to by the parameter. Here ^N# is a pointer to a 64-bit floating point BASIC variable but can be a pointer to an 8 byte block of memory [addr]. If the assembler reports a “Size needed” error add the dword prefix to the pushed parameter.
The following routine is called to seed the pseudo-random number generator with a 64-bit seed:
.RandSeed mov eax, [esp+4] ; load new seed mov edx, [esp+8] mov dword [seed], eax ; store new seed mov dword [seed+4], edx ret 8 ; restore stack and return
To seed the random number generator use the following code:
push hN push lN call RandSeed
Here we pass a 64-bit integer value to seed the pseudo-random number, hN is the top 32-bits of the seed and lN is the bottom 32-bits of the seed. By default the seed is set to the value of TIME when the code was assembled. If the assembler reports a “Size needed” error add the dword prefix to the pushed parameter.
These routines may be called from BASIC, instead of using the RND function, if you really want to:
result% = USR Rand :REM Return a pseudo-random number in result% SYS RandFloat, ^N# :REM Return a pseudo-random float in N# SYS RandRange, N TO result% :REM Return a range limited pseudo-random number in result% SYS RandSeed, TIME, TIME :REM Seed the pseudo-random generator using TIME
The multiplier 6364136223846793005 was obtained from Knuth, D.E., "The Art of Computer Programming," Vol 2, Seminumerical Algorithms, Third edition, Addison-Wesley, 1998, p. 106 (line 26) & p. 108.