First, it should be said - and already mentioned in other answers - that the code you got from the book is wrong (or maybe you missed out copying it). The condition of the internal loop is supposed to be
for(j=2; j <= i/j; j++)
one important lesson from this is that you have to try running even book examples, to see if they work. If you ran this, you would have seen that it marks 4,9,25,49 as prime, when they are not.
But your reading of the code was also not correct. You said that 4 is judged to be not prime because 4%4 == 0
. But in fact, a prime number is allowed to be divisible by itself (any number is). So indeed, testing for a prime number by dividing it by itself would not work.
Let's see what this is all about. You understand the outer loop - it is giving you the candidates to test whether they are prime or not.
Then it works like this: assume the current candidate is prime. Try to divide it by all possible divisors. If any of the divisors divides it evenly, mark it as "not prime".
If, after all the possible divisors were tested, none of them got to the point that marks it as "not prime", then it is indeed prime and you can print it.
So the i
loop represents the candidates, and the j
loop represents the potential divisors. Take the number 15
as an example of i
.
- Start
j
with 2. i/j
is 15/2
which is 7. 2 < 7
, so we go inside the loop.
- Is 2 a divisor of 15? No, it isn't. Nothing is done.
j
is now 3
. i/j
is 15/3
which is 5
. 3 < 5
, so we continue.
- Is 3 a divisor of 15? Yes! So set
isprime=false
.
j
is now 4
. i/j
is 15/4
which is 3
. 4 < 3
is false, so we stop.
As you can see, the step with j=3
above set isprime
as false
, so we do not print it - it is not prime.
Now let's take a real prime number, like 13:
- Start
j
with 2. i/j
is 13/2
which is 6. 2 < 6
, so we go inside the loop.
- Is 2 a divisor of 13? No, it isn't. Nothing is done.
j
is now 3
. i/j
is 13/3
which is 4
. 3 < 4
, so we continue.
- Is 3 a divisor of 13? No, it isn't. Nothing is done.
j
is now 4
. i/j
is 13/4
which is 3
. 4 < 3
is false, so we stop.
None of the steps set isprime
to false
. It is still true
, so the number 13 is prime and we print it.
Now, the trick here is which candidates we offer for j
. Naively, a person writing this the first time would think that the candidates should be all the numbers between 1 and i
, excluding 1
and i
themselves. So they would write a loop like:
for ( j = 2; j < i; j++ )
But this is wasteful. Why? Suppose you checked the number 15. You found that 3 is a divisor for it. How is that? Because 15 = 3 x 5
. But this means that 5
is also a divisor. You don't need to test the divisor which, if you divide by it, you get a divisor that you already tested. So no need to test 5
, because we have already tested 3
.
So, again, naive programmers might decide that we can just test half the candidates:
for ( j = 2; j <= i / 2; j++ )
But in fact, mathematically speaking, this is still wasteful. Really, we should go only up to the square root of i
. Why? Because if a divisor j
is bigger than the square root of i
, such that j x k = i
, then k
will be smaller than the square root of i
. If k
was bigger, then j x k
would be bigger than sqrt(i) x sqrt(i)
which means they j x k > i
. But we know that they are equal to i
!
This means that if there is a potential divisor greater than the square root of i
, we already found the other divisor, smaller than the square root of i
, and tested it and marked i
as "not prime".
So this is what your candidate loop is testing. It's basically a simple way of writing
for ( j = 2; j <= Math.sqrt(i); j++ )
Without calling the Math
class and without converting the integers to doubles.
But as I said from the start, it's important that the condition is <=
rather than <
. For numbers which are squares of primes, (4 = 2 x 2, 9 = 3 x 3), the only proper divisor is the number that is equal to their square root.
One last note: this book example is still wasteful, because it doesn't stop checking after it found one divisor that makes the number not prime. Finding one divisor is enough. One way to do this is to change the loop condition like this:
for ( j = 2; isprime && j <= i/j; j++ )
So the loop will only continue as long as we still have reason to believe that the number is prime. After a j
that marks isprime
as false
, the loop will automatically stop.
(And of course, this algorithm is not the best algorithm. There are better algorithms. It's just the one everybody starts with).
Answers to questions raised in the comments:
An inner loop is always executed afresh. Think of it like this:
If a loop says:
for ( int i = 0; i < 3; i++ ) {
doSomething();
}
It's equivalent to
doSomething();
doSomething();
doSomething();
So if the "do something" part is in itself a loop:
for ( int i = 0; i < 3; i++ ) {
for ( int j = 0; j < 2; j++ ) {
runSomething();
}
}
is equivalent to:
for ( int j = 0; j < 2; j++ ) {
runSomething();
}
for ( int j = 0; j < 2; j++ ) {
runSomething();
}
for ( int j = 0; j < 2; j++ ) {
runSomething();
}
So you see, each time it's a new j
that starts from zero.
As for the question about the curly braces: officially, the format of a for
statement is:
for ( initialization; termination; increment )
statement
The statement can be a single statement, like a method call, an inner for loop, an if statement, or something. In that case, it doesn't need braces. Or the statement can be a block, which is a pair of braces that contains zero or more statements. You are used to seeing a block, and indeed it's recommended to always use a block, not a single statement, as it is easier to add statements.