### Author Topic: RND and RANDOMIZE information  (Read 9289 times)

0 Members and 1 Guest are viewing this topic.

#### SMcNeill ##### RND and RANDOMIZE information
« on: June 10, 2019, 04:53:58 AM »
For folks who want a little extra information about how RND and RANDOMIZE work in QBASIC (and has been imitated to work the same in QB64), here's a little old documentation I dug up from the old drive on them:

Quote
;***
; RANDOM - RANDOM number generator AND RANDOMIZE
;
;        Copyright <C> 1986, Microsoft Corporation

;
; Algorithm:
;
; We use the "linear congruential" method FOR RANDOM numnber generation. The
; formula IS:
;
;        x1 = (x0 * a + c) MOD 2^24
;
; where
;
;        x1 = IS a new RANDOM number in the range [0..1^24]
;        x0 = the previous RANDOM number (OR the seed, FOR the first one)
;        a  = 214,013
;        c  = 2,531,011
;
; The RND FUNCTION returns a floating POINT number:
;
;        x1 / (2^24)
;
; which changes the range TO [0..1].

;***
;GetNextRnd -- GET NEXT RANDOM number
;MakeFloat -- make the number in [b\$RndVar] into a R4
;
;Purpose:
;        GET NEXT RANDOM number in sequence.
;Entry:
;        [b\$RndVar] has the seed.
;EXIT:
;        [AX]        = *B\$AC which contains the R4 result
;Exceptions:
;        none
;*******************************************************************************

cProc        GetNextRnd,<NEAR>

cBegin
PUSH        DI
MOV        AX,[WORD PTR b\$RndVar]         ;low half of previous number
MOV        CX,[RndA]        ;low half of A
MUL        CX
XCHG        AX,DI                ;save low half in DI
MOV        BX,DX                ;  high half in BX
MOV        AX,[WORD PTR b\$RndVar+2] ;high half of previous
MUL        CX
MOV        AX,[RndA]
MUL        [WORD PTR b\$RndVar]
ADD        BX,AX                ;last partial product (since we're mod 2^24)
XOR        BH,BH                ;extended 24-bit number TO 32 bits FOR NORM
MOV        DX,DI                ;number in BX:DX
MOV        [WORD PTR b\$RndVar],DX         ;save FOR NEXT time
MOV        [WORD PTR b\$RndVar+2],BX
POP        DI
MakeFloat:

FILD        b\$RndVar        ; PUT 24-bit INTEGER ON numeric stack
FDIV        FP_2T24         ; ST0 = seed/2^24
MOV        BX,OFFSET DGROUP:B\$AC
FSTP        DWORD PTR [BX]        ; PUT s.p. equivalent into FAC
XCHG        AX,BX                ; result IS *R4 in AX
FWAIT                        ; ensure result in RAM prior TO RETURN

cEnd                                ; EXIT TO caller

;***
;B\$RNZP - RANDOMIZE statement
;void B\$RNZP (R8 SeedNum)
;
;Purpose:
;        The number IS set into the middle word of the current RANDOM
;        number AS the seed FOR the NEXT one.
;Entry:
;        R8 SeedNum
;EXIT:
;        A new seed IS created in RndVar, based ON the seed value at entry
;        AND the least significant 2-words of the INPUT parameter.
;Exceptions:
;        none
;*******************************************************************************

cProc        B\$RNZP,<PUBLIC,FAR>
ParmQ        SeedNum         ; R8 seed number
cBegin
LEA        BX,SeedNum+4        ; GET MOST significant digits
MOV        AX,[BX]         ; GET word of D.P. number
XOR        AX,[BX+2]        ; XOR with the NEXT word
MOV        [WORD PTR b\$RndVar+1],AX ; replace middle word of current s.p. seed
;        with this value - - now we're reseeded.
cEnd                                ; EXIT

As you can see, we don't have any true randomness with RND in QB64.  In fact, our results are calculated on a mathematical formula!  (Which is why we always get the same results if we don't use RANDOMIZE TIMER to jump to some off point in the list of numbers we generate and use.)

If you're interested in this stuff, then here it is.  If not, then just ignore this topic and trust that RND isn't truly random -- which is why we call it pseduo-random, at best.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

#### Qwerkey ##### Re: RND and RANDOMIZE information
« Reply #1 on: June 10, 2019, 05:58:05 AM »
Yes, it's always best to put RANDOMIZE(TIMER) so that this is performed regularly in your running program.

#### SMcNeill ##### Re: RND and RANDOMIZE information
« Reply #2 on: June 10, 2019, 06:15:39 AM »
Apparently either the documentation I found is old and didn't apply to QBASIC RND (maybe it was the formula used with some other version Microsoft produced?), or else QB64 uses a different RND formula.

What we actually use is this one (as taken from libqb.cpp):

Code: [Select]
`float func_rnd(float n,int32 passed){    if (new_error) return 0;        static uint32 m;    if (!passed) n=1.0f;    if (n!=0.0){        if (n<0.0){            m=*((uint32*)&n);            rnd_seed=(m&0xFFFFFF)+((m&0xFF000000)>>24);        }        rnd_seed=(rnd_seed*16598013+12820163)&0xFFFFFF;    }         return (double)rnd_seed/0x1000000;}`
Instead of a formula where Seed = (Seed * 214013 + 2531011) MOD 2 ^ 24, we use one where rnd_seed=(rnd_seed*16598013+12820163)&0xFFFFFF;

Basically the concept is the same, but the formula for the calculations are different in the two versions.

I wonder how QB64's formula compares against QB45's. If anyone has a version of QB45 they can run, can you kindly tell me what the output might be for the following:

Code: [Select]
`FOR i = 1 TO 20    PRINT RND, RandNEXTFUNCTION Rand    STATIC Seed    x1 = (Seed * 214013 + 2531011) MOD 2 ^ 24    Seed = x1    Rand = x1 / 2 ^ 24END FUNCTION`
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

#### bplus ##### Re: RND and RANDOMIZE information
« Reply #3 on: June 10, 2019, 10:29:53 AM »
MOD 2^24 puts a limit on the numbers generated to 16+ million but does the rest of the formula even cover that amount?

Hmm... how many times 2^24 should that range be mostly covered? (for normal distribution).

#### SMcNeill ##### Re: RND and RANDOMIZE information
« Reply #4 on: June 10, 2019, 12:33:44 PM »
MOD 2^24 puts a limit on the numbers generated to 16+ million but does the rest of the formula even cover that amount?

Hmm... how many times 2^24 should that range be mostly covered? (for normal distribution).

The MOD, I think, is mostly there to make certain that we generate a value from 0 to 1.

x1 = (x0 * 214,013 + 2,531,011) MOD 2^24
x0 = the previous RANDOM number (OR the seed, FOR the first one)

So, in the case of the first number, what happens if someone plants a seed which results in a return value greater than 2 ^ 24?

We'd end up with a value which, in the end, would fall outside the 0 to 1 range which the function is built to generate.  The MOD makes certain we never fall outside that boundary. (Such as a RND(2^24) would generate without it.)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

#### bplus ##### Re: RND and RANDOMIZE information
« Reply #5 on: June 10, 2019, 01:10:45 PM »
Quote
The MOD, I think, is mostly there to make certain that we generate a value from 0 to 1.

Yes! I see that.

Do you see that there are only 16+ million actual values that can be generated by this function assuming MOD only generates Whole numbers and not Reals. No matter the seed, MOD will reduce it to < 2^24 or 16+ million values and that is only if all those values can be covered by the rest of the formula. It is possible the rest of the formula will only cycle through some fraction of 16+ million before repeating over and over again.

#### xra7en ##### Re: RND and RANDOMIZE information
« Reply #6 on: June 10, 2019, 06:41:45 PM »
Yes, it's always best to put RANDOMIZE(TIMER) so that this is performed regularly in your running program.

I don't remember where I saw this, but for years, I always used

Code: QB64: [Select]

SEEMS to work well.
Light is faster than sound, that is why people APPEAR intelligent until they speak

#### MWheatley

• Newbie
• Posts: 60 ##### Re: RND and RANDOMIZE information
« Reply #7 on: June 11, 2019, 11:01:13 AM »

I wonder how QB64's formula compares against QB45's. If anyone has a version of QB45 they can run, can you kindly tell me what the output might be for the following:

Code: [Select]
`FOR i = 1 TO 20    PRINT RND, RandNEXTFUNCTION Rand    STATIC Seed    x1 = (Seed * 214013 + 2531011) MOD 2 ^ 24    Seed = x1    Rand = x1 / 2 ^ 24END FUNCTION`

The result is an overflow error, as per the attached image.

Malcolm

#### Jack002 ##### Re: RND and RANDOMIZE information
« Reply #8 on: June 11, 2019, 11:47:48 AM »
I love how subjective computer random generators are. The idea of randomness is so hard to define. Shuffle some cards, now they're random, no, not yet, do it more.

A good psudorandom works well as far as I know. If you can never get a number _ from it, I'd think that is bad. Any time one number is coming out more/less than others, that is bad. A function to replicate the idea seems doable to me.

There was a test for randomness (there are many) the one I like is one that uses a poker format.
Find a way to assign your random number to one of 52 cards, record them in groups of five, then record when you see a pair, two pair, three of a kind, etc, etc, then gather some number of hands (I think a chi square thing would come into play on that number) and then look at the ratios of what hands came out and what you expect from established card probability. I think a pair is just under 50%, like 0.46 or so?

I did this test for school years back using my commodore 64, it turns out to have a very good random number generator, it passed the test
« Last Edit: June 11, 2019, 11:49:11 AM by Jack002 »
QB64 is the best!

#### Bert22306

• Forum Regular
• Posts: 189 ##### Re: RND and RANDOMIZE information
« Reply #9 on: June 11, 2019, 03:56:59 PM »
I've written two tests for PRNGs, which are attached.

One draws dots in a 2D space, using every adjacent pair of random numbers from the PRNG as x and y coordinates. Very simple code.

The second one fills 10 bins with the random numbers, as they are created by the PRNG, to see whether the distribution is even over time. It also calculates the max so far, min so far, and the running average.

A random number generator has to generate any given value with the same probability as any other value. So over time, you should notice the screen filling up evenly with dots, even if clusters do form here and there. In other words, an empty area in the screen should eventually fill in, even though, in theory, "eventually" could be a very long time. Similarly, one crowded cluster will eventually be no more crowded than the rest of the screen. And using TIMER as the seed, every iteration should look different, in terms of how the screen fills up with dots.

The test with the bins is more precise, but it's also one that would appear to do well with a non-random sequence. For example, a simple repeating sequence of 0 to 1, step 1, will be nice and evenly distributed. Still, if you run that second program, you can see how each try is giving different results, and how over time, things tend to even out.

The two tests show that QB64's PRNG is pretty good. No obvious biases. And, it's nice to have QB64's RANDOMIZE USING feature, to allow you to re-start the pseudo-random sequence, for a given seed value, from the beginning. Couldn't do that with QBasic. Essential if you're writing some sort of monte carlo simulation, where you need to be sure you're using the same random sequence every time, in testing the results.

#### Jack002 ##### Re: RND and RANDOMIZE information
« Reply #10 on: June 11, 2019, 05:45:55 PM »
Very cool, Bert. Those are great!
QB64 is the best!

#### Raven_Singularity

• Forum Regular
• Posts: 158 ##### Re: RND and RANDOMIZE information
« Reply #11 on: June 12, 2019, 10:29:45 AM »
I was always fascinated by BASIC's RND and RANDOMIZE.  After I figured out how the seed part worked, I came up with clever uses.

For example, I had a game with a top-down map, and wanted the levels to appear semi-random, for example a dirt path was mostly dirt with a bit of mud, so I would set those tiles to 90% likelihood of dirt, 10% chance of mud.  This produced nice looking paths.  The trick was to store the seed used on each level, then when the character returned to that screen, the semi-randomness was the same.  Mud patches in the same place you remember, but different places after starting a new game.  This really added to the feel of the game.  I used this nearly everywhere, such as the edge of water (land bits randomly jutting out into the water), hedges (bits taken out so it wasn't perfectly rectangular), etc.  It was great because storing a single random seed number was just one variable, but allowed me to produce variations for every screen of the game.  I think I used a variable counter + DATE + TIMER for getting actual random numbers after drawing the screen with the fixed seed.

I never saw anyone else using RANDOMIZE this way, having it produce fixed sets of random numbers that could be reused later via the same seed number.  I'm sure lots of people found clever uses for RANDOMIZE seeds, I just never encountered anyone else utilising this oddity of non-random randomness!

#### Raven_Singularity

• Forum Regular
• Posts: 158 ##### Re: RND and RANDOMIZE information
« Reply #12 on: June 12, 2019, 10:35:12 AM »
And, it's nice to have QB64's RANDOMIZE USING feature, to allow you to re-start the pseudo-random sequence, for a given seed value, from the beginning. Couldn't do that with QBasic. Essential if you're writing some sort of monte carlo simulation, where you need to be sure you're using the same random sequence every time, in testing the results.

Didn't read your post until after I wrote mine.  That's exactly how I used QuickBASIC seeds, resetting them by saving and reusing the seed number I passed to RANDOMIZE.  How does this not work?

#### bplus ##### Re: RND and RANDOMIZE information
« Reply #13 on: June 12, 2019, 11:04:23 AM »
Quote
I never saw anyone else using RANDOMIZE this way, having it produce fixed sets of random numbers that could be reused later via the same seed number.  I'm sure lots of people found clever uses for RANDOMIZE seeds, I just never encountered anyone else utilising this oddity of non-random randomness!

Never say never no more! :)

see line 194:
Code: QB64: [Select]
1. _TITLE "Happy Trails 2018"
2. ' 2017-12-29 another redesign of fireworks
3. ' 2017-12-28 redesign fireworks
4. ' now with lake refelction 2017-12-27 forget the bouncing sparks
5. ' combine Welcome Plasma Font with landscape
6. '_title "Fireworks 3 translation to QB64 2017-12-26 bplus"
7. 'fireworks 3.bas SmallBASIC 0.12.2 [B+=MGA] 2015-05-09
8. 'fireworks 2.bas 2016-05-05 now with Gravity, Newtonian bounce, smoke debris
9. 'fireworks 3.bas try with map variables make bursts around a central point
10.
11.
12. CONST xmax = 1200
13. CONST ymax = 720
14. CONST waterline = 600 ' 600 = ratio 5 to 1 sky to water
15. '                       raise and lower waterline as desired  highest about 400?
16. CONST lTail = 15
17. CONST bluey = 5 * 256 ^ 2 + 256 * 5 + 5
18. CONST debrisMax = 28000
19.
20. SCREEN _NEWIMAGE(xmax, ymax, 32)
21. _SCREENMOVE 120, 20
22.
23. TYPE fireWorkType
24.     seed AS INTEGER
25.     age AS INTEGER
26.     life AS INTEGER
27.
28.
29. TYPE debrisType
30.
31. COMMON SHARED fw() AS fireWorkType
32. COMMON SHARED debris() AS debrisType
33. COMMON SHARED cN, pR!, pG!, pB!
34.
35. SCREEN _NEWIMAGE(xmax, ymax, 32)
36.
37. 'prepare message font
38. mess\$ = " Happy New Year 2018"
39. PRINT mess\$
40. w = 8 * LEN(mess\$): h = 16
41. DIM p(w, h)
42. black&& = POINT(0, 10)
43. FOR y = 0 TO h
44.     FOR x = 0 TO w
45.         IF POINT(x, y) <> black&& THEN
46.             p(x, y) = 1
47. xo = 0: yo = 15: m = 7.2
48. resetPlasma
49.
50. 'prepare landscape
51. land& = _NEWIMAGE(xmax, ymax, 32)
52. _DEST land&
53. drawLandscape
54.
55. 'prepare fire works
56. nFW = 3
57. DIM fw(1 TO 10) AS fireWorkType
58. FOR i = 1 TO nFW
59.     initFireWork (i)
60.
61. 'debris feild
62. DIM debris(debrisMax) AS debrisType
63.
64. 'OK start the show
65.     'cls screen with land image
66.     _PUTIMAGE , land&, 0
67.
68.     'draw fireworks
69.     FOR f = 1 TO nFW
70.         IF fw(f).age <= fw(f).life THEN drawfw (f) ELSE initFireWork f
71.
72.     'debris
73.     FOR i = 0 TO debrisStack
74.         PSET (debris(i).x, debris(i).y), debris(i).c
75.         debris(i).x = debris(i).x + RND * 3 - 1.5
76.         debris(i).y = debris(i).y + RND * 3.5 - 1.5
77.         IF debris(i).x < 0 OR debris(i).y < 0 OR debris(i).x > xmax OR debris(i).y > waterline + RND * 20 THEN NewDebris (i)
78.
79.     'text message in plasma
80.     FOR y = 0 TO h - 1
81.         FOR x = 0 TO w - 1
82.             IF p(x, y) THEN
83.                 changePlasma
84.             LINE (xo + x * m, yo + y * m)-(xo + x * m + m, yo + y * m + m), , BF
85.     lc = lc + 1
86.     IF lc MOD 200 = 0 THEN resetPlasma
87.
88.     'reflect sky
89.     skyWaterRatio = waterline / (ymax - waterline) - .05
90.     FOR y = waterline TO ymax
91.         FOR x = 0 TO xmax
92.             c&& = POINT(x, waterline - ((y - waterline - 1) * skyWaterRatio) + RND * 5)
93.             PSET (x, y + 1), c&& + bluey
94.
95.     _LIMIT 200 'no limit needed on my system!
96.
97.     'accumulate debris
98.     IF lc MOD 2000 THEN
99.         IF debrisStack < debrisMax THEN
100.             FOR i = 1 TO 2
101.                 NewDebris i + debrisStack
102.             debrisStack = debrisStack + 2
103.
104. SUB NewDebris (i)
105.     debris(i).x = RND * xmax
106.     debris(i).y = RND * ymax
107.     c = RND * 155
108.     debris(i).c = _RGB32(c, c, c)
109.
110. SUB changePlasma ()
111.     cN = cN + .01
112.     COLOR _RGB(127 + 127 * SIN(pR! * .3 * cN), 127 + 127 * SIN(pG! * .3 * cN), 127 + 127 * SIN(pB! * .3 * cN))
113.
114. SUB resetPlasma ()
115.     pR! = RND ^ 2: pG! = RND ^ 2: pB! = RND ^ 2
116.
117. SUB drawLandscape
118.     'the sky
119.     FOR i = 0 TO ymax
120.         midInk 0, 0, 0, 78, 28, 68, i / ymax
121.         LINE (0, i)-(xmax, i)
122.     'the land
123.     startH = waterline - 80
124.     rr = 10: gg = 20: bb = 15
125.     FOR mountain = 1 TO 6
126.         Xright = 0
127.         y = startH
128.         WHILE Xright < xmax
129.             ' upDown = local up / down over range, change along Y
130.             ' range = how far up / down, along X
131.             upDown = (RND * .8 - .35) * (1 / (1 * mountain))
132.             range = Xright + rand&&(5, 35) * 2.5 / mountain
133.             lastx = Xright - 1
134.             FOR X = Xright TO range
135.                 y = y + upDown
136.                 COLOR _RGB32(rr, gg, bb)
137.                 LINE (lastx, y)-(X, ymax), , BF 'just lines weren't filling right
138.                 lastx = X
139.             Xright = range
140.         rr = rand&&(rr + 5, rr): gg = rand&&(gg + 5, gg): bb = rand&&(bb + 4, bb)
141.         IF rr < 0 THEN rr = 0
142.         IF gg < 0 THEN gg = 0
143.         IF bb < 0 THEN bb = 0
144.         startH = startH + rand&&(1, 10)
145.     'LINE (0, waterline)-(xmax, ymax), _RGB32(0, 0, 0), BF
146.
147. SUB midInk (r1, g1, b1, r2, g2, b2, fr)
148.     COLOR _RGB(r1 + (r2 - r1) * fr, g1 + (g2 - g1) * fr, b1 + (b2 - b1) * fr)
149.
150. FUNCTION rand&& (lo&&, hi&&)
151.     rand&& = INT(RND * (hi&& - lo&& + 1)) + lo&&
152.
153. SUB drawfw (i)
154.     'here's how to "save" a bunch of random numbers without data and arrays but tons of redundant calculations
155.     RANDOMIZE USING fw(i).seed 'this repeats all random numbers generated by seed in same sequence
156.     'recreate our firework from scratch!
157.     red = rand&&(200, 255)
158.     green = rand&&(200, 255)
159.     blue = rand&&(200, 255)
160.     x = rand&&(1, 4)
161.     IF x = 1 THEN
162.         red = 0
163.     ELSEIF x = 2 THEN
164.         green = 0
165.     ELSEIF x = 3 THEN
166.         blue = 0
167.         x = rand&&(1, 4)
168.         IF x = 1 THEN
169.             red = 0: green = 0
170.         ELSEIF x = 2 THEN
171.             green = 0: blue = 0
172.         ELSEIF x = 3 THEN
173.             blue = 0: red = 0
174.     ne = rand&&(80, 300)
175.     DIM embers(ne, 1)
176.     FOR e = 0 TO ne
177.         r = RND * 3
178.         embers(e, 0) = r * COS(e * _PI(2) / 101)
179.         embers(e, 1) = r * SIN(e * _PI(2) / 101)
180.     start = fw(i).age - lTail ' don't let tails get longer than lTail const
181.     IF start < 1 THEN start = 1
182.     FOR e = 0 TO ne
183.         cx = fw(i).x: cy = fw(i).y: dx = embers(e, 0): dy = embers(e, 1)
184.         FOR t = 1 TO fw(i).age
185.             cx = cx + dx
186.             cy = cy + dy
187.             IF t >= start THEN
188.                 'too much like a flower?
189.                 midInk 60, 60, 60, red, green, blue, (t - start) / lTail
190.                 'midInk 60, 60, 60, 128, 160, 150, (t - start) / lTail
191.                 fcirc cx, cy, (t - start) / lTail
192.
193.             dx = dx * .99 'air resitance
194.             dy = dy + .01 'gravity
195.         COLOR _RGB32(255, 255, 255)
196.         'COLOR _RGB32(red, green, blue)
197.         cx = cx + dx: cy = cy + dy
198.         fcirc cx, cy, (t - start) / lTail
199.     fw(i).age = fw(i).age + 1
200.
201. SUB initFireWork (i)
202.     fw(i).x = rand&&(.1 * xmax, .9 * xmax)
203.     fw(i).y = rand&&(.1 * ymax, .5 * ymax)
204.     fw(i).seed = rand&&(0, 32000)
205.     fw(i).age = 0
206.     fw(i).life = rand&&(20, 120)
207.
208. 'Steve McNeil's  copied from his forum   note: Radius is too common a name
209. SUB fcirc (CX AS LONG, CY AS LONG, R AS LONG)
211.
215.     Y = 0
216.
217.     IF subRadius = 0 THEN PSET (CX, CY): EXIT SUB
218.
219.     ' Draw the middle span here so we don't draw it twice in the main loop,
220.     ' which would be a problem with blending turned on.
221.     LINE (CX - X, CY)-(CX + X, CY), , BF
222.
223.     WHILE X > Y
225.         IF RadiusError >= 0 THEN
226.             IF X <> Y + 1 THEN
227.                 LINE (CX - Y, CY - X)-(CX + Y, CY - X), , BF
228.                 LINE (CX - Y, CY + X)-(CX + Y, CY + X), , BF
229.             X = X - 1
231.         Y = Y + 1
232.         LINE (CX - X, CY - Y)-(CX + X, CY - Y), , BF
233.         LINE (CX - X, CY + Y)-(CX + X, CY + Y), , BF
234.

Another great use of Randomize Using Seed is for encoding messages for cryptography.

« Last Edit: June 12, 2019, 11:11:07 AM by bplus »

#### Raven_Singularity

• Forum Regular
• Posts: 158 ##### Re: RND and RANDOMIZE information
« Reply #14 on: June 12, 2019, 11:17:44 AM »
Fireworks, landscapes, water reflections, and reusing random number sequences?

Sign me up!  I'll check it out once I'm back on Desktop, sounds very cool.

Are you reusing the random sequence to recreate the same firework as a reflection in the water?

Could you not just draw the firework twice at the time of generating the random numbers?