Extended Random

Share your advanced PureBasic knowledge/code with the community.
Froggerprogger
Enthusiast
Enthusiast
Posts: 423
Joined: Fri Apr 25, 2003 5:22 pm
Contact:

Extended Random

Post by Froggerprogger »

Hi,
here are some functions for creating (pseudo)random numbers.
You might create multiple randomizers for seperate use.
From each randomizer you can get by (uniform) random a
- long or quad-value in full range
- boolean by random
- positive long-value bounded by maximum
- long-value in range [minimum, maximum[
- float or double-value in range [0.0, 1.0[
Additionally it is possible to get a double-value with Gaussian distribution with mean 0.0 and standard deviation 1.0

Here the code:

Code: Select all

; #### Randomizer v1.0
; These procedures bring some extended randomize-functionality to all platforms.
; They are algorithmically based on the implementation of the Random()-class in Java.
; 
; Usage:
; 1. create a randomizer e.g. by calling *rnd.Randomizer = RND_Create()
; 2. get random long/float/double-values at uniform distribution or double-values at
;   gaussian distribution from *rnd by calling their respective function, e.g. RND_NextLong(*rnd)
;
; That's all. You might use multiple different randomizers at the same time without any conflict.
; Initializing two randomizers with the same seed will result in same behaviour.
;
; by Froggerprogger at 2008-10-28
;
; license: feel free to use this code for anything you want to

; #### Randomizer ####
; The structure identifying a randomizer
Structure Randomizer
  seed.q
  hasNextGaussian.l
  nextNextGaussian.d
EndStructure

; #### RND_NextBits ####
; Creates and returns a random long number, whose lower <bits> bits are set randomly.
; The random seed is taken from <*rnd> and updated there.
; <bits> should lay in range [1, 32]
; This procedure is exensively used by the other randomizing procedures, but might be used seperately.
Procedure.l RND_NextBits(*rnd.Randomizer, bits.l) ; returns number with lower <bits> bits (in range [1, 32]) set randomly
  Protected multiplier.q = $5DEECE66D
  Protected addend.q = $B
  Protected mask1.q = $FFFFFFFFFFFF
  Protected mask2.q = (1<<bits)-1

  *rnd\seed = (*rnd\seed * multiplier + addend) & mask1

  ProcedureReturn (*rnd\seed >> (48-bits)) & mask2
EndProcedure

; #### RND_CreateSeed ####
; Creates a new randomizer and sets its seed based on a modified <seed>
; (Use RND_SetSeed to set the seed without any modification to it.)
; Calling RND_CreateSeed two times with the same <seed> will result in the same behaviour of the both randomizers.
; Returns a pointer to the created randomizer, so e.g. call
; *rnd.Randomizer = RND_CreateSeed(seed)
Procedure.l RND_CreateSeed(seed.q) ; creates a new randomizer by given seed
  Protected multiplier.q = $5DEECE66D
  Protected mask.q = $FFFFFFFFFFFF
  Protected *rnd.Randomizer = AllocateMemory(SizeOf(Randomizer))
  If *rnd
    seed ! multiplier
    seed & mask
    *rnd\seed = seed
    *rnd\hasNextGaussian = #False
  EndIf  
  ProcedureReturn *rnd
EndProcedure

; #### RND_Create ####
; Creates a new randomizer with seed depending on ElapsedMilliseconds().
; Even multiple calls at the same millisecond will result in unique seeds.
; Returns a pointer to the created randomizer, so e.g. call
; *rnd.Randomizer = RND_CreateSeed()
Procedure.l RND_Create() ; creates a new randomizer with random seed
  Static seedUniquer.q = 8682522807148012
  Protected seed.q = seedUniquer + ElapsedMilliseconds()
  seedUniquer + 1
 
  ProcedureReturn RND_CreateSeed(seed)
EndProcedure

; #### RND_SetSeed ####
; Sets the seed of the given randomizer to <seed> without any modification to this value.
; Two randomizers with the same seed will result in the same behaviour.
Procedure RND_SetSeed(*rnd.Randomizer, seed.q)
  *rnd\seed = seed
  *rnd\hasNextGaussian = #False
EndProcedure

; #### RND_Free ####
; Destroys the randomizer given in <*rnd>. Returns wether or not it could be successfully freed.
Procedure.l RND_Free(*rnd.Randomizer) ; destroys the given randomizer (frees any memory used by it)
  ProcedureReturn FreeMemory(*rnd)
EndProcedure

; #### RND_NextLong ####
; Returns a random long in full range of -2147483648 to +2147483647
Procedure.l RND_NextLong(*rnd.Randomizer) ; returns random long in full range of -2147483648 to +2147483647
  ProcedureReturn RND_NextBits(*rnd, 32)
EndProcedure

; #### RND_NextLongMax ####
; Returns a random long in range of 0 to <exclMax>-1
; so inclusive the zero, but exclusive the value <exclMax>
Procedure.l RND_NextLongMax(*rnd.Randomizer, exclMax.l) ; returns random long between 0 (inclusive) and <exclMax> (exclusive)
  If exclMax <= 0
    ProcedureReturn -1
  EndIf
  
  If (exclMax  & (-exclMax)) = exclMax
    Protected q.q = RND_NextBits(*rnd, 31)
    q * exclMax
    q >> 31
    ProcedureReturn q
  Else
    Protected bits.l, val.l
    Repeat
      bits = RND_NextBits(*rnd, 31)
      val = bits % exclMax
    Until (bits - val + (exclMax-1) >= 0)
    ProcedureReturn val
  EndIf  
EndProcedure

; #### RND_NextLongMinMax ####
; Returns a random long in range of <inclMin> to <exclMax>-1
; so inclusive the value <inclMin>, but exclusive the value <exclMax>
; It should hold that <inclMin> is smaller than <exclMin> and that <exclMax> - <inclMin> is smaller than 2^32 
Procedure.l RND_NextLongMinMax(*rnd.Randomizer, inclMin, exclMax.l) ; returns random long between <inclMin> (inclusive) and <exclMax> (exclusive)
  ProcedureReturn inclMin + RND_NextLongMax(*rnd, exclMax - inclMin)
EndProcedure

; #### RND_NextQuad ####
; Returns a random long in whole range of -9223372036854775808 to +9223372036854775807
Procedure.q RND_NextQuad(*rnd.Randomizer) ; returns random quad in full range of -9223372036854775808 to +9223372036854775807
  Protected q.q = RND_NextBits(*rnd, 32)
  q << 32
  q + RND_NextBits(*rnd, 32)
  ProcedureReturn q
EndProcedure

; #### RND_NextBoolean ####
; Returns #False or #True by random
Procedure.l RND_NextBoolean(*rnd.Randomizer) ; returns #False or #True by random
  If RND_NextBits(*rnd, 1)
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

; #### RND_NextFloat ####
; Returns a random float-value in range of 0.0 (inclusive) to 1.0 (exclusive)
Procedure.f RND_NextFloat(*rnd.Randomizer) ; returns a random float-value in range of 0.0 (inclusive) to 1.0 (exclusive)
  Protected f.f = RND_NextBits(*rnd, 24)
  f / (1<<24)
  ProcedureReturn f
EndProcedure

; #### RND_NextDouble ####
; Returns a random double-value in range of 0.0 (inclusive) to 1.0 (exclusive)
Procedure.d RND_NextDouble(*rnd.Randomizer) ; returns a random double-value in range of 0.0 (inclusive) to 1.0 (exclusive)
  Protected q.q = RND_NextBits(*rnd, 26)
  q = (q << 27) + RND_NextBits(*rnd, 27)
  Protected d.d = (1<<53)
  d = q / d
  ProcedureReturn d
EndProcedure

; #### RND_NextGaussian ####
; Returns a random double-value that is distributed by Gaussian with mean 0.0 and standard-deviation 1.0
Procedure.d RND_NextGaussian(*rnd.Randomizer)
  If *rnd\hasNextGaussian
    *rnd\hasNextGaussian = #False
    ProcedureReturn *rnd\nextNextGaussian
  Else
    Protected v1.d, v2.d, s.d
    Repeat
      v1 = 2.0 * RND_NextDouble(*rnd) - 1.0 ; between -1 and 1
      v2 = 2.0 * RND_NextDouble(*rnd) - 1.0 ; between -1 and 1
      s = v1 * v1 + v2 * v2
    Until (s < 1 And s <> 0)
    Protected multiplier.d = Sqr(-2.0 * Log(s) / s)
    *rnd\nextNextGaussian = v2 * multiplier
    *rnd\hasNextGaussian = #True
    ProcedureReturn v1 * multiplier
  EndIf
EndProcedure

;-
;- END INCLUDE
;-

;########################################
;- Examples
;########################################
*rnd1.Randomizer = RND_Create()
*rnd2.Randomizer = RND_CreateSeed(987654321)

Debug "### 5 random doubles in [0.0, 1.0["
For i=1 To 5
  Debug RND_NextDouble(*rnd1)
Next

Debug "### 5 random longs in [-5, 5]"
For i=1 To 5
  Debug RND_NextLongMinMax(*rnd1, -5, 6)
Next

Debug "### 5 random longs in full range"
For i=1 To 5
  Debug RND_NextLong(*rnd1)
Next

Dim A.l(10)
Debug "### 10000 uniform distributed doubles in [0.0, 1.0["
For i=1 To 10000
  d.d = RND_NextDouble(*rnd1)
  index.l = Int(d*10.0) ; must use Int() or Round(..., 0) here for rounding downwards
  A(index) + 1
Next
f.f = 0.0
i = 0
While i < 10
  Debug "hits in range " + StrF(f, 1) + " to " + StrF(f + 0.1, 1) + " : " + Str(A(i))
  f.f + 0.1
  i + 1
Wend

Dim A.l(20)
Debug "### 10000 gaussian-distributed doubles"
For i=1 To 10000
  d.d = RND_NextGaussian(*rnd2)
  index.l =  Round(d*2.0 + 10.0, 0)
  If index >= 0 And index < 20
    A(index) + 1
  EndIf
Next

f.f = -5.0
i = 0
While i < 20
  Debug "hits in range " + StrF(f, 1) + " to " + StrF(f + 0.5, 1) + " : " + Str(A(i))
  f.f + 0.5
  i + 1
Wend

RND_Free(*rnd1)
RND_Free(*rnd2)
%1>>1+1*1/1-1!1|1&1<<$1=1