Skip to content

Instantly share code, notes, and snippets.

@DarrenSem
Last active December 21, 2023 18:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DarrenSem/640f0716a6be14ee0ac7a338051a4bee to your computer and use it in GitHub Desktop.
Save DarrenSem/640f0716a6be14ee0ac7a338051a4bee to your computer and use it in GitHub Desktop.
randomWithSeed.js - use rnd() after rnd = createRandomWithSeed( seed [0+] = Date.now() ) - Math.random is less than 2x faster than this 'good enough' SEED-able version
// randomWithSeed.js - use rnd() after rnd = createRandomWithSeed( seed [0+] = Date.now() ) - Math.random is less than 2x faster than this 'good enough' SEED-able version
let createRandomWithSeed = seed => {
seed = Math.abs(isNaN(seed) ? Date.now() : seed);
return () => {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
};
};
// R=s=>(s=Math.abs(isNaN(s)?Date.now():s),_=>(s=(9301*s+49297)%233280,s/233280));
// ^ 79 chars minified (FASTER than the "Modulus 4294967296" combo, but it visibly repeats earlier)
let createRandomWithSeed_SLOWER_BUT_LONGER_PERIOD = seed => {
seed = Math.abs(isNaN(seed) ? Date.now() : seed);
return () => {
seed = (seed * 1664525 + 1013904223) % 0x100000000; // 0x100000000 (2 ^ 32) = 4294967296
return seed / 0x100000000;
};
};
// R=s=>(s=Math.abs(isNaN(s)?Date.now():s),_=>(s=(1664525*s+1013904223)%4294967296,s/4294967296));
// ^ 95 chars minified (SLOWER than the "Modulus 233280" combo, but this does not visibly repeat as early)
// based on http://indiegamr.com/generate-repeatable-random-numbers-in-js/ , e.g. http://demos.indiegamr.com/jumpy/seededRandomLevel/?seed=1.57251
// ( found via https://github.com/bgrins/TinyColor/blob/v2/src/random.ts )
let seededRandom_original = (max, min) => {
max = max || 1;
min = min || 0;
Math.seed = (Math.seed * 9301 + 49297) % 233280;
var rnd = Math.seed / 233280.0;
return min + rnd * (max - min);
};
@DarrenSem
Copy link
Author

Note to self: the choices for the 3 numbers can have big impact on results. For one thing modulus # should likely be large and a power of 2 (faster processing esp. in modern CPUs).

For example, ChatGPT suggested these 2 combos:

With modulus = 0x100000000 (2 ^ 32), Multiplier=1664525, Increment=1013904223

With modulus 2 ^ 31, Multiplier=1103515245, Increment=12345

(And do not haphazardly mix-and-match MAGIC NUMBERS -- they were fine-tuned(?))
🤔

@DarrenSem
Copy link
Author

Also note to self:

Most examples of RNG with seed (including, but not only, MT) seem to follow a pattern of having a Class or at least a factory function.
So instead of

let randomWithSeed = ( seed = Math.seed ?? +new Date ) => {
...do things with (and to) seed / Math.seed (which limits us to just one randomizer :-( )

I should instead use something like

let createRandomWithSeed = ( seed = Math.seed ?? +new Date ) => {
return () => {
...do things with only this closure's seed, and not Math.seed which is not actually needed -- and also now this is obviously more scalable...
};
}

PS: +new Date might minify smaller but it is WAY SLOWER than Date.now()!



@DarrenSem
Copy link
Author

DarrenSem commented Dec 18, 2023

Also note to self:

Most examples of RNG with seed (including, but not only, MT) seem to follow a pattern of having a Class or at least a factory function. So instead of

let randomWithSeed = ( seed = Math.seed ?? +new Date ) => {
...do things with (and to) seed / Math.seed (which limits us to just one randomizer :-( )

I should instead use something like

let createRandomWithSeed = ( seed = Math.seed ?? +new Date ) => {
return () => {
...do things with only this closure's seed, and not Math.seed which is not actually needed -- and also now this is obviously more scalable...
};
}

PS: +new Date might minify smaller but it is WAY SLOWER than Date.now()!


AKA this:

let createRandomWithSeed = (seed) => {
  seed = Math.abs(seed);
  if(isNaN(seed))seed = Date.now();
  return () => {
    seed = (seed * 9301 + 49297) % 233280;
    return seed / 233280;
  };
};
// TODO: try these other 2 sets of numbers: 
// With Multiplier = 1664525, Increment = 1013904223, modulus = 0x100000000 (2 ^ 32) = 4294967296
// With Multiplier = 1103515245, Increment = 12345, modulus = 0x80000000 (2 ^ 31) = 2147483648



let SEED = 42;

let rnd = !!"RANDOM_WITH_SEED" ? createRandomWithSeed(SEED) : Math.random;
console.log( rnd() );

let rndA = createRandomWithSeed(7);
console.log( rndA() );

let rndB = createRandomWithSeed(7);
console.log( rndB(), rndB() );

console.log( rndA() );

console.log( rnd() );

^ curious what timing difference if any due to the signature being () instead of (seed)

@DarrenSem
Copy link
Author

DarrenSem commented Dec 21, 2023

Timing test showed "2147483648" (3rd combo) to be faster than "4294967296" (2nd combo)... but visible tests show 3rd repeats very early.
IN CONCLUSION, if PERFECTION re. periodization is most important use 2nd combo (4294967296) else 1st (233280, same speed as 2147483648 aka 3rd combo)

let createRandomWithSeed_FASTER_REPEATS_EARLY=s=>(s=Math.abs(isNaN(s)?Date.now():s),_=>(s=(9301*s+49297)%233280,s/233280));
let createRandomWithSeed_SLOWER_REPEATS_LATER=s=>(s=Math.abs(isNaN(s)?Date.now():s),_=>(s=(1664525*s+1013904223)%4294967296,s/4294967296));
// let rnd = createRandomWithSeed(SEED); // then use rnd() instead of Math.random()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment