JavaScript中的随机数

在JavaScript中随机性经常都会使用到,但随机性也有许多种不同的类型,以及需要根据不同的应用程序选择不同的随机性。

基础的随机数

JavaScript中有一个内置的Math.random()函数,可以帮你得到任意的随机数,比如:

Math.random(); // =>0.19401081069372594

Math.random()函数经常返回0~1之间的浮点数。从技术上讲,Math.random()返回的数会出现0,但永远不会出现1

因为我们经常要使用Math.random(),所以我们可以将它封装成一个简单的函数:

function getRandom () {
    return Math.random();
}

调用getRandom()函数和使用Math.random()效果等同:

getRandom(); // =>0.8312549368438933

封装的getRandom()函数得到的随机数依旧是在0~1之间的浮点数,但很多时候,需要的随机数是在一个特定的范围之内,比如10~100之内的一个随机整数。

特定范围内的随机数

其实在上面的函数基础上,可以将其功能扩展一下,获取在某一个范围间的随机数(排队最大数和最小数)。实现这个功能,其实不难,但需要一顶点数学知识。

浮点数

function getRandom (min, max) {
    return Math.random() * (max - min ) + min;
}

getRandom(10,100); // => 10.687806629219263

整数

function getRandom (min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

getRandom(10,100); // => 41

上面代码扩展获取的特定范围内的随机数,但不包括最大值max和最小值min。但有的时候得到的随机数,还需要包括最大值max和最小值min。实现这样的功能,并不难,只需要稍微改良一下上面的函数:

function getRandomInRange (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

getRandomInRange(10, 100); // => 63

真假随机

大家可能都玩过这样的一游戏,抛硬币。那么不管你怎么抛,都只有两个答案,一个正面(暂把它称为真,即1),另一个则是反面(也就是假,即0)。这样的游戏,我们也可以通过一个随机函数来帮我们实现:

function coinToss () {
    return Math.floor(Math.random() * 2);
}

coinToss(); // => 0

如果你想得到随机的truefalse值,可以这样做:

function coinToss () {
    return (Math.floor(Math.random() * 2) === 0);
}

coinToss(); // => false

还可以写得更简单一点:

function coinToss() {
    return Math.random() < .5;
}

coinToss(); // => false

运用到刚才所说的抛硬币的游戏中,我们想要得到的是“正面”(true)还是“反面”(false),那只需要根据得到的值再做一个简单的判断:

function coinFlip() {
    return (Math.floor(Math.random() * 2) === 0) ? "正面" : "反面";
}

coinFlip(); // => "正面"

从数组中随机抽取数值

除了在有限范围内取随机数之外,很多时候,我们还需要在一个数组中随机抽取数组中的数值:

var numPool = [ 1, 3, 5, 7, 9, 10 ],
rand = numPool[Math.floor(Math.random() * numPool.length)];

有的时候,需要在一个数组中排除另外一个数组中的数组,并且将结果放到一个空的数组中:

var numPool = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
var excludePool = [ 3, 4 ];
var filteredPool = [];

通过一个for循环对原数组numPool做一个遍历,如果这个数存在于excludePool中,就将结果放到filteredPool中:

for (var i = 0; i < numPool.length; i++) {
    if (excludePool.indexOf(numPool[i]) === -1) {
        filteredPool.push(numPool[i]);
    }
}

最后从filteredPool取得随机数:

var rand = filteredPool[Math.floor(Math.random() * filteredPool.length)];

随机洗牌

随机洗牌在JavaScript中是一个典型的算法,创建一个数组,通过洗牌算法,将结果放在一个新数组,然后每次弹出一个数组元素:

var numPool = [13, 21, 36, 14, 27, 10];
function shuffle (numPool) {
    for (var j, x, i = numPool.length; i; j = parseInt(Math.random() * i), x = numPool[--i], numPool[i] = numPool[j], numPool[j] = x);
    return numPool;
}
var randomResult = shuffle(numPool);
while (randomResult.length > 0) {
    console.log(randomResult.pop());
}

上面的示例是在创建的数组中做随机排序,然后将数组的每个元素取出来。下面我们来看另一个示例,从一个范围内(比如0~1000)随机取12数,并将他们创建成一个数组,同时取出数组中最后一个子元素。

var numReserve = [];while (numReserve.length < 12) {
    var randomNumber = Math.ceil(Math.random() * 1000);
    var found = false;
    for (var i = 0; i < numReserve.length; i++) {
        if (numReserve[i] === randomNumber) {
            found = true;
            break;
        }
    }
}if (!found) {
    numReserve[numReserve.length] = randomNumber;
}

随机密码

这些方法创建一个随机数字是足够了,但要使用Math.random()创建一个加密的随机安全密码是足足不够的。如果要实现这样的功能,我们可以使用网络加密的API(Web Cryptography API)创建一个typedArray

var cryptoStor = new Uint16Array(8);

在这个示例中,创建一个数组,这个数组使用了八个不同的插槽,每个插槽包含一个16位整数。除了Uint16Array之外,还有其他的选项:Int8ArrayUint8Arrayint16ArrayInt32ArrayUint32Array

然后使用定义好的类型产生的随机数放到数组中:

window.crypto.getRandomValues(cryptoStor);

在浏览器的控制台中,可以看到运行后的结果:

[37155, 40751, 61916, 11457, 54737, 37881, 61272, 49313]

Web Cryptography API现在得到众多浏览器的支持,不过在一些浏览器下,还是需要添加对应的私有前缀。

为什么是一个大约数

JavaScript中有一个非常奇怪的地方,他实际是不存储整数的,它认为数值是一个二进制浮点数。再加上很多分数也无法用有限数量的小数表达出来,这意味着JavaScript可以创建这样的结果,如:

.1 + .2; // => 0.30000000000000004
.2 * .1; // => 0.020000000000000004

出于实际目的,大多数情况之下这个不精确也并不是十分重要,我们讨论的是一个错误2/1000000000000000000,但它有点令人沮丧。但是它在处理代表货币的数字、百分比或者说文件大小,如此一来就有些怪怪的,而且我们需要的结果就得修复这些——设置小数精度。

许多实际使用当中,会使用四舍五入。比如说,一个用户正在操纵一系列元素,例如我们需要最近的整数的大约值,而不是处理小数。

随机小数

在JavaScript中可以使用toFixed()toPrecision()方法对小数进行截取。它们带有一个参数,决定有多少位有效数字或小数位:

  • toFixed()提供了小数点后长度;
  • toPrecision()提供小数总长度;

注意: toFixed()是计算小数点后的长度,toPrecision()是计算整个数字的长度 。而且他们返回的都是字符串.

var num=2011.1456;
num.toFixed();       // => 2011,不传参数,默认为0,表示没有小数位
num.toFixed(2);      // => 2011.15,保留2位小数
num.toFixed(3);      // => 2011.146,保留3位小数
num.toFixed(6);      // => 2011.145600,保留6位小数,如果小数位不够,会在后面补0
num.toPrecision();   // => 2011.1456,不传参数,表示数字不变
num.toPrecision(6);  // => 2011.15,保留6位数字
num.toPrecision(7);  // => 2011.146,保留7位数字
num.toPrecision(10); // => 2011.145600,保留6位数字
num.toPrecision(2);  // => 2.0e+3,保留6位数字

特别声明:toFixed()toPrecision()两个方法返回的是一个字符串,如查你将他们的值做加法运算,会导产生错误,并不会得到你想要的结果:

var num = Math.PI;
var randNum = num.toFixed(2);
var rounded = num.toPrecision(4);
typeof(randNum); // => string
typeof(rounded); // => string
var addNum = randNum + rounded;
console.log(addNum); // =>3.143.142
typeof(addNum); // => string

如果你想要结果是两个数的相加的得到的值,那得需要使用parseFloat()处理一下:

var num = Math.PI;
var rounded = parseFloat(num.toFixed(2)) + parseFloat(num.toPrecision(4));
console.log(rounded); // => 6.282

toFixed()toPrecision()方法可以给一个整数添加小数位,这样在处理货币的时候就特别的方便:

var wholeNum = 1;
var dollarsCents = wholeNum.toFixed(2);
console.log(dollarsCents); // => 1.00

避免小数四舍五入造成误差

在下面的示例中,使用toFixed()toPrecision()碰到大于或等于5的时,不会入值,反而是舍去:

var numTest = 1.005;
numTest.toFixed(2); // => 1

上述计算的结果应该是1.01而不是1。避免这种错误在实际中很重要,建议使用Jack L Moore提供的指数数据计算方案,来避免这种错误:

function round(value, decimals) {
    return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}

round(1.005,2); // => 1.01

Epsilon(ε)舍入

在ES6中提供了另一种方法,处理小数四舍五入。Machine epsilon提供了一个合理的方法,比较两个浮点数合理处理这个误差。

在Chrome的控制台中输入:

0.1 + 0.2 // => 0.30000000000000004
0.1 + 0.2 === 0.3 // => false

通过Math.EPSILON函数可以做出一个正确的比较:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON * Math.max(Math.abs(x), Math.abs(y));
}

Math.EPSILON接受两个参数,一个是需要计算的表达式,另一个是需要做为比较的预期结果,然后用这两个值作为比较:

epsEqu(0.1 + 0.2, 0.3); // => true

初学者学习笔记,如有不对,还希望高手指点。如有造成误解,还希望多多谅解。

原文链接:https://www.w3cplus.com/javascript/rounding-recipes.html

发表评论

登录后才能评论