Skip to content

Instantly share code, notes, and snippets.

@laurentsenta
Created June 2, 2014 17:33
Show Gist options
  • Select an option

  • Save laurentsenta/15d7f6fcfc2987176b54 to your computer and use it in GitHub Desktop.

Select an option

Save laurentsenta/15d7f6fcfc2987176b54 to your computer and use it in GitHub Desktop.
Seedable Random Number Generator in Typescript
export class RNG {
private seed:number;
constructor(seed:number) {
this.seed = seed;
}
private next(min:number, max:number):number {
max = max || 0;
min = min || 0;
this.seed = (this.seed * 9301 + 49297) % 233280;
var rnd = this.seed / 233280;
return min + rnd * (max - min);
}
// http://indiegamr.com/generate-repeatable-random-numbers-in-js/
public nextInt(min:number, max:number):number {
return Math.round(this.next(min, max));
}
public nextDouble():number {
return this.next(0, 1);
}
public pick(collection:any[]):any {
return collection[this.nextInt(0, collection.length - 1)];
}
}
@makoConstruct
Copy link

This is going to be biased against drawing min and max. Basically, imagine if min were 0 and max were 2, and imagine how the map of reals from 0 to 2 map through Math.round. 0 to 0.5 will map to 0. 0.5 to 1.5 will map to 1. 1's range, then, is twice as large as 0 (same goes for 2). This is not desirable.

I'd recommend changing

var rnd = this.seed / 233280;
to
var rnd = this.seed / 233281;

and changing

Math.round
to
Math.floor

the output range will now go from min to max-1, and that is fine and ordinary for range specifiers and programmers should generally expect that kind of behavior.

@ashelleyPurdue
Copy link

ashelleyPurdue commented Sep 4, 2018

Have you considered submitting this to npm, or is it too simple?

@pjbaron
Copy link

pjbaron commented Oct 15, 2018

In reference to @makoConstruct suggestions:

  • it is not necessary to change the value, the modulus already reduces the range to "less than" so the division is correct as written
  • it is however critical to change the 'round' to a 'floor' (because otherwise the integer value '0' is picked only half as many times as other values)

I ran a quick test to check distribution after this change:

       // NOTE: I modified the class to static, the logic remains the same
        RNG.seed = 12345;
        let r:Array<number> = new Array<number>(11);
        for(let i:number = 0; i < 11; i++)
            r[i] = 0;
        for(let i:number = 0; i < 1000000; i++)
            r[RNG.nextInt(0,10)]++;
        for(let i:number = 0; i < 11; i++)
            console.log(i + " = " + r[i]);

which results in values:

0 = 100004
1 = 100016
2 = 99986
3 = 100000
4 = 100026
5 = 100008
6 = 99956
7 = 100035
8 = 99977
9 = 99992
10 = 0

(Similar tests with different seeds and ranges also produced reasonable looking distributions)
EDIT: I recommend throwing an 'abs' in for the seed too, I think negative seed values might mess up the algebra

@jppresents
Copy link

When changing to floor, you also have to change pick (remove the -1):

public pick(collection:any[]):any {
return collection[this.nextInt(0, collection.length)];
}

otherwhise the last element is never picked, because max is no longer inclusive.

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