Patrick Garner
Patrick Garner

Reputation: 3321

Math.random() and precision loss curiosity

The following does not compile:

int result = Math.random() + 1;

error: possible loss of precision
    int result = Math.random() + 1;
                               ^
    required: int
    found:    double

but the following does compile:

int result = 0;
result += Math.random() + 1;

Why?

Putting the compilable code into a nested loop, one would expect result to increment by 1 with each iteration because Math.random() always returns a double whose value is less than 1 and when added to an integer the fractional part would be lost due to precision loss. Run the following code and see the unexpected result:

public class MathRandomCuriosity
{
  public static void main(String[] args)
  {
    int result = 0;
    for (int i = 0; i < 10; i++)
    {
      // System.out.println(result);
      for (int j = 0; j < 20; j++)
      {
        // System.out.println(result);
        for (int k = 0; k < 300; k++)
        {
          // System.out.println(result);
          for (int m = 0; m < 7000; m++)
          {
            result += Math.random() + 1;
          }
        }
      }
    }
    System.out.println(result);
  }
}

With 10*20*300*7000 = 42,000,000 iterations the result should be 42,000,000. But it's not! The result varies i.e. 42,000,007 vs. 42,000,006 vs. 42,000,010 etc.

Why?

By the way...this is not code that is being used anywhere, it comes from a quiz I received in a newsletter. The reason for the nested loops is so that I can view the value of result at intervals.

Upvotes: 4

Views: 2455

Answers (3)

Peter Lawrey
Peter Lawrey

Reputation: 533530

Assigned operators like += do an implicit cast.

Note: in this case Math.random() will be rounded down to 0 every time which is a significant loss of precision. ;)

However Math.random() + 1 has a very small chance of being rounded to 2. e.g. 1.999999 will be rounded to 1 but 1.9999999999999999 will be rounded to 2 (but the double + operator rather than the cast to int).

long l = Double.doubleToLongBits(1.0);
double d0_999etc = Double.longBitsToDouble(l -1);
System.out.println("The value before 1 is " +d0_999etc+" cast to (int) is "+ (int) d0_999etc);
System.out.println("The value before 1, plus 1 is " +(1+d0_999etc)+" cast to (int) is "+(int)(1 +d0_999etc));

prints

The value before 1 is 0.9999999999999999 cast to (int) is 0
The value before 1, plus 1 is 2.0 cast to (int) is 2

Upvotes: 12

Michael Shopsin
Michael Shopsin

Reputation: 2138

The details of an IEEE math implementation point out the loss of precision and unreliable results from double/float to integer conversion. For example I once found code that compared floating point numbers:

int x = 0;
if (a <= b) 
{ 
    x = y; 
}
if (a > b) 
{ 
    x = z; 
}

Sometimes the result was x == 0 eg a number the was caught by neither if statement, I had to rewrite the code as:

int x = 0; 
if (a <= b) 
{ 
    x = y; 
} 
else 
{ 
     x = z; 
}

Upvotes: 1

AlexR
AlexR

Reputation: 115338

By definition Math.random() returns double result from 0.0 to 1.0. The operation Math.random() + 1 creates double result that is then assigned to int variable, that produces integer result. On each iteration the result is 1 unless Math.random() returns exactly 1.0. The chance that it will happen is very low but still exists. It seems statistically it is something like 1/6000. This is the reason that some loop iterations add 2 to your result.

So, not lost of precision here. Everything happens according to spec.

Upvotes: -1

Related Questions