热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

randomInt函数,可以统一处理MIN和MAX_SAFE_INTEGER的整个范围

如何解决《randomInt函数,可以统一处理MIN和MAX_SAFE_INTEGER的整个范围》经验,请问有什么解决方案?

要求和背景

我想要一个通用randomInt函数,该函数可以处理直至(包括)Number.MIN_SAFE_INTEGERto 的值范围,并且Number.MAX_SAFE_INTEGER返回的值是均匀分布的。

因此,我从MDN开始,然后浏览了该Math.random页面。他们举了一个例子,它看起来是均匀分布的。

// Returns a random integer between min (included) and max (excluded)
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

但是它带有以下注释。

请注意,由于Javascript中的数字是IEEE 754浮点数,具有从近似到最近的行为,因此以下函数所声明的范围(不包括Math.random()本身的范围)不准确。如果选择了很大的界限(2 ^ 53或更高),则在极少数情况下可以计算通常不包括的上限。

我想使用-(2 ^ 53-1)和2 ^ 53-1范围,因此我认为此注释不适用。然后,我注意到max - min:对于我指定的较大范围,这将是一个问题:

示例-最大范围

Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER > Number.MAX_SAFE_INTEGER

解决方案1-不是解决方案

根据MDN示例和我的要求,我开始做一些工作,并提出以下代码。

// Returns a random integer between min (included) and max (excluded)
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

但是正如您所看到的,这将在我需要的较大范围之前引发错误。

解决方案2-解决了数学问题,但似乎破坏了一致性

因此,我摆弄一个小提琴,并提出以下建议。

Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER > Number.MAX_SAFE_INTEGER

虽然我们不再抛出错误并且数学似乎在最大安全整数值之内,但是我不确定这如何影响原始MDN示例的均匀分布(如果它是均匀分布的)?

我的测试似乎表明这破坏了均匀分布。

分布图

Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;

Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -Number.MAX_SAFE_INTEGER;

Number.toInteger = Number.toInteger || function (inputArg) {
    var number = +inputArg,
        val = 0;

    if (number === number) {
        if (!number || number === Infinity || number === -Infinity) {
            val = number;
        } else {
            val = (number > 0 || -1) * Math.floor(Math.abs(number));
        }
    }

    return val;
};

function clampSafeInt(number) {
    return Math.min(Math.max(Number.toInteger(number), Number.MIN_SAFE_INTEGER), Number.MAX_SAFE_INTEGER);
}

// Returns a random integer between min (included) and max (included)
// Using Math.round() will give you a non-uniform distribution!
function randomInt(min, max) {
    var tmp,
        val;

    if (arguments.length === 1) {
        max = min;
        min = 0;
    }

    min = clampSafeInt(min);
    max = clampSafeInt(max);
    if (min > max) {
        tmp = min;
        min = max;
        max = tmp;
    }

    tmp = max - min + 1;
    if (tmp > Number.MAX_SAFE_INTEGER) {
        throw new RangeError('Difference of max and min is greater than Number.MAX_SAFE_INTEGER: ' + tmp);
    } else {
        val = Math.floor(Math.random() * tmp) + min;
    }
    
    return val;
}

console.log(randomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER));
Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;

Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -Number.MAX_SAFE_INTEGER;

Number.toInteger = Number.toInteger || function (inputArg) {
    var number = +inputArg,
        val = 0;

    if (number === number) {
        if (!number || number === Infinity || number === -Infinity) {
            val = number;
        } else {
            val = (number > 0 || -1) * Math.floor(Math.abs(number));
        }
    }

    return val;
};

function clampSafeInt(number) {
    return Math.min(Math.max(Number.toInteger(number), Number.MIN_SAFE_INTEGER), Number.MAX_SAFE_INTEGER);
}

// Returns a random integer between min (included) and max (included)
// Using Math.round() will give you a non-uniform distribution!
function randomInt(min, max) {
    var tmp,
        val;

    if (arguments.length === 1) {
        max = min;
        min = 0;
    }

    min = clampSafeInt(min);
    max = clampSafeInt(max);
    if (min > max) {
        tmp = min;
        min = max;
        max = tmp;
    }

    tmp = max - min + 1;
    if (tmp > Number.MAX_SAFE_INTEGER) {
        if (Math.floor(Math.random() * 2)) {
            val = Math.floor(Math.random() * (max - 0 + 1)) + 0;
        } else {
            val = Math.floor(Math.random() * (0 - min + 1)) + min;
        }
    } else {
        val = Math.floor(Math.random() * tmp) + min;
    }
    
    return val;
}

console.log(randomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER));
function getData() {
  var x = {},
    c = 1000000,
    min = -20,
    max = 20,
    q,
    i;

  for (i = 0; i 

解决方案3-不是解决方案

因此,我按一下创建Box-Muller Transform函数,以创建我认为需要的随机正态分布范围(但我的错误是我希望均匀分布)。我做了一些阅读,然后选择rejection sampling了从分布生成观察结果的方法。了解了如何计算范围的偏差而无需使用Math.sqrt

如果x的值为负,则Math.sqrt()返回NaN

这就是我想出的。

body {
  font: 10px sans-serif;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}

不确定我是否正确完成了所有操作(还没有破坏正态分布),但是在较小的整数范围内,我看到的是生成的正确整数范围。

但是,当我使用范围的最大限制时(或实际上在这些限制之前),仍然存在问题。数学仍然超出Number.MAX_SAFE_INTEGER值。从上面输出console.log(tmp);

{mean: 0, variance: 8.112963841460666e+31, deviation: 9007199254740991} 

如您所见,计算出的variance是不安全的。由于我对分布类型感到困惑,因此可以忽略该算法。

分布图

我已经包括了它,以便您可以看到我实际上已经很接近将这项工作作为正态分布发布,尽管这并不是我真正需要的。它可以帮助正在执行这种分配的人。

Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;

Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -Number.MAX_SAFE_INTEGER;

Number.toInteger = Number.toInteger || function (inputArg) {
    var number = +inputArg,
        val = 0;

    if (number === number) {
        if (!number || number === Infinity || number === -Infinity) {
            val = number;
        } else {
            val = (number > 0 || -1) * Math.floor(Math.abs(number));
        }
    }

    return val;
};

function clampSafeInt(number) {
    return Math.min(Math.max(Number.toInteger(number), Number.MIN_SAFE_INTEGER), Number.MAX_SAFE_INTEGER);
}

var boxMullerRandom = (function () {
    var phase = 0,
        RAND_MAX,
        array,
        random,
        x1, x2, w, z;

    if (crypto && crypto.getRandomValues) {
        RAND_MAX = Math.pow(2, 32) - 1;
        array = new Uint32Array(1);
        random = function () {
            crypto.getRandomValues(array);

            return array[0] / RAND_MAX;
        };
    } else {
        random = Math.random;
    }

    return function () {
        if (!phase) {
            do {
                x1 = 2.0 * random() - 1.0;
                x2 = 2.0 * random() - 1.0;
                w = x1 * x1 + x2 * x2;
            } while (w >= 1.0);

            w = Math.sqrt((-2.0 * Math.log(w)) / w);
            z = x1 * w;
        } else {
            z = x2 * w;
        }

        phase ^= 1;

        return z;
    }
}());

function rejectionSample(stdev, mean, from, to) {
    var retVal;
    
    do {
        retVal = (boxMullerRandom() * stdev) + mean;
    } while (retVal  max) {
        tmp = min;
        min = max;
        max = tmp;
    }

    tmp = {};
    tmp.mean = (min / 2) + (max / 2);
    tmp.variance = (Math.pow(min - tmp.mean, 2) + Math.pow(max - tmp.mean, 2)) / 2;
    tmp.deviation = Math.sqrt(tmp.variance);
    console.log(tmp);
    return Math.floor(rejectionSample(tmp.deviation, tmp.mean, min, max + 1));
}

console.log(randomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER));
{mean: 0, variance: 8.112963841460666e+31, deviation: 9007199254740991} 

什么是解决方案

那么,我想念什么?有一些我忽略的简单方法吗?我必须使用大数字库作为解决方案吗?如何测试分布:我有一些图表正在绘制,这对于较小的范围很好,但是不可能较大的范围?

请让我摆脱这一烦恼。:)


推荐阅读
author-avatar
阵风阿斯顿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有