Community discussions

MikroTik App
 
juaco
just joined
Topic Author
Posts: 15
Joined: Tue Nov 09, 2010 12:15 am

random number generators

Mon Nov 29, 2010 10:18 am

For a script i'm doing i need to randomly pick values from an array, in a way that the values are only picked once. What i was doing was:

1) copy the array in ArrayTemp
2) grab a random number in the interval 0..[:len $ArrayTemp]
3) pick the item from the array
4) reconcatenate the array to eliminate the picked item

when the [:len array]=0 i had picked all values.

The prng used is an implementation of Marsaglia's MWC (semi-implementation because it uses floats). The big random number was modulo-normalized to the wanted interval. I only could get very poor distribution, only a little better if iterated a few times taking the result and xoring it with the seeds but to get acceptable results it costed too much cpu.

I turned then to an LFSR, and i'm pleased with the results, works faster and has better (apparent) randomization, doesn't need filters on its results nor any operation on arrays except for grabbing each time a unique value between 0 and [:len array]. It will initialize and give exactly all the numbers in a permutation of the interval, only one time.

The LFSR can be improved, especially the seeding and finding some nondeterministic way to traverse the sequence just one time and then stop, and also to not have a sequence truncated in the first run. I couldn't find solutions for these (and have to resume the main program) so for now i just put some ifs here and there to catch them. OTOH the central part, where the operations on the register are carried, is done using an optimal functional branch to decide whether to apply the tapmask, and that compensates for the extra fluff.

The time required (running inside a VM in an amd X2 1.7Ghz) for generating a 0..16384 sequence was 00:01:29.642229. I look forward to see how it does in a RB.

Neither algorithm has good crypto properties on its own, but for trivial operations they are useful. That said: a LFSR can be used as a base for some well known ciphers/checksums and a lot more, while mwc afaik is just a fast and simple prng with relatively big period.

Just starting to explore all this, and found it quite fascinating.

If you know how i can improve them, let me know!

multiply with carry
# usage: call after setting prngParams = {low;high}
# also see comments on maxnum

# GLOBALS ---------------------------------------------------------------

# debug settings
:global dbgMisc;

# params /result
:global prngParams;
:global prngResult;
:global prngMWC;

# START SCRIPT ----------------------------------------------------

# copy params to local scope
:local Low [:tonum [:pick $prngParams 0]];
:local High [:tonum [:pick $prngParams 1]];

:if ($High <= $Low) do={
	:set prngResult $Low;
} else={

# to fill distribution array set maxnum > 1
# and dbgMisc = true
	:local distribution {};
	:local contador 0;
	:local maxnum ($High / 4);
	:if ($maxnum > 1) do={
		:for i from=0 to=($High-$Low) do={
			:set distribution ($distribution,0);
		}
	}

	:do {
# time seed
		:local seed1 "";
		{
			:local time [:tostr [/system clock get time]];
			:for i from=0 to=([:len $time] -1) do {
				:local char [:pick $time $i ($i+1)];
				:if ($char!=":") do={:set seed1 ($seed1 . $char)};
			};
			:set seed1 [:tonum $seed1];
		}

# firewall counters seed
		:local seed2 0
		:foreach item in=[/ip firewall filter find bytes>0] do={
			:set seed2 ($seed2+[/ip firewall filter get $item bytes]);
		};
		:foreach item in=[/ip firewall nat find bytes>0] do={
			:set seed2 ($seed2+[/ip firewall nat get $item bytes]);
		};
		:foreach item in=[/ip firewall mangle find bytes>0] do={
			:set seed2 ($seed2+[/ip firewall mangle get $item bytes]);
		};

# uptime seed
		:local seed3 "";
		{
			:local uptime [:tostr [/system resource get uptime]];
			:for i from=0 to=([:len $uptime] - 1) do {
				:local char [:pick $uptime $i ($i+1)];
				:if ($char!=":") do={:set seed3 ($seed3 . $char)};
			};
			:set seed3 [:tonum $seed3];
		}

# cpu load seed
		:local seed4 [/system resource get cpu-load];

# conntrack seed
		:local seed5 0;
		{
			:foreach connection in=[/ip firewall connection find] do={
				:local src [:tostr [/ip firewall connection get $connection src-address ]];
				:local dst [:tostr [/ip firewall connection get $connection dst-address ]];
				:local timeout [:tostr [/ip firewall connection get $connection timeout ]];
				:for i from=0 to=([:len $src] - 1) do {
					:local char [:pick $src $i ($i+1)];
					:if ([:typeof [:find ":," $char]]="nil") do={:set seed5 ($seed5 + [:tonum $char])};
				};
				:for i from=0 to=([:len $dst] - 1) do {
					:local char [:pick $dst $i ($i+1)];
					:if ([:typeof [:find ":," $char]]="nil") do={:set seed5 ($seed5 + [:tonum $char])};
				};
				:for i from=0 to=([:len $timeout] - 1) do {
					:local char [:pick $timeout $i ($i+1)];
					:if ($char!=":") do={:set seed5 ($seed5 + [:tonum $char])};
				};
			};
		}

# acumulator seed
		:local seed6;
		:if ([:typeof $prngMWC]!="num") do={
			:set seed6 64;
		} else {
			:set seed6 $prngMWC;
		};

		:local mz (($seed1 ^ $seed3 ^ $seed6) + 1);
		:local mw (($seed2 ^ $seed4 ^ $seed5) + 1);

		:set mz (36969 * ($mz & 65535) + ($mz >> 16));
		:set mw (18000 * ($mw & 65535) + ($mw >> 16));

		:set prngMWC (($mz << 16) + ($mw & 65535));

# normalize
		:set prngResult ($prngMWC - ( $prngMWC / ($High + 1)) *  ($High + 1) + $Low);
		
		:if ($maxnum > 1) do={
			:set distribution ([:pick $distribution 0 ($prngResult-$Low)],([:tonum [:pick $distribution ($prngResult-$Low) ($prngResult-$Low+1)]]+1),[:pick $distribution ($prngResult-$Low+1) [:len $distribution]]);
		}

		:set counter ($counter+1);
	} while ($counter < $maxnum);

	:if ($dbgMisc) do={
		:for i from=0 to=([:len $distribution]-1) do={
			:put "$($i+$Low): $([:pick $distribution $i]* 100 / $maxnum )%";
		};
		:put $prngParams;
		:put $prngResult;
	};
};
LFSR
# Binary Galois LFSR 1/63 bits, periods 0-1..(2^63)-1
# based on:
# http://en.wikipedia.org/wiki/Linear_feedback_shift_register
# http://stackoverflow.com/questions/693880/create-random-number-sequence-with-no-repeats/3181296#3181296

# usage: call first time after setting prngParams={low,high,true} for initializing
# 0 <= Low <= High <= ((2^63)-1)
# then keep calling ((high - low) + 1) times with prngParams={low,high,false}
# to get the permutated interval one item at a time, in prngResult
# when no more numbers are inside the LFSR, prngResult starts to be -1

# GLOBALS ---------------------------------------------------------------

# debug settings
:global dbgMisc;

# params / result prng
:global prngParams;
:global prngResult;

# register state
:global prngSeed;
:global prngLFSR;
:global prngSeq;
:global prngMask;

# START SCRIPT ----------------------------------------------------


# copy params to local scope
:local Low [:tonum [:pick $prngParams 0]];
:local High [:tonum [:pick $prngParams 1]];
:local Initialize (!![:pick $prngParams 2]);

# adjust interval to 0..prngMax
:local prngMax ($High - $Low + 1);

# initialization
:if ($Initialize = true) do={

# time seed
	:local seed1 2;
	{
		:local time [:tostr [/system clock get time]];
		:for i from=0 to=([:len $time] -1) do {
			:local char [:pick $time $i ($i+1)];
			:if ($char!=":") do={:set seed1 ($seed1 * ([:tonum $char] + 1))};
		};
	}

# firewall counter seed
	:local seed2 0
	:foreach item in=[/ip firewall filter find bytes>0] do={
		:set seed2 ($seed2+[/ip firewall filter get $item bytes]);
	};
	:foreach item in=[/ip firewall nat find bytes>0] do={
		:set seed2 ($seed2+[/ip firewall nat get $item bytes]);
	};
	:foreach item in=[/ip firewall mangle find bytes>0] do={
		:set seed2 ($seed2+[/ip firewall mangle get $item bytes]);
	};

# uptime seed
	:local seed3 2;
	{
		:local uptime [:tostr [/system resource get uptime]];
		:for i from=0 to=([:len $uptime] - 1) do {
			:local char [:pick $uptime $i ($i+1)];
			:if ($char!=":") do={:set seed3 ($seed3 * ([:tonum $char] + 1))};
		};
	}

# cpu load seed
	:local seed4 ([/system resource get cpu-load] * 16);

# conntrack seed
	:local seed5 "";
	{
		:local t "";
		:local s "";
		:local d "";
		:foreach connection in=[/ip firewall connection find] do={
			:local timeout [:tostr [/ip firewall connection get $connection timeout ]];
			:for i from=0 to=([:len $timeout] - 1) do {
				:local char [:pick $timeout $i ($i+1)];
				:if ($char!=":") do={:set t "$t$char"};
			};
			:local src [:tostr [/ip firewall connection get $connection src-address ]];
			:for i from=0 to=([:len $src] - 1) do {
				:local char [:pick $src $i ($i+1)];
				:if ([:typeof [:find ":." $char]]="nil") do={:set s "$s$char"};
			};
			:local dst [:tostr [/ip firewall connection get $connection dst-address ]];
			:for i from=0 to=([:len $dst] - 1) do {
				:local char [:pick $dst $i ($i+1)];
				:if ([:typeof [:find ":." $char]]="nil") do={:set d "$d$char"};
			};
		};
		:set seed5 "$t$s$d";
	}

# checksum seeds
	:local prngPreSeed  (($seed1 ^ $seed2 ^ $seed3 ^ $seed4) | 1);
	:set prngSeed 0;
	:for i from=[:len $seed5] to=0 do={
		:local charnum [:tonum [:pick $seed5 $i]];
		:set prngSeed ($prngSeed ^ ($prngPreSeed / ($charnum + 1)));
	}

#	:put "prngPreSeed=$prngPreSeed, seed1=$seed1, seed2=$seed2, seed3=$seed3, seed4=$seed4, seed5=$seed5";
#	:put "prngSeed=$prngSeed";


# array of arrays with taps for maximal period, indexed by bit width (1..63)
	:local tapsfor {\
		"";\
		"";\
		"";\
		"3,2";\
		"4,3";\
		"5,3";\
		"6,5";\
		"7,6";\
		"8,6,5,4";\
		"9,5";\
		"10,7";\
		"11,9";\
		"12,6,4,1";\
		"13,4,3,1";\
		"14,5,3,1";\
		"15,14";\
		"16,15,13,4";\
		"17,14";\
		"18,11";\
		"19,6,2,1";\
		"20,17";\
		"21,19";\
		"22,21";\
		"23,18";\
		"24,23,22,17";\
		"25,22";\
		"26,6,2,1";\
		"27,5,2,1";\
		"28,25";\
		"29,27";\
		"30,6,4,1";\
		"31,28";\
		"32,22,2,1";\
		"33,20";\
		"34,27,2,1";\
		"35,33";\
		"36,25";\
		"37,5,4,3,2,1";\
		"38,6,5,1";\
		"39,35";\
		"40,38,21,19";\
		"41,38";\
		"42,41,20,19";\
		"43,42,38,37";\
		"44,43,18,17";\
		"45,44,42,41";\
		"46,45,26,25";\
		"47,42";\
		"48,47,21,20";\
		"49,40";\
		"50,49,24,23";\
		"51,50,36,35";\
		"52,49";\
		"53,52,38,37";\
		"54,53,18,17";\
		"55,31";\
		"56,55,35,34";\
		"57,50";\
		"58,39";\
		"59,58,38,37";\
		"60,59";\
		"61,60,46,45";\
		"62,61,6,5";\
		"63,62"\
	};

# determine $prngMax bit width
	:local highB $prngMax;
	:local bits 0;
	:while ($highB > 0) do={
		:set highB ($highB >> 1);
		:set bits ($bits + 1);
	};
	:if ($bits < 3) do={ :set bits 3 };

# get taps for a $bits wide register
	:local taps [:toarray [:tostr [:pick $tapsfor $bits]]];

# get feedback function
	:set prngMask 0;
	:for i from=0 to=([:len $taps]-1) do={
		:set prngMask ($prngMask | (1 << ([:tonum [:pick $taps $i]]-1)));
	};

# init register with the seed
	:set prngLFSR $prngSeed;

# init seq counter
	:set prngSeq 0;
};

# if we haven't finalized the sequence
:if ($prngSeq < $prngMax) do={
	:do {
# shift the register until it's high bit is 1 and then apply the mask
# we end up with next seq number
		:do {
			:set prngLFSR (($prngLFSR >> 1) ^ (-($prngLFSR & 1) & $prngMask));
		} while ($prngLFSR > $prngMax);

# if our first result is 0 we cheat
		:set prngResult ($prngLFSR-1);

		:if (($prngSeq=0) && ($prngLFSR=0)) do={
			:set prngLFSR ($prngLFSR+1);
		};
	} while (($prngSeq=0) && ($prngResult=-1));
	
# keep the seq count
	:set prngSeq ($prngSeq+1);
	
# move result to the original range
	:set prngResult ($prngResult+$Low);
} else={
# if we completed the sequence then exit
	:set prngResult -1;
};

:put $prngResult;
 
anf
just joined
Posts: 13
Joined: Mon Dec 22, 2008 11:26 pm

Re: random number generators

Thu May 05, 2011 12:44 am

Thanks a lot for your work and script sharing. I'll try to use this for the random delay that I need in one other script.
 
bburley
Frequent Visitor
Frequent Visitor
Posts: 80
Joined: Thu Nov 18, 2010 7:22 am
Location: Alberta, Canada

Re: random number generators

Thu May 05, 2011 7:53 am

That is an awesome script, I can't even try to follow it through until my workload lets up.

I made a very poor attempt at getting a random number out of the MikroTik but I rushed through it and it wasn't very well thought out. I was going to make another attempt, but after seeing your script, I may not bother. I can tell what kind of work went into it and I haven't even tried it yet.

When you mentioned reconcatenating the array after using a number, I wondered about using an extra dimension in the array with the extra column used to "mark" picked numbers. When it was all marked you could flip a toggle and start unmarking picked numbers. It may use a little more memory but I wonder if it would be faster.
 
juaco
just joined
Topic Author
Posts: 15
Joined: Tue Nov 09, 2010 12:15 am

Re: random number generators

Thu May 05, 2011 4:37 pm

Thanks guys :) just glad to know it is useful to you.

@bburley:

I think the LFSR should be more optimal than extracting items / reconcatenating the array (would have to :time it though, to get a more accurate estimation of how each performs):

Array reconcatenation
  • seed prng (this could be done just one time)
  • call prng to get a prn
  • normalize prn to get an index
  • pick the array element
  • reconcatenate the array: split, rejoin without the element
LFSR
  • initialize/seed LFSR
  • call LFSR in turns to get an index already in the needed range
  • pick the array element
So it goes down to how fast the LFSR performs against another prng, as the remaining operations with the array method would seem less ram/cpu efficient.

Hope this helps

Who is online

Users browsing this forum: No registered users and 5 guests