Java奇怪的浮动行为

时间:2015-11-21 17:26:03

标签: java floating-point

以下是我的代码尽可能地被删除的内容:

    float delay = (float)5000;
        long startTime = System.nanoTime();
        int elapsed = 0;
        for (int i = 0; i < 20; i++) {
            elapsed = (int) ((System.nanoTime() - startTime) / 1000000);
//          System.out.println("Elapsed: " + elapsed);
            float range = delay * 0.4f;
            float randomNum = (float)(Math.random() * range - (delay * 0.2f));
            if (elapsed > (delay + randomNum)) {
                System.out.println("Random Num: " + randomNum);
                startTime = System.nanoTime();
            } else {
                i--;
                continue;
            }
        }

正如你所看到的,我循环了20次并在5秒(5000毫秒)后打印出一个随机数。这就是输出的样子:

enter image description here

如您所见,所有输出都非常接近-1000。我试图从-1000到1000生成一个随机浮点数,但它们似乎都在-1000左右。因此,我检查以确保实际的随机数生成器正常工作,使用以下代码:

    float delay = (float)5000;
        long startTime = System.nanoTime();
        int elapsed = 0;
        for (int i = 0; i < 20; i++) {
            elapsed = (int) ((System.nanoTime() - startTime) / 1000000);
//          System.out.println("Elapsed: " + elapsed);
            float range = delay * 0.4f;
            float randomNum = (float)(Math.random() * range - (delay * 0.2f));                  
            System.out.println("Random Num: " + randomNum);
            startTime = System.nanoTime();
        }

基本上我已经超出了等式,只打印了没有if语句的随机数。这是我得到的输出:

enter image description here

在这个例子中,我得到了非常随机的输出,就像你期望的那样。但是,一旦你像经过第一组代码一样将经过的时间添加到等式中,输出将返回到-999并带有一些随机小数位。

更有意思的是,如果你在if语句的正上方放置一个print语句,并且在randomNum被赋予一个值之后,你会得到这些输出:

enter image description here

再次,这些数字是随机的。似乎由于某种原因,在第一个示例代码中放入了代码的已用部分,randomNum变量在调用if语句后立即更改。为什么会这样?

2 个答案:

答案 0 :(得分:1)

问题在于,虽然您在所需范围内生成随机数,但系统地丢弃-999以上的所有数据。

考虑这样的代码:

while (true) {
    // generate a random number in the range [-1000, 1000):
    final double randomNum = 2000 * Math.random() - 1000;

    // print it if it's in the range [-1000, -999):
    if (randomNum < -999) {
         System.out.println("Random Num: " + randomNum);
    }
}

上面的代码将打印出一堆[-1000,-999]范围内的随机数;你知道为什么吗?

当然,你的代码更复杂,但它实际上是在做同样的事情。

要了解原因,让我们来看看您的代码:

    float delay = (float)5000;
        long startTime = System.nanoTime();
        int elapsed = 0;
        for (int i = 0; i < 20; i++) {
            elapsed = (int) ((System.nanoTime() - startTime) / 1000000);
//          System.out.println("Elapsed: " + elapsed);
            float range = delay * 0.4f;
            float randomNum = (float)(Math.random() * range - (delay * 0.2f));
            if (elapsed > (delay + randomNum)) {
                System.out.println("Random Num: " + randomNum);
                startTime = System.nanoTime();
            } else {
                i--;
                continue;
            }
        }

让我们简化/修剪它以便更容易阅读 - 删除注释掉的行,清理空白,删除强制转换为intfloat(可以使用{{ 1}}和long),内联各种值,将double - loop-that-c​​ontains-code-that-mutates-its-index-variable更改为更明确的{{1} } -loop,更改for - 但是 - 然后将结果除以一百万到while,为了清晰起见重命名一些变量等等:

System.nanoTime()

即使手头有这么简单的代码,我们仍然需要两个关键的见解:

  • System.currentTimeMillis()只是撰写long prevTimeMillis = System.currentTimeMillis(); int i = 0; while (i < 20) { final long elapsedMillis = System.currentTimeMillis() - prevTimeMillis; final double randomNum = 2000 * Math.random() - 1000; if (elapsedMillis > 5000 + randomNum) { System.out.println("Random Num: " + randomNum); prevTimeMillis = System.currentTimeMillis(); i++; } } 的另一种方式。
  • 最初,elapsedMillis > 5000 + randomNum;每次我们成功打印出一个数字randomNum < elapsedMillis - 5000之后。在两者之间,有一些循环迭代,其中elapsedMillis == 0增加elapsedMillis == 0,但在大多数循环迭代中它根本不会改变。
    • 这是因为这个循环非常快,每毫秒迭代次数非常多。 (从第一原则来看,这不一定是显而易见的,但这是解释你得到的输出的唯一方法。)

因此,此代码将快速循环,生成一个接一个的随机数并丢弃每一个,直到elapsedMillis,此时每个随机数将被丢弃随机数小于1。由于您每毫秒执行大量循环迭代,并且每毫秒生成大量随机数,因此您很可能在elapsedMillis == 4001时生成小于-999的随机数。然后-999重置为零。因此大于elapsedMillis == 4001的随机数永远不会有竞争机会:elapsedMillis永远不会大于-999,因此这些数字总是被丢弃。

要解决此问题,您需要预先选择一个随机数“我应该延迟多长时间?” 之前开始循环,然后循环直到elapsedMillis超过该预选的随机数。

此外,假设您的真正目标是在[4秒,6秒]范围内延迟一段时间,您应该使用4001而不是此轮询/忙等待机制。这样,您可以优雅地生成此处理器,供其他线程和进程使用,直到您准备好继续。要做到这一点,你可以写:

elapsedMillis

答案 1 :(得分:0)

我相信您实际上已经找到了您提供给我们的第二行代码中的问题的答案。注意你如何转换为float:(float)?

在包含以下内容的行上执行相同的操作:

  

浮点数=延迟+ randomNum;

所以它看起来像:

  

float number =(float)delay + randomNum;

延迟不是浮点数据类型。我认为应该这样做。