简体   繁体   中英

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). This is what the output looks like:

在此处输入图片说明

As you can see, all of the outputs are VERY close to -1000. I'm trying to generate a random float from -1000 to 1000, but they all seem to be around -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. 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.

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:

在此处输入图片说明

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. 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 .

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); 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.:

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 .
  • Initially, elapsedMillis == 0 ; and after each time we successfully print out a number, elapsedMillis == 0 again. In between, there are some loop iterations where elapsedMillis increases by 1 , but in most loop iterations it doesn't change at all.
    • 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 . 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 . And then elapsedMillis gets reset back to zero. So random numbers greater than -999 never have a chance to compete: elapsedMillis is never greater than 4001 , so such numbers are always discarded.

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.

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. 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. 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. I think that should do it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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