在Node.js

时间:2017-08-09 02:07:58

标签: javascript node.js random arbitrary-precision

所以我非常熟悉旧的

Math.floor(Math.random() * (max - min + 1)) + min;

并且这对于小数字非常有效,但是当数字变大时,这很快变得有偏差并且只返回低于它的数字零(例如,01e100之间的随机数几乎将总是(每次我测试过,因此我使用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相同。但与此同时,我需要支持较小的数字和范围。

3 个答案:

答案 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));