Skip to main content

Section 8.13 Case Study: Finding Primes

Subsection 8.13.1 The problem

Recall that prime numbers are those that are only divisible by 1 and themselves. They play a significant role in computer cryptography where they help to form computationally hard problems.
We would like to find all of the prime numbers less than or equal to a value n. We will do so by making two functions. One that decides if a given number is prime and a second that counts the primes up to n.
bool isPrime(int number);

int countPrimesUpTo(int n);

Subsection 8.13.2 Understanding isPrime and Tests

Let us start with the isPrime function. We will need to test it with a variety of numbers. We want to test it with some numbers that are prime and some that are not. 3, 7, 17, and 23 are all prime numbers. 4, 15, and 181 are not. We will also test it with 1 and 2, which are special cases. 1 is not prime (prime numbers should have 2 factors), and 2 is the only even prime number.
Let’s turn some of those into test values. If the following CHECKs all pass, we should feel good about our function:
// The tests
TEST_CASE("isPrime") {
    CHECK(isPrime(3) == true);
    CHECK(isPrime(7) == true);
    CHECK(isPrime(17) == true);
    CHECK(isPrime(4) == false);
    CHECK(isPrime(15) == false);
    // special cases
    CHECK(isPrime(1) == false);
    CHECK(isPrime(2) == true);
}

Subsection 8.13.3 isPrime

Let’s build the isPrime function. The activity below will help you build your way to a solution in the exercise below it.

Activity 8.13.1. isPrime Guide.

Work through the tabs (a), (b)... in order to build your way to a solution of the exercise below.
(a)
To test if a number n is prime, we need to see if anything in the range 2 to n - 1 can divide into it. The starter code has a counting loop that is missing some pieces (marked with ???). Fill in the blanks so that the loop counts from 2 to n - 1.
Don’t worry about passing the tests yet, just look at the program output and verify that the loop is iterating the correct number of times. Make sure your counter does not go all the way to n or stop at n - 2.
(b)
Now let’s check if the current value of the loop counter divides into n. Recall that if n is divisible by i, then n % i will be 0. Add a conditional to check if i divides into n. If so, print out "NOT A PRIME!".
Again, don’t worry about passing the tests yet, just look at the program output and verify that the code prints out the message at the right times.
(c)
If you find a factor, there is no point continuing on with the loop - we know that n is not prime. One way to handle this would be to use break to stop if we find a factor. We would need a variable to keep track of if we think the number is prime. Initially, we would assume it is. If we find a divisor we would change our mind. Here is pseudocode for that approach:
isPrime = true
loop from 2 to n - 1
    if n is divisible by i
        isPrime = false
        break
return isPrime
Another way would be to simply return false; as soon as we find the factor. This approach would have the advantage of not needing a variable to keep track of our answer.
Choose one of these approaches and implement it. You should pass most of the tests now, but will still fail the special case of 1.
(d)
We still have a problem with the special case. Our loop never runs if n is 1. So we end up returning true. Sometimes the best way to deal with a special case is to just add logic to handle it. Add a condition that returns false if n == 1. This logic could go either before or after the loop.
You should now pass all of the tests, but will have a lot of extra output.
(e)
Now remove all the cout statements. They were helpful for testing and debugging your code, but will not be needed or wanted in the final version of the function. Make sure you still pass all the tests!

Checkpoint 8.13.1.

Use the guide above to build isPrime.

Subsection 8.13.4 Understanding countPrimes and Tests

Count primes needs to check each value from 1 to n to see if it is prime. Consulting the internet to find a list of prime numbers, it appears that there are 2 primes less than or equal to 4, 4 primes less than or equal to 10, and 25 primes less than or equal to 100. A tricky case for this problem might be right at a number that is prime. 11 is the 5th prime, so let’s include it as a test case:
// The tests
TEST_CASE("countPrimes") {
    CHECK(countPrimes(2) == 1);
    CHECK(countPrimes(4) == 2);
    CHECK(countPrimes(10) == 4);
    CHECK(countPrimes(11) == 4);
    CHECK(countPrimes(100) == 25);
}

Subsection 8.13.5 countPrimes

Count primes needs to check each value from 1 to n to see if it is prime. Fortunately, we now have isPrime to do the checking for us. We will need an accumulator to keep track of how many primes we have seen. The guide below will help you build out the needed code in the exercise below.

Activity 8.13.2. countPrimes Guide.

Work through the tabs (a), (b)... in order to build your way to a solution of the exercise below.
(a)
Let’s start in on the rest of the code for countPrimes. We know we need to count from 1 to n. Write a loop that does that and prints out each value. Don’t worry about checking primes or passing the tests. Just make sure the loop counts through he right numbers.
(b)
To identify the primes will need your isPrime function. Copy the final version and paste it into the program below where the comment indicates.
Try testing each number that your loop counts through by calling your isPrime function and passing it the current number. If isPrime returns true, print out "Prime".
(c)
Any time you print "Prime", also make sure to increment the variable numPrimes. It will get returned at the end of the function.
You should now pass the tests. You can remove the cout lines from the function.

Checkpoint 8.13.2.

Use the guide above to build countPrimes.

Subsection 8.13.6 Wrapup

You now hopefully have a working countPrimes function. At this point, building a main function to get some input and print out the final result is easy. Here is such a program. If you want, you can paste in your functions from the exercise above to try it out:
Listing 8.13.1.
This case study previews some of the advantages of doing iterative development - building and testing one piece of a program at a time. We will learn more about solving complex problems by breaking them down in a future chapter.
It also demonstrates the advantages of building abstractions - wrapping complex logic into a function like isPrime so that you don’t have to worry about those details when you just want to know β€œis this number prime?”.
Instead of writing that function, we could have written the logic for isPrime into countPrimes. That would involve a nested loop where the outer loop counted i from 2 to n and the inner loop counted from 2 to i, checking to see if any value divides into i. Although that would work, it would be more complex to build and debug. Making the

Note 8.13.1.

Our functions aren’t very efficient. There are some tweaks we could make to speed them up (for instance, there is no point in checking values larger than \(\sqrt{n}\) to see if they are factors, so we could stop the isPrime loop at that point). But there are much faster algorithms for determining if values are prime. So if efficiency was a big concern, we would be better off going back to the drawing board than trying to improve this algorithm.
You have attempted of activities on this page.