# 3 Amazing ways to generate random numbers without Math.random()

###
Vijay Koushik, S. ๐จ๐ฝโ๐ป
*
Originally published at
svijaykoushik.github.io
on
*
ใป9 min read

Have you ever played an online spin to win game? You know! The one that pops up as an Ad with the message โSpin this to win the latest, feature-rich Samsung smartphone!โ? I did. Because who doesnโt want the latest smartphone for free? Sadly, no matter how many times I played, I didnโt win. Never the less, I was rewarded. Rewarded with the curiosity to know how this deceitful game worked. With a quick google search, I found that it worked by using a random number generator (RNG). Itโs probably Math.random()

Butโฆ How does a computer, a device that is designed to work as per the instructions given by a human generate random numbers? Answer, it doesnโt. And it canโt. That is why itโs called a โpseudoโ random number generator (PRNG). It means itโs a fake. Itโs a knock-off.

## Why a knock-off?

A True RNG needs additional hardware that can use real-world random phenomena from throwing dice ๐ฒ to measuring radiation from a radioactive material as an input to generate random numbers. Wow! Using the randomness in radioactive decay just to generate a number is mind-blowing! ๐คฏ Take a moment to let that sink in.

But this additional hardware is costly and who would willingly carry a radioactive material in their pockets except for Lex Luthor? That is why everyone agreed to settle with a PRNG.

PRNG is not a single standard algorithm used everywhere. I was struck dumb when I found out that there are not 1, not 2 but 28 different algorithms created by very smart people over the past 7 decades.

Let me show you 3 amazing ways to replace `Math.random()`

in Javascript.

## How do they work?

Pseudo Random Number Generator (PRNG) refers to an algorithm that uses mathematical formulas to produce sequences of random numbers.

https://www.geeksforgeeks.org/pseudo-random-number-generator-prng/

Though I couldnโt research all the 28 algorithms in a short time, I looked up 3 good ones. I first thought they used complex mathematical derivatives involving 100s of lines of code. Nope! I was wrong. With 2 to 5 lines of code involving basic arithmetic operations, theyโre unbelievably simple. This makes it easier for beginners to understand.

All 3 algorithms and PRNGs, in general, followed these common steps

All these algorithms accept an input called the seed ๐ฑ number. This is the base number on which the formula is applied. Some algorithms can take other inputs as required by the mathematical operation to be performed.

They, then apply the inputs on the formula and the result generated is the random number.

The generated number is used as the seed for the next run.

These steps are repeated to create a sequence of numbers that make us believe they are random.

An unique property that separates PRNGs from true RNGs is that the sequences generated by PRNGs inevitably repeat themselves at one point of time.

## 1. Middle square method (MSM)

Invented by John von Neumann and described in 1946, the Middle Square Method (MSM) is the first-ever method designed to generate pseudo-random number sequences ^{[1]}. Implementing this method is a childโs play. For an n-digit random number sequence,

Start with an n-digit number as the seed. Letโs say itโs a 2-digit number 42.

Square it. Here, the square of 42 is 1764.

Extract the middle n-digits of the squared number to get the next number in our sequence. In our case, the next number would be 76.

Use the result as the seed and repeat steps 1-4 for the next cycle.

Representation of middle square method

The simple nature of the algorithm is used as an exercise for beginner programmers to check their knowledge in the language they learned in Bootcamp. So, hereโs my implementation in JS to help them.

```
/**
* Middle Square Method implementation in JavaScript
* for a 2-digit random number sequence
**/
var seed;
function middleSquareMethod(){
var result = (seed * seed).toString().slice(1, 3); // extracting the middle value.
seed = parseInt(result);
return parseInt(result);
}
```

There is a problem with this approach. There are exceptional numbers whose square value have odd digits which makes it difficult to extract the middle digits like in the case of 15. Squaring 15 would result in 225. And we cannot accept 2 as the middle number since we need two digits. To solve this, we pad zeroes in front of the square value to make it even digits. Now 225 becomes 0225 which makes it easy to extract the middle 2 digits which is 22. After rectifying the problem, the code looks like this.

```
/**
* Middle Square Method implementation in JavaScript
* for a 2-digit random number sequence
**/
var seed = 42;
function middleSquareMethod(){
var result = (seed * seed).toString().padStart(4,"0").slice(1, 3);
// pad with zero when necessary and extract the middle value.
seed = parseInt(result);
return parseInt(result);
}
```

With just three lines of code, we could generate a maximum of 8^{n} numbers for an n-digit number after which the sequence repeats itself. There is a pitfall though. Some seeds can cause the algorithm to have a shorter cycle like the seed 25, which causes the algorithm to repeat 25 indefinitely.

## 2. The Linear Congruential Generator (LCG) algorithm

This fascinating algorithm uses more math than MSM. The LCG uses a linear equation that involves congruence operation for the generation of a random sequence of numbers. โWhoa! Whatโs all these fancy terms?โ I can hear you exclaiming. Let me explain.

*Linear means an algebraic equation that has no variables raised to the power greater than one.*

*Congruential means an equation that uses modulus division operation.*

With its jargons, the algorithm might seem sophisticated. But, itโs very simple to understand as it uses very basic algebra and arithmetic operations. It uses this particular equation **X _{n+1} = (aX_{n} + c) mod m**. Alright! Alright! No more math terms. Iโll translate it to programmer readable from. The translated equation is,

`X = (a * X + c) % m`

*Where X is the seed. Akin to the MSM the result is used as the seed for the next cycle.*

*a โ is the multiplier*

*c โ is the increment and*

*m โ is the modulus*

It has the following conditions

m > 0, duh! divide by zero is impossible

0 < a < m

0 โค c < m and

0 โค X < m

Since this is a simple equation, solving this is a piece of cake for computers. In the case of the MSM, data conversion from number to string and back to number is required, which are heavy on the CPU. For this reason, LCG is the oldest and best-known random number generator algorithm ^{[2]}. And hence takes 2^{nd} in the list.

After all, both the increment and the seed can take the value zero care should be taken that both do not take zero or else it just spits out a sequence of zeroes.

Hereโs how I wrote the LCG in JS

```
/**
* Implementation of the Linear congruential generator
* algorithm in JavaScript
*/
var X,a,c,m;
linearCongruentialGenerator(){
X = (a * X + c) % m;
return X;
}
```

It takes just two lines. Just two! I did a double-take after writing it ๐ฒ. Itโs really incredible to see a simple equation to achieve something this big. This just increased my respect for Math.

With the right combination of inputs, we could generate a very long sequence. Longer than the MSM before it starts repeating itself. In my example I used the values a = 1664525, m = 2^{32} and c = 1013904223 as used in Numerical Recipes ^{[3]}.

## 3. Xorshift algorithm

The third algorithm in the list is the Xorshift algorithm. Iโve saved this special one for the last. If the MSM is easier to understand for humans and the LCG is understandable by both humans and computers then the XOR shift algorithm is easily understandable only to computers. Because this method, as the name suggests, uses the special and rarely used binary operations Xor and bit shift.

Please bear with me. This one uses a lot of computer science terms. I chose this one because I thought I would never get to use those binary operators in my life just like how I thought I could never see Ash Ketchum win the Pokรฉmon league championship.

Let me break down the algorithm. Bit shifting works by shifting the bits in the binary number either to the left or right. The result is a completely different number. For 1-bit shift to left, each bit is shifted one place to the left. The empty space is filled with 0 and the shifted out bit is discarded. And for a 5-bit shift to the left, single-bit shift operation is repeated 5 times. Here is an example:

The binary equivalent of 42_{10} in a 16-bit representation is 0000000000101010_{2}.

After shifting 5 bits to the left it becomes 0000010101000000_{2} which is the binary equivalent of 1344_{10}.

Representation of 1-bit shift left operation in a 8 bit system

And if we shift the binary equivalent of 2524_{10} โ 0000100111011100_{2} 5 bits to the right it becomes 0000000001001110_{2} which is 78_{10} in decimal. The rest of the bits on the right side is discarded.

Representation of 1-bit shift right operation in a 8 bit system

The bit shift operation, as you can see, requires only one operand and the result is a completely different number. On the other hand, the Xor operation requires two operands. XOR short for Exclusive OR operation compares the bits of two binary numbers and sets the bit of the result to 1 only when one of the bits in comparison is 1. Continuing with the previous example the Xor of 42 and 2524 takes place like this:

4210 โ 0000000000101010_{2}

252410 โ 0000100111011100_{2} XOR - 0000100111110110_{2} which is the equivalent of 2550_{10}.

Representation of xor operation in a 8 bit system

Xor also results in a different number. This algorithm combines the power of these two operations. And, here is my implementation of Xorshift in JavaScript.

```
/**
* Implementation of XorShift
* algorithm in JavaScript
*/
var seed;
function xorShift(){
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return seed;
}
```

This method performs consecutive bits shifts and Xor operations on the seed which creates a random sequence containing both positive and negative numbers. The constants 13, 17 and 5 in the algorithm is from the list of triplets suggested in the paper describing the Xor-shift algorithm ^{4}. This algorithm works directly in binary, the language of the computers, which makes it faster than the LCG.

If you want only positive numbers, you can take the 2โs complement of the seed if itโs negative before returning the value. This can reduce the performance with the inclusion of a condition.

```
/**
* Implementation of XorShift
* algorithm in JavaScript
* with 2's complement
*/
function xorShift(){
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return (seed <0)?~seed+1: seed;
//2's complement of the negative result to make all numbers positive.
}
```

Computers store the positive and negative numbers (called signed integers) as binary numbers in 2โs compliment from. The leftmost bit (the most significant bit) is reserved to represent the sign of the number. 0 represents a positive (+) sign and 1 stands for negative (-) sign.

Do you know what a twoโs complement is? Donโt worry Iโll explain.

In 2โs complement, A binary number say 11111111 11010011 (-45_{10}) is taken and its bits are flipped. Meaning, 0s are made to 1s and vice versa. And finally, 1_{2} is added to the flipped number. The result 00000000 00101101_{2},is the positive form of the number (45_{10}).

-45

_{10}โก 11111111 11010011_{2}

Bit inversion00000000 00101100

_{2}

Add 100000000 00101100

_{2}+ 1_{2}00000000 00101101

_{2}โก 45_{10}

Thus, in our algorithm, we always end up with positive numbers.

## Conclusion

This article is just the tip of the iceberg in the rabbit hole of PRNGs. I wanted to share you the different ways to replace `Math.random()`

. All these samples give out whole numbers which is the complete opposite of Math.random(). Math.random() spits out random decimal numbers only between 0 and 1. Iโll leave the conversion as an exercise to you. You can use ES5 features like generator functions to implement these. If someone does that, please post it in the comments.

Thanks for reading ๐

## References

^{[1]}โList of pseudo random number generatorsโ, Wikipedia.^{[2][3]}โLinear congruential generatorโ, Wikipedia.^{[4]}โXorshift RNGsโ [pdf] by Marsaglia, George, The journal of statistical software.

I think it's nice to mention that bit shifting isn't just a bit operation which produces a completely different number but instead a multiplication or division by a power of two so 42*2

^{5}= 1344Umm. No! computers(CPUs) do not handle bit shifts like you mentioned. Rather it takes place entirely in Binary in the CPU registers. The instruction

`42*(2**5)`

consumes more resources of the CPU than`42<<5`

It produces the same result that's all I said. Nevertheless in C++ it's compiled to the same (42*32) and 42 << 5 both to the latter because bit shifting is faster.

I think it is better to be

explicitin this case.๐Without knowing the target system, using multiplication is a risk.๐

I didn't want that it uses multiplication. I thought it would be nice to mention that the result (mathematically) is the same instead of: it's changing the result to make a completely new number. Make it clear to the reader what this does and how to visualize it. Then people who don't know it can use it if they want to multiply by a power of two somewhere.

Oh... Okay ๐๐ฝ. You're right. Why I didn't do is because,I thought it would be confusing to the readers.

Fair enough. Maybe an extra box for extra information but understand your point.

Actually you don't need radioactive stuff; some computer use a very simple piece of hardware: the microphone. Sound (fan, conversation...) is indeed totally random and unpredictable

Modern cpus also tend to have randomness modules in them, that generally look at everything that is running and timing of interrupts, like key presses or WiFi packets.

Thank you for pointing this out๐. I didn't know about the randomness modules in CPUs ๐ฐ

They are generally exposed through the OS/Kernel's randomness functions, which are then used again in other languages and programs. I don't know if Math.rand() uses it or not though.

I don't want to toot my own horn, but I did write an article a while back on pseudo-randomness vs randomness in OS systems: dev.to/artis3n/random-vs-pseudorandom. You both may be interested in it.

I wanted to convey the scale of how far we've gone to generate true random numbers โบ. Using the microphone sounded a bit creepy to me. That's why I didn't include it. ๐

This is a really cool article, but I'd advise anyone not to use these. If you really need integer random numbers and don't want to convert from a floating point, use a library. If you're doing something serious. Otherwise these low-quality PRNGs are a cool thing to use to simplify and spice up your code that will never ever make it into a public website or a business.

Hi, my code is a basic implementation of the algorithms. It doesn't cover all the parameters required for a strong PRNG. I wanted to show how easy it is to implement these algorithms ๐

It's a cool article regardless, I just wouldn't like to use them in anything significant for the reasons your provided. But it's still cool! ๐

I like this blog, I like how you ELI5 PRNG concepts and even implement them. While all that's cool, I'm of the opinion that one shouldn't be generally replacing/implementing Math.Rand unless necessary. Abstraction is good, usually.

Thank you โบ. You are right. You can't use the code directly in production.The goal of this post is to show how easy it is to implement the PRNGs and I've left the implementation details to the readers.

Dude, like, It's literally what I need for my class project. I have to generate randomness from scratch ๐๐

I'm glad it helped ๐

Implementing 'randomness' in my programs has been a simple challenge for me: I use the timestamp or to be specific, the count of milliseconds elapsed since EPOCH, which in JavaScript is as simple as

`new Date().getTime()`

. I know it's probably not the best methods out there and I didn't even bother to read more about it. However, with my 'cheap' method to generate random numbers, there's always a seed: the time at that exact moment.I liked all of the three methods explained in this post but have a question: like we mentioned that the previous random number forms a seed to generate the next one, I'm wondering (I couldn't find it in the post) for what the seed would be for the first random number in the sequence?

Why, ho why would you present PRNGs without a single warning about the use of cryptographically secure randomness where security is needed?

I didn't want to confuse you people with cryptography and cryptographically secure PRNGs.

Middle square method without string manipulation:

Wow! Great work ๐๐๐๐! Why didn't I think of this? ๐

For no encryption uses, do not forgot tha old, easy, fast, venerable, 997 multiplier algorithm.

Those are too difficult for me xD. I'd rather just use math.random

Sure! ๐

Thanks Very helpfull !