Homework 5: I/O Manipulators and Base Conversion

Educational Objectives: After completing this assignment the student should have the following knowledge, ability, and skills:

Operational Objectives: Create customized output manipulators and a client program that formats numerical input.

Deliverables: Four files xiomanip.h, xiomanip.cpp, b2b.cpp, and log.txt

Assessment Rubric

Rubric to be used for grading:  
                                          range  :  value
build fmanip.x                             0..5  :  
build b2b.x                                0..5  :  
assess 1: fmanip.x < num1 > fmanip.num1    0..5  :  
assess 2: b2b.x d1.in d1.out               0..5  :  
assess 3: b2b.x d2.in d2.out               0..5  :  
assess 4: b2b.x d3.in d3.out               0..5  :  
code quality                            -20..20  :  
late fees                                0..25%  :  
                                         ------  :  --
total                                     0..50  :  

Note: input files may vary over time and from those distributed.

Code quality includes: 
  - conformance to assignment requirements and specifications [see assignment document]
  - conformance to coding standards [see course organizer]
  - engineering and design, including appropriateness of name choices
  - readability

I/O Manipulators

One of the goals for this assignment is learn how to create and use I/O manipulators.

An I/O manipulator may seem mysterious at first, but it is really a simple function. (The manipulators that take parameters are somewhat more complex, requiring function objects.) Here's how it works:

Various properties of I/O are controlled by "flags", which are public member variables of the base class std::ios and hence inherited by other classes such as std::istream, std::ostream, std::ifstream, and std::ofstream. These flag names are:

boolalpha , dec , fixed , hex , internal , left , oct , right , scientific ,
showbase , showpoint , showpos , skipws , unitbuf , uppercase , adjustfield ,
basefield , floatfield

These names can be manipulated as if they were single bits (and in most implementations they are single bits, although this is not specified by the language). There are also member functions of class ios

void setf   (std::ios::flag flags);
void unsetf (std::ios::flag flags);

that take flags as argument and either set or unset the flags. If, for example, you want to unset the flags dec and oct and set the hex flag for the stream object s, you would make these two calls:

s.unsetf (std::ios::dec | std::ios::oct);
s.setf (std::ios::hex);

There is also a 2-argument version of setf designed to perform of this kind of change in one call:

void setf (std::ios::flag flags, std::ios::flag mask);

This member function unsets the bits defined by mask and then sets the bits defined by flags. This is typically used when there are flags that are related, as in hex,dec,oct. For example, we should not have both the hex and the dec flags set in the same object. Using the defined std::ios::basefield = std::ios::hex | std::ios::dec | std::ios::oct the correct way to set the hex flag would be:

setf (std::ios::hex, std::ios::basefield);

This has the effect of unsetting whatever base flag is set, and then setting the hex flag, thus ensuring that only one of the basefield flags is set.

Note that the scope of the flags must also be resolved as in (std::ios::). Note also the use of bitwise OR to combine the dec and oct flags into a single argument.

An I/O manipulator has the following prototype pattern:

std::ios& M(std::ios& s);  

where M is the name of the manipulator. Note that it takes a stream base object by reference and then returns the same object (modified), so that it makes sense to call the manipulator using the syntax

os << ... << M << ... // manipulator call syntax for an ostream os
is >> ... >> M >> ... // manipulator call syntax for an istream is

This syntax is facilitated by overloads of the input and output operators something like this:

ios& operator << (ios& s, std::ios& f(std::ios&))
{
  return f(s);
}
ios& operator >> (ios& s, std::ios& f(std::ios&))
{
  return f(s);
}

(In case you are wondering - the second parameter is a function f with signature std::ios& f(std::ios&).) These overloads are already defined in the iostream library. So, the only thing left to do is actually write the manipulator function f. Here is how to write the std::hex manipulator function:

std::ios& hex (std::ios& s)
{
  s.setf(std::ios::hex, std::ios::basefield);
  return s;
}

Note the return of the incoming stream object (after its state is modified by changing the flag settings). The effect of this code:

is >> std::hex;
os << std::hex;

is to set the hex flag (by calling hex(os)), unset the other flags in basefield, and return the modified stream object for further processing. For input streams, this will mean that numerical input is assumed to be in hexadecimal notation. For output streams, this will mean that numbers are output in hexadecimal notation.

Procedural Requirements

  1. Create and work within a separate subdirectory cop3330/hw5. Review the COP 3330 rules found in Introduction/Work Rules.

  2. Begin by copying the following files from the course directory: into your hw5 directory:

    LIB/hw5/fmanip.cpp
    LIB/hw5/makefile
    LIB/hw5/data?.in
    LIB/hw5/deliverables.sh
    LIB/scripts/submit.sh
    LIB/area51/b2b_i.x
    LIB/area51/fmanip_i.x
    

    The naming of these files uses the convention that _s are compiled for Sun/Solaris and _i are compiled for Intel/Linux. Use one of the sample client executables to experiment to get an understanding of how your program should behave.

  3. Begin a log file named log.txt. This should be an ascii text file in cop3330/hw5 with the following header:

    log.txt # log file for I/O project
    <date file created>
    <your name>
    <your CS username>
    

    This file should document all work done by date and time, including all testing and test results. A free-form section at the end may be used for any other purpose.

  4. Create the files xiomanip.h, xiomanip.cpp, and b2b.cpp that meet the specifications and requirements below.

  5. Turn in the files xiomanip.h, xiomanip.cpp, and b2b.cpp using submit.sh.

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

Code Requirements and Specifications: fsu manipulators

  1. The manipulators are in the fsu namespace.

  2. The manipulators should be defined (prototyped) in the file xiomanip.h and implemented in the file xiomanip.cpp.

  3. Provide manipulators that accomplish the following:

    fsu I/O Manipulators
    name functionality
    hex hexadecimal, lower case, no fill, no show base
    Hex hexadecimal, upper case, no fill, no show base
    hexFill hexadecimal, lower case, fill, no show base
    HexFill hexadecimal, upper case, fill, no show base
    hexShowBase hexadecimal, lower case, no fill, show base
    HexShowBase hexadecimal, upper case, no fill, show base
    oct octal, no fill, no show base
    octFill octal, fill, no show base
    octShowBase octal, no fill, show base
    dec decimal, no fill, no show base [default settings]

  4. Implement each manipulator using std::ios member functions only - do not use std:: manipulators.

  5. Thoroughly test your manipulators using fmanip.cpp as follows: First compile with the command make fmanip.x. Then run the program and compare the results with the results using the same input on fmanip_i.x. File redirect and the unix diff command are handy for this comparison.

Code Requirements and Specifications: b2b.cpp

  1. The program b2b.cpp must use the manipulators in namespace fsu prototyped in the file xiomanip.h. The only standard manipulator allowed is std::setw(cw) that takes a column width as argument.

  2. The I/O header files included by b2b.cpp should be exactly these:

    #include <iostream>
    #include <fstream>
    #include <iomanip>    // std::setw
    #include <xiomanip.h> // fsu::hex, fsu::hexShowBase, fsu::hexFill, ...
    

  3. Do not use any "using" directives in your code.

  4. The following constants should be declared and used in b2b.cpp:

    static const unsigned short
      cwh = 16, // max digits of unsigned long hexadecimal
      cwd = 20, // max digits of unsigned long decimal
      cwo = 26, // max digits of unsigned long octal
      cs1 = 4, cs2 = 2// col spacers
    

    Do not hard code literal values for column widths and column spacers. These constants are set up to produce tables when the integers are 64-bit (eight byte) words (the size of type unsigned long). The various cw values are the max size of a 64-bit number in the various notations. The cs values are the spacing preceeding the column.

  5. The application b2b should take two file names at the command line. The first is an input file, which contains documentation, instructions, and data. The second is an output file in which a table should be created according to the input file instructions and data.

    Note that there are options specifying the numerical notation for input and for the notation in the second column of output. (The first column of output should be formated the same as the read format for the input file.) The output file should be a table with header (giving the names of the output file and the input file) and column headers indicating the notation used in that column. The data in the table consists of one line for each number in the input file, in the notation specified by the formatting characters. See b2b.x and the example input data files for clarification of this behavior.

  6. The input file is formatted as follows:

    input file format:
    =================
    
      1: file documentation lines at top of file begin with '#'
    
      2: next string of data: (three format control characters)
    
        format for input file
           first character - how to interpret input numbers:
            'o' or 'O': read in octal notation
            'd' or 'D': read in decimal notation
            'h' or 'H': read in hexadecimal notation
    
        next two characters format second column of output
    
          first character of pair: numerical notation
            'd' = decimal
            'D' = decimal
            'h' = hex/lower case
            'H' = hex/upper case
            'o' = octal
            'O' = octal
          second character of pair: fill (affects hex and oct only)
            'f' or 'F' = fill hex and octal with leading zero, 64-bit
            'n' or 'N' = no fill
            'b' or 'B' = no fill and show base
    
      3: remainder of input file: unsigned long integers in notation designated
         by first format control character
    

    The program should skip the documentation, then read the three formatting characters, and then proceed to read the file data, constructing the table in the output file as it goes. (There is no need to store more than one integer at a time, just read an integer and then write the line of the output file table corresponding to that input.)

  7. The output file should begin with two lines of information giving the names of the file and the input data file. The third line should be blank, and the fourth line should have the headers for the table columns. The actual table entries should commence below, and allign correctly with, the column headers.

  8. Be sure that hex and octal numbers are output with the correct number of leading zeros in case 'f'. The defined constants cwx above give the correct size needed for 64-bit numbers.

  9. Output from your b2b.x should duplicate exactly the output produced by the distributed executables. When in doubt, use the distributed executables for guidance in constructing the output file.

Hints - xiomanip

Hints - b2b