为什么在JavaScript中更喜欢使用Math.floor()而非Math.round()?

8
我在FreeCodeCamp中发现解决获取指定范围内随机整数的问题的方法是使用 Math.floor。它不是四舍五入,而是向下取整。它返回小于或等于指定值的最大整数。这与我的想法不同。
以下是给出的公式: Math.floor(Math.random() * (max - min + 1)) + min 有人知道为什么它更适用于四舍五入到最近的整数吗?
提前感谢!

3
向下取整是指将数字向小的方向舍入到最接近的整数,而不是四舍五入到最接近的整数。 - Thomas Jager
Math.floor() 函数返回小于或等于一个给定数字的最大整数。 - DBS
此外,如果您的目标仅是向下取整 - Floor 不关心小数。除了简单的数字比较之外,没有其他计算,只返回一个整数作为结果。 - Lewis
你提供的公式是一个获取随机范围,包括边界的示例,其中你只想使用(Math.floor())向下取整以保持在你的范围内。 - DaveStSomeWhere
4个回答

8

总结:由于使用 Math.round()minmax 的值被低估了。


让我们举个例子,比较使用 Math.floor()Math.random() 得到的结果。

为了清晰起见,我添加了我们要比较的两个公式:

min = 0;
max = 3;

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

| result | Math.round() | Math.floor() |
|:------:|:------------:|:------------:|
|    0   |  0.0 - 0.499 |   0 - 0.999  |
|    1   |  0.5 - 1.499 |   1 - 1.999  |
|    2   |  1.5 - 2.499 |   2 - 2.999  |
|    3   |  2.5 - 2.999 |   3 - 3.999  |

你会发现03只有其他范围的一半大小,这是使用Math.random()生成它们的范围的原因。
添加了一个代码片段来展示这种效果。

// the same random numbers for all arrays, nonone should say these would have any influence.
const randomNumbers = Array(10000).fill().map(Math.random);

//min <= random <= max
const min = 4, max = 7;

// the +1 is to do `random <= max` otherwise it would produce `random < max`
const floored = randomNumbers.map(random => Math.floor(random * (max - min + 1) + min));

const rounded = randomNumbers.map(random => Math.round(random * (max - min) + min));

//utility to count the number of occurances per value
const count = (acc, nr) => (acc[nr] = (acc[nr] || 0) + 1, acc);

console.log({
  "Math.floor()": floored.reduce(count, {}),
  "Math.round()": rounded.reduce(count, {})
});
.as-console-wrapper{top:0;max-height:100%!important}


非常感谢!我只是想知道你是在哪里或者如何找到这个参考资料的?@Thomas - UnorthodoxThing
@UnorthodoxThing 什么参考资料?我是通过个人经验了解到这个问题,并制作了一个示例来演示它。 - Thomas
认真的吗?你应该记录下来或者提交一份报告!o.o - UnorthodoxThing
3
记录什么?即Math.round()可能会四舍五入,但Math.floor()不会?所提到的“问题”是Math.round()不适合此任务的原因,因为我在这个答案中解释过的原因。使用的任何方法本身都没有错。 - Thomas
那么明确一下,Math.round 只有在与 Math.random 一起使用时才会出现问题?我经常看到即使没有使用 Math.random,在某些情况下仍然更喜欢使用 Math.floor,因为它可以四舍五入到最近的整数,而不仅仅是向下取整。 - Hashim Aziz

2

这是我认为的真正原因,Math.random返回一个0(包括)到1(不包括)之间的浮点数,类似于0-0.9999999999999999(我猜测)。看到这个信息,我们可以很容易地想到,好的,这是一个百分比,所以我们只需要做一些像这样的事情:

    let max = 10;

    console.log(Math.round(Math.random() * max))

好的,让我们来看看我们的Math.random数字在我们的范围内应该代表什么。
假设我们的范围是min=1和max=5,这就创建了五种不同的可能性:1、2、3、4、5。如果0.9999999999999999,或者为了简单起见,取1作为从Math.random获取的最大数字,我们可以将它除以我们的范围所具有的可能性的数量,例如1/max,得到大约0.2作为结果,这意味着每个可能性至少将等于这个数乘以它的值,也就是说Math.random值在以下范围内将满足每个可能性:
现在让我们看一下Math.round的作用:
    console.log(0.25 * 5, 'rounded to:', Math.round(0.25 * 5));
    //Prints 1.25 'rounded to:' 1
    
    console.log(0.31 * 5, 'rounded to:', Math.round(0.31 * 5));
    //Prints 1.55 'rounded to:' 2

    console.log(0.85 * 5, 'rounded to:', Math.round(0.85 * 5));
    //Prints 4.25 'rounded to:' 4

如您所见,在某些情况下(最小值的浮点数和最大值的浮点数),Math.round 将无法返回我们期望的结果,比如在第一个例子中,我们期望得到2,因为0.25大于0.2,在最后一个例子中,我们期望得到5,因为0.85大于0.8。这意味着从0.21到0.299999999999999以及从0.81到0.899999999999999的每个浮点数都会产生意外的结果,这就是为什么 Math.floor 解决方案更受欢迎的原因。
我们可以把随机生成的浮点数除以固定的可能性浮点数,这样怎么样?
    console.log(0.25 / (1 / 5));//Prints 1.25
    console.log(0.31 / (1 / 5));//Prints 1.5499999999999998
    console.log(0.85 / (1 / 5));//Prints 4.25

如果您查看这些结果,可以通过将它们舍入并确保我们至少获得最小值来修复它们:在末尾添加最小值。

    let min = 1;
    let max = 5;
    let r = Math.random();
    let f = 1 / max;
    let x = Math.floor(r / f) + min;

    console.log(r, x);

现在我们有另一个问题,如果我们将最小值更改为除1以外的任何数字,我们需要调整我们的公式以涵盖可能性数量中创建的偏移量。因此,如果我们的范围更改为min=2max=5,我们现在有四种可能性:2、3、4、5,我们的公式现在包括(max-min+1)来计算可能性的数量;+1使范围包含创造四种可能性所需的插槽:
    let min = 2;
    let max = 5;
    let r = Math.random();
    let f = 1 / (max - min + 1);
    let x = Math.floor(r / f) + min;

    console.log(r, x);

我们最终的函数应该是这样的:
    const randomNumber = (min, max) => {
        return Math.floor(Math.random() / (1 / (max - min + 1))) + min;
    }
    
    console.log(randomNumber(2, 5));
    
    //If Math.random ever returns 0.999999999999999
    console.log(Math.floor(0.999999999999999 / (1 / (5 - 2 + 1))) + 2);//Prints 5
    
    console.log(Math.floor(0.249999999999999 / (1 / (5 - 2 + 1))) + 2);//Print 2

    //Hope this never happens
    console.log(Math.floor(0.25 / (1 / (5 - 2 + 1))) + 2);//Print 3

这与以下代码相同:

    const randomNumber = (min, max) => {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    console.log(randomNumber(2, 5));
    
    //If Math.random ever returns 0.999999999999999
    console.log(Math.floor(0.999999999999999 * (5 - 2 + 1)) + 2);//Prints 5
    
    console.log(Math.floor(0.249999999999999 * (5 - 2 + 1)) + 2);//Print 2

    //Hope this never happens
    console.log(Math.floor(0.25 * (5 - 2 + 1)) + 2);//Print 3

那么明确一下,Math.round 只有在与 Math.random 一起使用时才会出现问题?我经常看到即使没有使用 Math.random,人们也更喜欢使用 Math.floor,在这种情况下,我认为实际上使用 Math.round 会更准确,因为它会四舍五入到最近的整数,而不仅仅是向下取整。 - Hashim Aziz
我不知道其他情况,但我应该澄清的是这与Math.floor无关,实际上是Math.floor + 1或Math.ceil。想象一下你有一个范围为1-3的区间,即1、2、3;每个数字应该有相同的机会,也就是说每个数字应该有33.33%或0.3333333的机会,因此如果Math.random返回0.34-0.66之间的任何浮点数,则期望的结果应该是2,因为0.34大于0.3333333,对吧?0.34*3=1.02,Math.round会将其四舍五入到1,而Math.floor也会完全相同,但+1会修复结果。现在测试0.34-0.49之间的任何浮点数。 - Puerto AGP

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

会给你一个[min, max]范围内的随机数,因为Math.random()会给你[0, 1)。我们使用Math.round而不是Math.floor,Math.random()会给你[0, 1),如果你将它乘以10,你会得到[0, 10)。这是一个浮点数,如果你向上舍入,你会得到[0, 10]作为整数。然而,如果你向下舍入,你会得到[0, 10)作为整数。

在大多数随机函数中,返回[min, max)是标准。

回答你的问题,作者使用Math.floor,这样随机数就会在[min, max]范围内,而不是在[min, max+1]范围内,如果使用Math.round。

来自维基百科

区间 主要文章:区间(数学) 圆括号()和方括号([])也可以用来表示区间。符号{\displaystyle [a,c)} [a, c)用于表示从a到c的区间,包括{\displaystyle a} a但不包括{\displaystyle c} c。也就是说,{\displaystyle [5,12)} [5, 12)将是所有实数的集合,介于5和12之间,包括5但不包括12。这些数字可以无限接近12,包括11.999等等(任意有限数量的9),但不包括12.0。在一些欧洲国家,也使用符号{\displaystyle [5,12[} [5,12[来表示这个区间。

0

Math.floor(Math.random()) 总是返回 0,而 Math.round(Math.random()) 将返回 0 或 1,因此使用 Math.round() 生成的随机数将遵循非均匀分布。这可能不符合您的需求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接