Homework 1: Sieve of Eratosthenes

Educational Objectives: After completing this assignment, the student should be able to accomplish the following:

Operational Objectives: Implement the class alt::BitVector and provide a client program sieve.cpp for alt::BitVector that implements the Sieve of Eratosthenes algorithm to compute all prime numbers less than or equal to an input integer.

Deliverables: Three files: sieve.cpp, bitvect.cpp, and log.txt.

The Sieve of Eratosthenes Algorithm

Algorithm Assumptions

b is a bitvector indexed in the range [0 ... n). Bit k of b is denoted by b[k].

Algorithm Outcomes

After invoking the sieve algorithm body, an integer k in the range [0 ... n) is prime iff b[k] is set.

Algorithm Body

  1. Begin with a bitvector b indexed in the range 0 <= k < n.
  2. Initialize b by setting all bits.
  3. Clear b[0] and b[1] (because 0 and 1 are not prime).
  4. For k between 2 and the square root of n, stepsize 1:
      if b[k] is set
        for j between k + k and n, stepsize k:
          clear b[j].
  5. Stop.
  6. In short, clear the bits of all multiples of primes less than the square root of n.

Algorithm Proof

Given a bitvector b, indexed from 0 to n, the sieve algorithm begins with all bits set. The outcome to be proved is that the algorithm ends with b[k] set iff k is prime. The proof makes use of two useful observations about prime numbers.

  1. If k is not prime, then k is divisible by a prime. In other words, the non-prime numbers are precisely the numbers that are multiples of smaller prime numbers.
  2. If a number is not prime, then it is divisible by a prime that is no greater than the square root of the number.
  3. Proof of observation 2: Let k = pq, and suppose p <= q. Then

    p2 <= pq = k

    and hence the square root of p is no greater than the square root of k, using the monotonicity of the square root function.

Proof of Sieve: Suppose that k is an integer in the range 0 <= k < n and that the sieve algorithm body has been invoked. We must show that k is prime iff b[k] is set. The cases k = 0 and k = 1 are handled by the initialization process, so we may assume that k is in the range [2 ... n).

Suppose b[k] is set. If k is not prime then k is a multiple of a prime less than the square root of k (by observation 2) and therefore b[k] would have been cleared by the algorithm, contradicting the assumption that b[k] is set.

Conversely, suppose b[k] is cleared. Then that bit was cleared because k was a multiple of some smaller prime, so k is not prime.

Final remark - what Eratosthenes was thinking: Clearly, the big E did not use bitvectors. His approach went something like this: Imagine the numbers 1..n all written down in a list. We will cross all the composit numbers off of the list, so that those that are left must be all of the non-composit, that is, prime, numbers. The E-man went on to describe how to cross numbers off: first cross off 1, keep 2, and then cross off all multiples of 2. Go to the next number not crossed off (which must be prime) and cross of all of its multiples. Keep going until the list is exhausted.

The BitVector API

Bit vectors, the BitVector API (application programming interface), and implementation techniques are discussed at length in the lecture notes. We repeat here a statement of the API (using "unsigned int" instead of "size_t" for readability):

std::ostream& operator << (std::ostream&, const BitVector&);
// send representation of bits as 0,1 digits

class BitVector
{
public:
  explicit BitVector  (unsigned int);     // construct a BitVector with specified size
           BitVector  (const BitVector&); // copy constructor      
           ~BitVector ();                 // destructor

  BitVector& operator = (const BitVector& a);  // assignment operator

  unsigned int Size () const;             // return size of bitvector

  void Set   ();                          // make all bits = 1
  void Set   (unsigned int index);        // make bit at index = 1
  void Unset ();                          // make all bits = 0
  void Unset (unsigned int index);        // make bit at index = 0
  void Flip  ();                          // change all bits 
  void Flip  (unsigned int index);        // change bit at index 
  bool Test  (unsigned int index) const;  // return bit value as bool

} ; // class BitBector

Both fsu::BitVector and alt::BitVector will conform to this API.

Procedural Requirements

  1. All of the work for this assignment should be done in your subdirectory cop4530/hw1.

  2. Maintain an ascii (text) file named log.txt with entries giving date, time, duration and task description for all work on this project. Example entries:

    01-15-10, 1:30pm, 2 hours
    -------------------------
    - copied distributed files, initialized all other project files
    - experimented extensively with the area51 executables - believe I understand how they work
    - studied the Sieve algorithm, traced it on paper for p = 64
    
    01-16-10, 1:30pm, 1 hour
    ------------------------
    - first pass coding sieve.cpp
    - wrote main() and Sieve()
    - debugged compile errors using co4530
    - read Sieve() again too be sure it implements the algotithm correctly
    
    01-16-10, 7:00pm, 2 hours (second session today!)
    -------------------------
    - built executable using fsu::BitVector; this took a while to get right
    - tested; seems to be working OK!!!
    

  3. Copy the following files from HOME into your assignment directory:

    tests/fbitvect.cpp
    hw1/bitvect.h
    hw1/makefile
    hw1/hw1submit.sh
    

    Then change permissions on the script to x-- (executable by you only) with the command chmod 700 hw1submit.sh.

  4. Create the files sieve.cpp and bitvect.cpp, according to the specifications below.

  5. Turn in the files sieve.cpp, bitvect.cpp, and log.txt using the script hw1submit.sh.

  6. Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit this assignment. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.

Technical Requirements and Specificatons: sieve.cpp

  1. The file sieve.cpp should contain these elements:

    typedef fsu::BitVector BitVector;
    // typedef alt::BitVector BitVector;
    ...
    void Sieve (BitVector& bv);
    ...
    int main()
    {
      ...
    }
    
  2. Function Sieve() should take a BitVector object bv by reference and perform the sieve algorithm on it, so that when the function returns, bits of bv are set if and only if the index of the bit is a prime number. Note that Sieve() is a function only. It does not do any interaction with users, streams, or files; it only alters the state of the BitVector passed in by reference.

  3. Function main() should handle the I/O: query the user for a positive integer and output a list consisting of all prime numbers that are less than or equal to the input. (See example executable.)

  4. The entire sieve program should be a BitVector client, that is, use the BitVector public interface (API). This means that the application should work equally well as a client of fsu::BitVector and a client of alt::BitVector.

  5. Thoroughly test your sieve program using the fsu::BitVector type definition.

Technical Requirements and Specificatons: alt::BitVector

  1. The file bitvect.h should not be modified in any way, because your solution must work with the distributed version.

  2. The file bitvect.cpp should implement all of the alt::BitVector methods, including friends and members, functions and operators, as defined in the file hw1/bitvect.h. Note that alt::BitVector uses a vector of size_t as its private storage mechanism, that is, an fsu::TVector<size_t> object.

  3. The body of the constructor should be as follows:

    {
      if (sizeof(size_t) != 8)
      {
        std::cerr << "** size mismatch: sizeof(size_t) = " << sizeof(size_t) << '\n';
        exit (EXIT_FAILURE);
      }
    }
    

    This code checks that the type size_t is an 8 byte (64 bit) word. The initializations should be handled in an initialization list.
    Note: The Linprog machines are 64-bit architecture. Most Intel machines are 32-bit, as are the program machines, so it is likely you will need to work on linprog.

  4. You should use the public interface of fsu::TVector for all implementation code. Do not create any arrays directly - memory management is taken care of by fsu::TVector.

  5. Test alt::BitVector thoroughly, using both your sieve.cpp discussed above as well as fbitvect.cpp.

Technical Requirements and Specificatons: General

  1. Your code should conform to the coding standards outlined in the document Coding Standards available through the Course Organizer.

  2. Your project should build without warnings using the distributed makefile.

Hints: