问题
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?
回答1:
JavaScript is a dialect of ECMAScript. The ECMAScript-262 standard fails to specify Math.random
precisely. In clause 20.2.2.7, it says:
20.2.2.27 Math.random ( )
Returns a Number value with positive sign, greater than or equal to 0 but less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-dependent 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 s • f • 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 providers 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.
来源:https://stackoverflow.com/questions/52089553/what-are-the-chances-of-math-random-returning-0