Mars Mixed Number Generator

Monkey Forums/Monkey Code/Mars Mixed Number Generator

retroX(Posted 2013) [#1]
tags: rng prng random

I needed a repeatable mixed number stream for a game using only 32 bit signed integers. Monkey's rnd function is good but I wanted numbers that were more mixed, even if only slightly more mixed. None of the formulas I could find which worked within 31 bits would produce an output I liked. After much experimentation I finally tweaked all of the ideas into a formula that did what I wanted.
#rem
		'Mars MNG (Mixed Number Generator) presented 2013-09 by apo
		'A routine for producing scrambled number sequences
		'  using 32 bit signed integers.
#end
Import mojo

Private
Const bits31 = 2147483647
Const marsSize = 64
Const marsMax = marsSize - 1
Const marsD1 = marsSize / 3
Const marsD2 = marsSize * 2 / 3
Global marsX:int = 521288629
Global marsY:int = 362436069
Global marsZ:int = 217598643
Global marsNdx:int = marsMax
Global marsA:Int[] =[20814, 20970, 21153, 21243, 21423, 21723, 21954, 22125, 22188, 22293,
					22860, 22938, 22965, 22974, 23109, 23124, 23163, 23208, 23508, 23520,
					23553, 23658, 23865, 24114, 24219, 24660, 24699, 24864, 24948, 25023,
					25308, 25443, 26004, 26088, 26154, 26550, 26679, 26838, 27183, 27258,
					27753, 27795, 27810, 27834, 27960, 28320, 28380, 28689, 28710, 28794,
					28854, 28959, 28980, 29013, 29379, 29889, 30135, 30345, 30459, 30714,
					30903, 30963, 31059, 31083]
Public
			'---  inspired by the Marsaglia MNG  ---

Function mnMars:Int(r)	'mn = mixed number output function

	marsNdx = (marsNdx + 1) & marsMax
	marsX = ( (marsX * marsA[marsNdx]) ~ (marsY shr 15)) & bits31
	marsY = ( (marsY * marsA[ (marsNdx + marsD1) & marsMax]) ~ (marsZ shr 15)) & bits31
	marsZ = ( (marsZ * marsA[ (marsNdx + marsD2) & marsMax]) ~ (marsX shr 15)) & bits31
	Return ( (marsX + marsY + marsZ) & bits31) * 4.6566125E-10 * r
End Function
#rem         
	using: mnMarsInit(123, 456, 789) and mnMars(1000000000)
	first 5 values:	282319147
					972584087
					694988900
					773596550
					859902265
#end
Function mnMarsInit(s1, s2, s3)	's <= 0 = arbitrary seed

	Local date:= GetDate()
	If (s1 <= 0) or (s2 <= 0) or (s3 <= 0)
		marsX = date[1] * 9301 + date[6] * 100000
		marsY = date[2] * 9301 + date[5] * 100000
		marsZ = date[3] * 9301 + date[4] * 100000
	Else
		marsX = s1
		marsY = s2
		marsZ = s3
	End If
	marsNdx = marsMax
	mnMars(99)		'the first output is discarded because seeds having
End Function		'  low values produce a low initial ouput

Global mnMonkey = False

Function mn:Int(range)

	If mnMonkey Then Return Rnd(range)
	Return mnMars(range)
End Function

Function mnInit()

	Local date:= GetDate()
	If mnMonkey
		Seed = date[1] * 9999 + date[2] * 8888 + date[3] * 7777 +
				date[4] * 6666 + date[5] * 5555 + date[6] * 444444
	Else
		mnMarsInit(0, 0, 0)
	End If
End Function

Class mnResults Extends App

	Const MNSTREAM_SIZE = 20 * 6
	Field mnStream[MNSTREAM_SIZE]
	Field birthdays[60]
	Field p[2], x[100], y[100]
	Field ClrView, k, n, row, GetData
	Field c0, c1, r0, r1, t0, t1
	
	Method OnCreate()
	
		SetUpdateRate 30
		mnMarsInit(123, 456, 789)
'		For Local k = 0 To 4
'			Print(mnMars(1000000000))
'		End For
		GetData = True; ClrView = True
		c0 = 0; c1 = 0; r0 = 0; r1 = 0; t0 = 0; t1 = 0
	End Method
	
	Method OnUpdate()
	
		If KeyHit(KEY_SPACE) Then GetData = True	'get more data
		If KeyHit(KEY_R)							'reseed
			mnInit(); GetData = True; ClrView = True
			c0 = 0; c1 = 0; r0 = 0; r1 = 0; t0 = 0; t1 = 0
		End If
		If KeyHit(KEY_M)							'change MNG
			If mnMonkey Then mnMonkey = False Else mnMonkey = True
			mnInit(); GetData = True; ClrView = True
			c0 = 0; c1 = 0; r0 = 0; r1 = 0; t0 = 0; t1 = 0
		End If
		If GetData
			GetData = False
			For k = 0 Until MNSTREAM_SIZE
				mnStream[k] = mn(1000000000)
			End For
			For k = 0 Until 60
				birthdays[k] = 1 + mn(365)
			End For
		End If
		For k = 0 Until 100
			For n = 0 Until 2
				p[n] = mn(150)
				If (p[n] mod 2) < 1
					c0 += 1; r0 += 1; r1 = 0	'count 0's
					If r0 > t0 Then t0 = r0
				Else
					c1 += 1; r1 += 1; r0 = 0	'count 1's
					If r1 > t1 Then t1 = r1
				End If
			End For
			x[k] = p[0]; y[k] = p[1]	'pixel coordinates
		End For
	End Method
	
	Method OnRender()
	
		If ClrView
			ClrView = False
			Cls
		Else
			SetColor(0, 0, 0)
			DrawRect(0, 0, 510, 280)
			DrawRect(0, 300, 370, 174)
			SetColor(255, 255, 255)
		EndIf
		For k = 0 To 19
			row = k * 14
			DrawText(k, 5, row)
			For n = 0 To 5
				DrawText(mnStream[k + n * 20], 40 + n * 80, row)
			End For
		End For
		For k = 0 To 9
			row = 300 + k * 14
			DrawText(k + 1, 5, row)
			For n = 0 To 5
				DrawText(birthdays[k + n * 10], 40 + n * 40, row)
			End For
		End For
		If mnMonkey
			DrawText("Monkey MNG", 280, 300)
		Else
			DrawText("Mars MNG", 280, 300)
		End If
		DrawText("#  same total", 280, 398)
		DrawText("0  " + t0 + "   " + c0, 280, 412)
		DrawText("1  " + t1 + "   " + c1, 280, 426)
		DrawText("Space = more data, R = Reseed, M = change MNG", 5, 460)
		For k = 0 Until 100
			DrawPoint(440 + x[k], 300 + y[k])
		End For
	End Method
End

Function Main ()
	New mnResults()
End Function



Gerry Quinn(Posted 2013) [#2]
I have something similar in this forum:

http://www.monkeycoder.co.nz/Community/posts.php?topic=1568#14494

It emulates the linear congruent RNG used by MSVC (which is a fairly decent one), and should be the same on all targets.