6

Like the asker of this question, I was wondering why Math.ceil(Math.random() * 10) was not preferred over Math.floor(Math.random() * 10) + 1, and found that it was because Math.random has a tiny (but relevant) chance of returning 0 exactly. But how tiny?

Further research told me that this random number is accurate to 16 decimal places... well, sort of. And it's the "sort of" that I'm curious about.

I understand that floating point numbers work differently to decimals. I struggle with the specifics though. If the number were a strict decimal value, I believe the chances would be one in ten billiard (or ten quadrillion, in the American system) - 1:1016.

Is this correct, or have I messed up, or does the floating point thing make a difference?

Isaac Reefman
  • 537
  • 1
  • 8
  • 26
  • Why does returning zero from `Math.random` matter to you? – Tim Biegeleisen Aug 30 '18 at 05:07
  • 1
    @TimBiegeleisen As I mentioned in the question, it has implications for using it to generate random integers. I *could* go with the common way of doing things (using .floor...+1 instead of .ceil) but I'd like to know if it's *actually* necessary to prevent inaccuracies (or code potentially breaking every 1 in a billiard iterations). Plus it may have implications on the *actual* randomness of this method. – Isaac Reefman Aug 30 '18 at 05:10
  • A possible hackish fix: Check for exact equality to zero, and, if true, then add whatever the smallest possible number is in JavaScript. This would have _almost_ a negligible effect on the distribution, I think, but would avoid the zero problem. – Tim Biegeleisen Aug 30 '18 at 05:12
  • @TimBiegeleisen I mean, yeah, but adding in an `if (result == 0){result =+ Number.MIN_VALUE}` seems more unwieldy than just going `.floor...+1`. Interesting to note though. – Isaac Reefman Aug 30 '18 at 05:16
  • what about `Math.ceil((Math.random() + Number.MIN_VALUE) * 10)` – Jaromanda X Aug 30 '18 at 05:48
  • d'oh ... `Math.ceil(Math.random() * 10) || 1` – Jaromanda X Aug 30 '18 at 06:25
  • @JaromandaX the first one has a similarly miniscule chance of going higher than the established bounds, and the second one... I'm not sure how that would work... what are the conditions for returning 1 instead of 0? Also I think the chance of 0 comes out of the chance of 10, so I'd imagine you'd need `|| 10`... – Isaac Reefman Aug 30 '18 at 06:54
  • First one won't go over maximum. Try plugging in 1 instead of random – Jaromanda X Aug 30 '18 at 07:42
  • In fact you can use 1e-17 instead of minvalue due to the number of significant digits in a JavaScript number – Jaromanda X Aug 30 '18 at 07:43
  • @JaromandaX h-uh. Why though? 10.00...1 rounded up is 11... Now I'm even more confused! – Isaac Reefman Aug 30 '18 at 08:07
  • 1
    Significant digits – Jaromanda X Aug 30 '18 at 08:17

1 Answers1

9

JavaScript is a dialect of ECMAScript. The ECMAScript-262 standard fails to specify Math.random precisely. The relevant clause says:

Math.random ( )

Returns a Number value with positive sign, greater than or equal to +0 but strictly less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-defined algorithm or strategy. This function takes no arguments.

Each Math.random function created for distinct realms must produce a distinct sequence of values from successive calls.

In the absence of a complete specification, no definitive statement can be made about the probability of Math.random returning zero. Each ECMAScript implementation may choose a different algorithm and need not provide a truly uniform distribution.

ECMAScript uses the IEEE-754 basic 64-bit binary floating-point format for its Number type. In this format, the significand (fraction portion) of the number has 53 bits. Every floating-point number has the form sf • 2e, where s (for sign) is +1 or −1, f (for fraction) is the significand and is an integer in [0, 253), and e (for exponent) is an integer in [−1074, 971]. The number is said to be normalized if the high bit of f is set (so f is in [252, 253)). Since negative numbers are not a concern in this answer, let s be implicitly +1 for the rest of this answer.

One issue with distributing random numbers in [0, 1) is that the representable values are not evenly spaced. There are 252 representable values in [½, 1)—all those with f in [252, 253) and e = −53. And there are the same number of values in [¼, ½)—all those with f in [252, 253) and e = −54. Since there are the same number of numbers in this interval but the interval is half as long, the numbers are more closely spaced. Similarly, in [⅛, ¼), the spacing halves again. This continues until the exponent reaches −1074, at which point the normal numbers end with f = 252. The numbers smaller than that are said to be subnormal (or zero), with f in [0, 252) and e = −1074, and they are evenly spaced.

One choice about how to distribute the numbers for Math.random is to use only the set of evenly spaced numbers f • 2−53 for f in [0, 253). This uses all the representable values in [½, 1), but only half the values in [¼, ½), one-fourth the values in [⅛, ¼), and so on. This is simple and avoids some oddities in the distribution. If implemented correctly, the probability zero is produced is one in 253.

Another choice is to use all the representable values in [0, 1), each with probability proportional to the distance from it to the next higher representable value. Thus, each representable number in [½, 1) would be chosen with probability 1/253, each representable number in [¼, ½) would be chosen with probability 1/254, each representable number in [⅛, ¼) would be chosen with probability 1/255, and so on. This distribution approximates a uniform distribution on the reals and provides finer precision where the floating-point format is finer. If implemented correctly, the probability zero is produced is one in 21074.

Another choice is to use all the representable values in [0, 1), each with probability proportional to the length of the segment in which the representable value is the nearest representable value of all the real numbers in the segment. I will omit discussion of some details of this distribution except to say it mimics the results one would get by choosing a real number with uniform distribution and then rounding it to a representable value using the round-to-nearest-ties-to-even rule. If implemented correctly, the probability zero is produced is one in 21075. (One problem with this distribution is that a uniform distribution over the reals in [0, 1) will sometimes produce a number so close to 1 that rounding produces 1. This then requires either that Math.random be allowed to return 1 or that the distribution be fudged in some way, perhaps by returning the next lower representable value instead of 1.)

I will note that the ECMAScript specification is sufficiently lax that one might assert that Math.random may distribute the numbers with equal probability for each representable value, ignoring the spacing between them. This would not mimic a uniform distribution over the real numbers at all, and I expect very few people would favor it. However, if implemented, the probability zero is returned is one in 1021 • 252, because there are 252 normalized numbers with exponents from −53 to −1074 (1020 values of e), and 252 subnormal or zero numbers.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312