Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Math.random() and precision loss curiosity

Tags:

java

math

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.

like image 334
Patrick Garner Avatar asked Dec 16 '22 06:12

Patrick Garner


2 Answers

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
like image 166
Peter Lawrey Avatar answered Dec 18 '22 19:12

Peter Lawrey


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; 
}
like image 22
Michael Shopsin Avatar answered Dec 18 '22 20:12

Michael Shopsin