所以我非常熟悉旧的
Math.floor(Math.random() * (max - min + 1)) + min;
并且这对于小数字非常有效,但是当数字变大时,这很快变得有偏差并且只返回低于它的数字零(例如,0
和1e100
之间的随机数几乎将总是(每次我测试过,因此我使用for循环生成大量数字,所以几十亿次)返回[x]e99
)。是的,我等了很长时间才让程序生成那么多数字,两次。到目前为止,可以安全地假设输出始终为[x]e99
用于所有实际用途。
接下来我尝试了这个
Math.floor(Math.pow(max - min + 1, Math.random())) + min;
虽然它适用于大范围,但它适用于小范围。所以我的问题是如何做到这两点 - 能够在没有任何偏见的情况下产生小的和大的随机数(或者最小的偏差到不明显的地方)?
注意:我使用Decimal.js来处理-1e2043
<范围内的数字x< 1e2043
但由于它是相同的算法,我在上面显示了vanilla JavaScript表单以防止混淆。我可以毫不费力地将一个香草答案转换为Decimal.js,所以请随意回答。
注意#2:我想要获得大数字的几率。例如,1e33
示例中1e90
的赔率与0-1e100
相同。但与此同时,我需要支持较小的数字和范围。
答案 0 :(得分:2)
你的问题很精确。这就是你首先使用Decimal.js的原因。与JS中的每个其他数字一样,Math.random()
仅支持53位精度(某些浏览器甚至用于创建仅32位的随机性)。但是你的值1e100
需要333位的精度。因此,公式中将丢弃较低的280位(100个中的〜75个小数位)。
但是Decimal.js提供了random()
方法。为什么不使用那个?
function random(min, max){
var delta = new Decimal(max).sub(min);
return Decimal.random( +delta.log(10) ).mul(delta).add(min);
}
另一个"问题"为什么你得到e+99
这么多的值就是概率。对于范围0 .. 1e100
,得到一些指数的概率是
e+99 => 90%,
e+98 => 9%,
e+97 => 0.9%,
e+96 => 0.09%,
e+95 => 0.009%,
e+94 => 0.0009%,
e+93 => 0.00009%,
e+92 => 0.000009%,
e+91 => 0.0000009%,
e+90 => 0.00000009%,
and so on
因此,如果您生成了100亿个数字,从统计上来说,您将获得最多1e+90
的单个值。这是可能性。
我想为大数字消除这些可能性。 1e33应具有与1e90相同的赔率,例如
好的,然后让我们在min ... max
范围内生成10 random 。
function random2(min, max){
var a = +Decimal.log10(min),
b = +Decimal.log10(max);
//trying to deal with zero-values.
if(a === -Infinity && b === -Infinity) return 0; //a random value between 0 and 0 ;)
if(a === -Infinity) a = Math.min(0, b-53);
if(b === -Infinity) b = Math.min(0, a-53);
return Decimal.pow(10, Decimal.random(Math.abs(b-a)).mul(b-a).add(a) );
}
现在指数几乎是均匀分布的,但价值有点偏差。因为10 1 至10 1.5 10 .. 33
具有与10 1.5 至10 2 {相同的概率{1}}
答案 1 :(得分:1)
Math.random() * Math.pow(10, Math.floor(Math.random() * 100));
数字较小的问题是随机范围[0, 1)
,这意味着在单独计算指数时,需要确保前缀范围为[1, 10)
。否则,您想要计算[1eX, 1eX+1)
中的数字,但是例如0.1
作为前缀并以1eX-1
结尾。这是一个例子,maxExp
不是100而是10,输出的可读性,但很容易调整。
let maxExp = 10;
function differentDistributionRandom() {
let exp = Math.floor(Math.random() * (maxExp + 1)) - 1;
if (exp < 0) return Math.random();
else return (Math.random() * 9 + 1) * Math.pow(10, exp);
}
let counts = new Array(maxExp + 1).fill(0).map(e => []);
for (let i = 0; i < (maxExp + 1) * 1000; i++) {
let x = differentDistributionRandom();
counts[Math.max(0, Math.floor(Math.log10(x)) + 1)].push(x);
}
counts.forEach((e, i) => {
console.log(`E: ${i - 1 < 0 ? "<0" : i - 1}, amount: ${e.length}, example: ${Number.isNaN(e[0]) ? "none" : e[0]}`);
});
您可能会在此处看到类别<0
,这可能是您想要的(截止点是任意的,此处[0, 1)
与[1, 10)
的概率与[10, 100)
的概率相同依此类推,但[0.01, 0.1)
的可能性再次低于[0.1, 1)
)
如果你没有坚持base 10
,你可以将Math.random
两个Float64
个调用中的伪随机位重新解释为base 2
,它会产生类似的分布,function exponentDistribution() {
let bits = [Math.random(), Math.random()];
let buffer = new ArrayBuffer(24);
let view = new DataView(buffer);
view.setFloat64(8, bits[0]);
view.setFloat64(16, bits[1]);
//alternatively all at once with setInt32
for (let i = 0; i < 4; i++) {
view.setInt8(i, view.getInt8(12 + i));
view.setInt8(i + 4, view.getInt8(20 + i));
}
return Math.abs(view.getFloat64(0));
}
let counts = new Array(11).fill(0).map(e => []);
for (let i = 0; i < (1 << 11) * 100; i++) {
let x = exponentDistribution();
let exp = Math.floor(Math.log2(x));
if (exp >= -5 && exp <= 5) {
counts[exp + 5].push(x);
}
}
counts.forEach((e, i) => {
console.log(`E: ${i - 5}, amount: ${e.length}, example: ${Number.isNaN(e[0]) ? "none" : e[0]}`);
});
:
Float64
这个明显受到Infinity
的精确结束的限制,由于IEEE754的某些细节,例如,有一些不均匀的部分。 denorms / subnorms我并没有像1 << 11
那样处理特殊值。它被视为一种有趣的额外功能,提醒浮动值的分布。请注意,循环执行Float64
(2048)次迭代次数,大约是指数范围[-1022, 1023]
,11位,Option(Baz)
。这就是为什么在这个例子中每个桶获得大约所述数量(100)命中。
答案 2 :(得分:0)
您可以以小于Number.MAX_SAFE_INTEGER
的增量创建数字,然后将生成的数字连接到单个字符串
const r = () => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
let N = "";
for (let i = 0; i < 10; i++) N += r();
document.body.appendChild(document.createTextNode(N));
console.log(/e/.test(N));