Skip to main content

What is the meaning of life?

Posted by kabutz on December 21, 2011 at 11:37 PM PST

A few weeks ago I updated my age to be a factor of 2 and 5.  It is the perfect age to reflect what life is all about.  Some men don a leather jacket and ride around on a Harley.  But as a geek I know exactly where to turn - my beloved computer.  

I needed a long-running method for the new concurrency course I am writing.  Something that would take about 15 seconds and that would keep the CPU busy at 100%.  Also, since life throws us random events, we would have to include a call to Math.random() in the calculation.  In my next article I will explain why Math.random() is dead, long live Java 7, but for now we will call it in order to be super slow.

public class MeaningOfLife {
  public static String findOutWhatLifeIsAllAbout() {
    int meaning = 0;
    for (int i = 0; i < 10; i++) {
      for (int j = 0; j < 20; j++) {
        for (int k = 0; k < 300; k++) {
          for (int m = 0; m < 7000; m++) {
            meaning += Math.random() + 1;
          }
        }
      }
    }
    return String.valueOf(meaning).replaceAll("0*$", "");
  }

  public static void main(String[] args) {
    System.out.println(findOutWhatLifeIsAllAbout());
  }
}

Think about what the output should be before you run it. Then try it out, preferably with the -server switch. On my machine it takes 15 seconds to find the meaning of life with -server and 25 seconds with -client. Patience is apparently a virtue, though I would not be able to confirm or deny that.  Never had patience, which is why I always want the fastest meanest computer that I can get.

The question is: Why is it giving this result? And why does it even compile?
 

 

Comments

Please don't post spoilers until after Christmas - let your ...

Please don't post spoilers until after Christmas - let your fellow Java experts think a bit about what the answer would be first :-)

public static void main(String args) { test(1); ...

public static void main(String[] args)  {
    test(1);
    test(167390728);
}

static void test(int intVal) {
    {
        int v1 = intVal;
        double v2 = 0.9999999945056177;
        double sum = v1 + v2;
        System.out.println(v1 + &quot; +  &quot; + v2 + &quot; = &quot; + sum + &quot; aka &quot; + new BigDecimal(sum) + &quot; =(int) &quot; + (int) sum);
    }
    {
        double v1 = intVal;
        double v2 = 0.9999999945056177;
        double sum = v1 + v2;
        System.out.println(v1 + &quot; +  &quot; + v2 + &quot; = &quot; + sum + &quot; aka &quot; + new BigDecimal(sum) + &quot; =(int) &quot; + (int) sum);
    }
}

Output:
1 + 0.9999999945056177 = 1.9999999945056177 aka 1.999999994505617717521772647160105407238006591796875 =(int) 1
1.0 + 0.9999999945056177 = 1.9999999945056177 aka 1.999999994505617717521772647160105407238006591796875 =(int) 1
167390728 + 0.9999999945056177 = 1.67390729E8 aka 167390729 =(int) 167390729
1.67390728E8 + 0.9999999945056177 = 1.67390729E8 aka 167390729 =(int) 167390729
First two lines truncate as expected, second two appear rounded as seen. (A '+= ' does the same, as seen previously).

Further, here's the output from a C++ program doing the same:
1 + 0.99999999450561771752 = 1.99999999450561771752 =(int) 1
1.00000000000000000000 + 0.99999999450561771752 = 1.99999999450561771752 =(int) 1
167390728 + 0.99999999450561771752 = 167390729.00000000000000000000 =(int) 167390729
167390728.00000000000000000000 + 0.99999999450561771752 = 167390729.00000000000000000000 =(int) 167390729
It appears that the issue has nothing to do with the += operation, nor int values, nor java ... but rather floating point processing (ie. hardware/standards).
Though the 'int += double' allowance is interesting... ;)

Multiple Layers I think there are multiple facets to this ...

Multiple Layers

I think there are multiple facets to this puzzle, and all have been touched on by different responders. I think I can help make some sense of this puzzle.

We start with yishai who uncovered the value of Math.random() that causes the anomaly. In my experiment, I modified the code (inside the deepest for loop) by...

// adding extra variables
double rand = Math.random();      // we save Math.random() into a variable for later output
double rand_plus_1 = rand + 1;    // I just wanted to see what would happen
int xmeaning = meaning;           // we use this for comparison
int temp = (int)(rand + 1);       // always equal to 1, strange...

// modifying existing code
meaning += rand + 1;              // meaning += Math.random() + 1;   &lt;-- old code

// and finding and displaying the anomaly
if(meaning - xmeaning != 1) {
   System.out.println(xmeaning + &quot;-&gt;&quot; + meaning + &quot; &quot; + rand + &quot; &quot; + rand_plus_1);
}

The output of the program is...

167390728-&gt;167390730 0.9999999945056177 1.9999999945056177
205622567-&gt;205622569 0.999999996646961  1.999999996646961
245591182-&gt;245591184 0.9999999863337321 1.999999986333732
339981965-&gt;339981967 0.9999999905665823 1.9999999905665824
368881211-&gt;368881213 0.9999999843595886 1.9999999843595886
378638703-&gt;378638705 0.9999999776003651 1.999999977600365
420000006

As you can see, random values greater than approximately 0.99999997 cause the anomaly. Thank you yishai.

The Anomaly

So what is the anomaly exactly? The post by forax about compound operators combined with yishai's discovery (above) is the answer to that question.

Observe... Look at the output above, look only at the first line. As you can see, meaning incremented by 2 in the first column. Math.random() generated the value in the second column, and that value plus one is the value in the third column... the increment value! It is not quite 2.

SO, IF THE INCREMENT VALUE IS NOT QUITE 2, HOW DID IT INCREMENT BY 2!?

After all, if you cast this value (the one from the third column) to an int, all of the remainder is stripped and all you're left with is 1. This is (in theory) how it should have worked, however, we have not cast anything to an int! The code...

meaning += rand + 1;

simply stuffs a double (incremented by 1 but still a double) into an int in order to increment some other int. This stuffing operation is NOT the same as a casting operation. But if we assume they are the same, which most programmers will, we are making a wrong assumption. Whether the increment operator or something else inside the virtual machine is broken, all I can say is forax lead us in the right direction.

So, the way we fix our code to get that nice "42" output is to actually cast the random number first to an int. Like so...

meaning += (int)(rand + 1);

Problem solved.

Final Note

I want to clarify that while it is possible (albeit inaccurate, as we have proven) to stuff a double into an int during a compound operation, it is not possible to do so during an assignment operation. But we already knew this.

some_int += some_double + 1;  // possibly buggy, however this operation is legal
some_int = some_double + 1;   // compiler error

In other words, I cannot answer the author's original question of "How is it even able to compile?" I do wonder, though, if any documentation exists which could have alerted a studious programmer to this anomaly i.e. virtual machine specifications, java language specifications, etc.

You peeled away all the layers like from an onion.&nbsp; ...

You peeled away all the layers like from an onion.  Lovely, thanks for your great explanation.

Since it is after Christmas ... meaning += Math.random() ...

Since it is after Christmas ...

meaning += Math.random() + 1;

can be thought of as:

double doubleMeaning = meaning + Math.random();

meaning = (int) doubleMeaning + 1;

For some values of meaning and Math.random() the resulting double will be represented as a whole number. Consider this case:

System.out.println(BigDecimal.valueOf(280658056.9999999876849885));

So if the value in the loop was 280658056 and the value of Math.random() returned 0.9999999876849885, then the result will already increment meaning by 1, causing that pass of the loop to increase it by two. So the number above 420 million indicates how many times that condition was met.

Yes. &nbsp;It should also be pointed out that for ...

Yes.  It should also be pointed out that for Math.random(), this can only happen with a large value of "meaning".  It is impossible in the current Math.random() implementation for (int)(Math.random() + 1) to equal 2.  Anybody like to try figure out why?

Wait a second, I definitely missed something then.&nbsp; I ...

Wait a second, I definitely missed something then. I thought it was strictly the size of the random number, but what you're saying is that the size of "meaning" also matters. Well, I just ran other tests to see if I could qualify this. They capture all anomalies AND all high random numbers. However, these tests appear to be inconclusive. In one test, the code...

if(meaning - xmeaning != 1 || rand &gt; 0.99999997) {
    System.out.println(xmeaning + &quot;-&gt;&quot; + meaning + &quot; &quot; + rand + &quot; &quot; + rand_plus_1);
}

produced...

24371067-&gt;24371068   0.9999999828062357 1.9999999828062358
59006142-&gt;59006143   0.9999999757290122 1.9999999757290121
85837282-&gt;85837283   0.9999999807169675 1.9999999807169675
107546119-&gt;107546121 0.9999999971733692 1.999999997173369  (*)
117495561-&gt;117495562 0.9999999729988222 1.9999999729988223
123249024-&gt;123249026 0.9999999965744986 1.9999999965744986 (*)
140167680-&gt;140167681 0.9999999809920106 1.9999999809920106
145594763-&gt;145594765 0.9999999996361264 1.9999999996361264 (*)
160475235-&gt;160475236 0.9999999793442659 1.999999979344266
178166317-&gt;178166318 0.9999999760638756 1.9999999760638756
196935260-&gt;196935262 0.9999999999024445 1.9999999999024445 (*)
217866433-&gt;217866434 0.9999999707050973 1.9999999707050973
239191478-&gt;239191479 0.9999999749421629 1.9999999749421629
283879355-&gt;283879357 0.9999999707268747 1.9999999707268747 (*)
333059249-&gt;333059251 0.9999999792976765 1.9999999792976766 (*)
420000006

In lines 1 - 3 "meaning" is small and there are no anomalies, so perhaps you're right. But there are some qualifying combinations that don't result in anomalies, how can those be explained? By the way there is an answer to this riddle, right? When do we get to know what it is?

Compound operator is broken, short x = 3; x += 4.6; is ...

Compound operator is broken, [1]

short x = 3;
x += 4.6;

is equivalent to

short x = 3;
x = (short)(x + 4.6);

and
  Math.random() returns a value between [0, 1[, so the value can be ignored.

Rémi

[1] http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#5304

Thus, we would expect the value to meaning to be ...

Thus, we would expect the value to meaning to be 420,000,000, then we truncate the zeros with the regular expression and we end up with 42?  After all, Math.random() never returns 1.0 :-)

&nbsp;We would thusly, except I'm returned 420000006 (or ...

We would thusly, except I'm returned 420000006 (or same value with different trailing int).

I would reflexively assume that Math.random() DOES return 1.0 on the odd occasion regardless of the obviously lying documention, but I suspect I'm overlooking something trite.

Nope, Math.random() for sure never returns 1.0 :-)

Nope, Math.random() for sure never returns 1.0 :-)

&nbsp;In any case, I'm quite interested in understanding why ...

In any case, I'm quite interested in understanding why I'm NOT returned 420,000 then truncated to 42. Which of course is the answer to life, the universe and everything.

Right, but the best puzzles are those that we eventually ...

Right, but the best puzzles are those that we eventually figure out :-)  Math.random() never returns 1.0.  It is always less than 1.0.

Try figure out for what values of "meaning" and random it increments by 2, then you will have the answer (almost) figured out :-)

Interresting, when I've answered I was on my phone, so no ...

Interresting, when I've answered I was on my phone, so no way to actually run the program.
I think I've found the problem, as often the computer is right, the error is that we don't have the same meaning for +.

The + between math.random() and 1 is a + between a double and an int, so the int is converted
to a double, so here 1 is converted to 1.0 which is in fact 1.0000000000000XXX so
math.random() is between [0, 1[ but math.random() + 1 can be >= 2.0

I hate IEEE 754 :)

Rémi
 

Hum, reading the other comments before posting a comment is ...

Hum, reading the other comments before posting a comment is a good idea. jaeksmith has already provided the anwser, yesterday. Remi