4

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.

Patrick Garner
  • 3,201
  • 6
  • 39
  • 58
  • This question has been answered repeatedly in the general case. – Woot4Moo Dec 06 '11 at 15:39
  • 10
    What a strange way to write `for (int i = 0; i < 42000000; i ++)` – James Clark Dec 06 '11 at 15:39
  • Try casting the Math.random() call to an integer first. Currently, the '1' literal in your code is being implicitly casted to a double, which means there is precision lost somewhere. – Richard J. Ross III Dec 06 '11 at 15:40
  • possible duplicate of [float vs. double precision](http://stackoverflow.com/questions/5098558/float-vs-double-precision) – Woot4Moo Dec 06 '11 at 15:41
  • @Woot4Moo no? an integer's max value (in java) is 2,147,483,647. Thats over 50 times 42 million. – Richard J. Ross III Dec 06 '11 at 15:42
  • Math calls system functions depending on the JVM implementation: "Unlike some of the numeric methods of class StrictMath, all implementations of the equivalent functions of class Math are not defined to return the bit-for-bit same results". Hence the question: what JVM/OS are you using? Does `((Math.random() + 1) == 2)` ever evaluate to true in the innermost loop? – Viruzzo Dec 06 '11 at 15:42
  • @Woot4Moo to elaborate, Java's int is a signed 32 bit integer, so it has a max value of about 2^31 = 2 billions. – Viruzzo Dec 06 '11 at 15:44
  • @RichardJ bah I knew there was a 2 in the front :( – Woot4Moo Dec 06 '11 at 15:50
  • @Viruzzo I do indeed see that now, this is why I need to stay off SO before noon. – Woot4Moo Dec 06 '11 at 15:55
  • The nested loops I created in order to view the value of 'result' at regular intervals. – Patrick Garner Dec 06 '11 at 17:38
  • possible duplicate of [Varying behavior for possible loss of precision](http://stackoverflow.com/questions/2696812/varying-behavior-for-possible-loss-of-precision) – tvanfosson Dec 07 '11 at 13:27

3 Answers3

12

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
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 2
    Very small addendum: it's not that Math.random() is truncated to 0, but that "Math.random()+1" is truncated to 1. The 1 is promoted to double, then added, then truncated. It's not that the random number is truncated upfront. – Sean Owen Dec 06 '11 at 15:46
  • You answered when I found `System.out.println(0.99999999999999999);` is `1.0`. +1 for being fast. – Jomoos Dec 06 '11 at 15:46
  • @SeanOwen, My point is that there is a false assumption that since `Math.random()` is always rounded to 0, `Math.random() + 1` will always be rounded to 1. – Peter Lawrey Dec 06 '11 at 15:58
  • It is not 1.9999999999999999 that is being rounded. Rather it's that 1.0D + 0.9999999999999999D = 2.0D. In fact it has nothing to do with the += operator. – jsravn Dec 06 '11 at 16:55
  • @jsravn, `+=` is casting to `int` without which the program wouldn't compile. I have clarified which operation does the rounding. Casting to `(int)` always rounds down (like Math.floor(x)). However `double` operations round to closest representation. – Peter Lawrey Dec 06 '11 at 16:57
  • So the answer to my first question is still not clear. @Peter I understand that assigned operators like += do an implicit cast but does not = do an implicit cast also? Why does the "+=" code compile but not the "=" code example? – Patrick Garner Dec 06 '11 at 17:58
  • @PeterLawrey thank you for your concise answer to my second question, including the example code. It is very helpful. – Patrick Garner Dec 06 '11 at 18:07
  • @PatrickGarner when `=` is used for initialisation, an implicit cast is used, however when `=` is used for assignment, no cast is performed. – Peter Lawrey Dec 06 '11 at 18:51
  • Indeed, I was wrong in my initial assertion. += does have something to do with it - namely, it increases the probability that the floating point addition will roll over to x+2 as the magnitude of result increases (since x += m becomes x = (int) ((double) x + m)). – jsravn Dec 06 '11 at 20:02
1

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; 
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
Michael Shopsin
  • 2,055
  • 2
  • 24
  • 43
  • I'd guess this is because the zero is signed. (Ie. there exists both a "+0" and a "-0" which have different representations in the floating point system) Kind of fun to see how those numbers interact with comparison operators though. – Alderath Dec 06 '11 at 16:00
  • Yeah, there are even ways to adjust how close to zero rounds to zero. The original code was written by a scientist so I had to explain to him how IEEE math violates some basic tenants of algebraic expressions. – Michael Shopsin Dec 06 '11 at 16:35
  • Although this doesn't answer my two questions, it is very interesting and useful! Thank you! – Patrick Garner Dec 06 '11 at 17:45
  • I want to make you aware of the general issues with floating point math. Java hides many of the details of IEEE math but not the consequences. – Michael Shopsin Dec 06 '11 at 18:06
-1

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.

AlexR
  • 114,158
  • 16
  • 130
  • 208
  • 2
    This answer is wrong. According to the spec, Math.random returns a double value, such that ´0 <= value < 1´. Hence, it cannot return exactly 1.0. – Alderath Dec 06 '11 at 15:56