简体   繁体   English

Java 奇怪的浮动行为

[英]Java Strange Float Behavior

Here is what my code looks like stripped down as much as possible:这是我的代码尽可能精简的样子:

    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;
            }
        }

As you can see I'm looping 20 times and printing out a random number after 5 seconds (5000 milliseconds).如您所见,我循环了 20 次并在 5 秒(5000 毫秒)后打印出一个随机数。 This is what the output looks like:这是输出的样子:

在此处输入图片说明

As you can see, all of the outputs are VERY close to -1000.如您所见,所有输出都非常接近 -1000。 I'm trying to generate a random float from -1000 to 1000, but they all seem to be around -1000.我试图生成一个从 -1000 到 1000 的随机浮点数,但它们似乎都在 -1000 左右。 So, I checked to make sure the actual random number generator is working, using this code:因此,我使用以下代码检查以确保实际的随机数生成器正常工作:

    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();
        }

Basically I took elapsed out of the equation and just printed the random numbers without the if statement.基本上我从等式中退出,只打印没有 if 语句的随机数。 This is the output I got:这是我得到的输出:

在此处输入图片说明

In this example, I got very random output, exactly like you would expect.在这个例子中,我得到了非常随机的输出,正如你所期望的那样。 However, once you add elapsed back into the equation like in the first set of code, the output turns back to -999 with some random decimal places.但是,一旦您像第一组代码一样将 elapsed 重新添加到等式中,输出就会变回 -999,并带有一些随机小数位。

Even more interesting is that if you put a print statement right above the if statement and right after randomNum is assigned a value, you get these outputs:更有趣的是,如果你在 if 语句的正上方放置一个 print 语句,并且在randomNum被赋值之后,你会得到以下输出:

在此处输入图片说明

Once again, the numbers are random.再一次,数字是随机的。 It seems like for some reason in the first example code with the elapsed part of the code put in, the randomNum variable changes right after the if statement is called.似乎出于某种原因,在第一个示例代码中放入了代码的已用部分, randomNum变量在 if 语句被调用后randomNum更改。 Why does this happen?为什么会发生这种情况?

The problem is that, although you're generating random numbers in your desired range, you're systematically discarding all the ones over -999 .问题是,尽管您在所需范围内生成随机数,但您正在系统地丢弃所有超过-999随机数。

Consider code like this:考虑这样的代码:

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);
    }
}

The above code will print out a bunch of random numbers in the range [−1000, −999);上面的代码会打印出一堆[-1000,-999)范围内的随机数; do you see why?你明白为什么吗?

Your code is more complicated, of course, but it's effectively doing the same thing.当然,您的代码更复杂,但它实际上在做同样的事情。

To see why, let's take a look at your code:要了解原因,让我们看一下您的代码:

    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;
            }
        }

Let's simplify/trim it a bit so it's easier to read — remove the commented-out line, clean up the whitespace, remove the casts to int and float (it's OK to use long and double ), inline the various values, change the for -loop-that-contains-code-that-mutates-its-index-variable into a more-explicit while -loop, change the System.nanoTime() -but-then-dividing-the-result-by-a-million to System.currentTimeMillis() , rename some variables for clarity, etc.:让我们稍微简化/修剪一下,让它更容易阅读——删除注释掉的行,清理空格,删除转换为intfloat (可以使用longdouble ),内联各种值,更改for -loop-that-c​​ontains-code-that-mutates-its-index-variable 变成更明确的while -loop,改变System.nanoTime() -but-then-dividing-the-result-by-a-million到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++;
    }
}

Even with that simpler code in hand, we still need two key insights:即使手头有这么简单的代码,我们仍然需要两个关键的见解:

  • elapsedMillis > 5000 + randomNum is just another way of writing randomNum < elapsedMillis - 5000 . elapsedMillis > 5000 + randomNum只是randomNum < elapsedMillis - 5000另一种写法。
  • Initially, elapsedMillis == 0 ;最初, elapsedMillis == 0 ; and after each time we successfully print out a number, elapsedMillis == 0 again.每次我们成功打印出一个数字后, elapsedMillis == 0再次。 In between, there are some loop iterations where elapsedMillis increases by 1 , but in most loop iterations it doesn't change at all.在两者之间,有一些循环迭代,其中elapsedMillis增加1 ,但在大多数循环迭代中,它根本没有改变。
    • This is because this loop is very very quick, with a very large number of iterations per millisecond.这是因为这个循环非常非常快,每毫秒迭代次数非常多。 (That's not necessarily obvious from first principles, but it's the only way to explain the output you're getting.) (从基本原理来看,这不一定很明显,但这是解释您得到的输出的唯一方法。)

So this code will loop quickly, generating one random number after another and discarding every single one, until elapsedMillis == 4001 , at which point every random number will be discarded except random numbers less than -999 .所以这段代码将快速循环,生成一个又一个随机数并丢弃每个随机数,直到elapsedMillis == 4001 ,此时除了小于-999随机数,每个随机数都将被丢弃。 Since you're performing a huge number of loop iterations per millisecond, and generating a huge number of random numbers per millisecond, it's overwhelmingly likely that you manage to generate a random number less than -999 while elapsedMillis == 4001 .由于您每毫秒执行大量循环迭代,并且每毫秒生成大量随机数,因此当elapsedMillis == 4001 ,您极有可能设法生成小于-999的随机数。 And then elapsedMillis gets reset back to zero.然后elapsedMillis被重置回零。 So random numbers greater than -999 never have a chance to compete: elapsedMillis is never greater than 4001 , so such numbers are always discarded.所以大于-999随机数永远没有机会竞争: elapsedMillis永远不会大于4001 ,所以这样的数字总是被丢弃。

To fix this, you need to pre-select a single random number "How long should I delay?"要解决此问题,您需要预先选择一个随机数“我应该延迟多长时间?” before you start looping, and then loop until your elapsedMillis exceeds that one pre-selected random number.你开始循环之前,然后循环直到你的elapsedMillis超过一个预先选择的随机数。

In addition, assuming that your real goal here is to delay for some amount of time in the range [4sec,6sec), you should probably use Thread.sleep() rather than this polling/busy-waiting mechanism.此外,假设您的真正目标是在 [4sec,6sec) 范围内延迟一段时间,您可能应该使用Thread.sleep()而不是这种轮询/忙等待机制。 That way, instead of just burning CPU, you can gracefully yield this processor for use by other threads and processes until you're ready to proceed.这样,您可以优雅地让出此处理器供其他线程和进程使用,而不是仅仅消耗 CPU,直到您准备好继续。 To do that, you can write:为此,您可以编写:

for (int i = 0; i < 20; i++) {
    final long millisToDelay = (long) (2000 * Math.random() - 1000);
    System.out.println("Millis To Delay: " + millisToDelay);
    Thread.sleep(millisToDelay);
}

I believe you actually have the answer to your problem located on the second line of code you have provided to us.我相信您实际上已经在您提供给我们的第二行代码中找到了问题的答案。 Notice how you cast to float: (float)?请注意您如何转换为浮动:(浮动)?

Do the same thing on the line that contains:在包含以下内容的行上执行相同的操作:

float number = delay + randomNum;浮点数 = 延迟 + 随机数;

so that it looks like:所以它看起来像:

float number = (float) delay + randomNum;浮点数 =(浮点数)延迟 + 随机数;

delay is not a float data type. delay 不是浮点数据类型。 I think that should do it.我认为应该这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM