关于Math.random()生成指定范围随机数的两项技术疑问
问题1:Math.random() * 4 - 2是否能生成(-2, 2)的随机数?
先明确基础规则:JavaScript里Math.random()返回的是**左闭右开区间[0, 1)**的double类型随机数。我们拆解下这个表达式的范围:
Math.random() * 4的结果区间是[0, 4)- 减去2后,最终范围是**[-2, 2)**
所以严格来说,这个写法会生成包含-2但不包含2的随机数,并非你说的(-2, 2)——虽然Math.random()返回0的概率极低(几乎可以忽略),但从语言规范和数学定义上,它确实可能产出-2。
至于你提到的“实际很少得到正数”,这只是小样本随机带来的错觉:[0,4)被平分为[0,2)和[2,4)两部分,对应映射后的[-2,0)和[0,2),两者理论概率完全相等,当生成足够多的样本后,正负数值的占比会趋近于1:1。
如果想要严格的(-2, 2)区间,可以加个小循环排除-2的情况(实际场景中几乎没必要,因为概率太低):
let num; do { num = Math.random() * 4 - 2; } while (num === -2);
问题2:Math.random() * 2.0000000000000001 - 1能否生成[-1, 1]包含两端的随机有理数?
答案是不行,核心问题出在JavaScript的double类型精度限制上:
double用53位有效数字存储数值,当数值为2时,它的下一个可精确表示的数是2 + 2^-52(约等于2.000000000000000444)。你写的2.0000000000000001比这个临界值小,会被double自动舍入为2,这就导致你的表达式等价于Math.random() * 2 - 1,生成的范围还是[-1, 1),永远到不了1。
如果想要生成包含1的[-1, 1]区间,可以用2 + Number.EPSILON(Number.EPSILON就是2^-52,是double中1和大于1的最小可表示数的差值):
Math.random() * (2 + Number.EPSILON) - 1
不过要注意:即便这样,你也只能生成[-1, 1]范围内可被double表示的离散值,而非所有有理数——浮点数的本质就是离散的,无法覆盖无限多的有理数。
内容的提问来源于stack exchange,提问作者Uzoma Ohajekwe

