Home | C++ Tutorial | 6. Common Student Problems |     Share This Page
The skill that makes your computer think

Output Formatting

Many students complain that C++ streams are difficult to use for output, when compared to the older C methods. My theory is that, if the situation were reversed and students were required to move from C++ stream formatting back to the old methods, the complaints would be louder.

The biggest single advantage to the stream methods is they are type-safe. If you change a variable's type, the subsequent stream operations using that variable will either automatically accommodate the change, or will indicate an incompatibility at compile time. In older C code, any number of difficult-to-detect bugs result from either incorrectly specifying a variable's type, or changing the variable's type and not remembering all the places where the specifiers need to be changed.

Please remember — this is a very basic introduction. Like this entire tutorial, this page is not a comprehensive guide to stream formatting — it just answers the most commonly heard student questions. A quality textbook should be acquired to serve more general needs.

1. Field Width

Setting field width is very simple. For each variable, simply precede it with "setw(n)". Like this:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	const int max = 12;
	const int width = 6;
	for(int row = 1;row <= max;row++) {
		for(int col = 1;col <= max;col++) {
			cout << setw(width) << row * col;
		}
		cout << endl;
	}
	return 0;
}
    
Notice how "setw(n)" controls the field width, so each number is printed inside a field that stays the same width regardless of the width of the number itself.

2. Justification in Field

Now that you have selected a field, you may wish to decide which side of this field to occupy. As you may imagine, the choices are left and right. Here is an example of switching justification line-by-line:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	const int max = 12;
	const int width = 6;
	for(int row = 1;row <= max;row++) {
		if(row % 2) {
			cout << setiosflags(ios::left);
		}
		else {
			cout << resetiosflags(ios::left);
		}
		for(int col = 1;col <= max;col++) {
			cout << setw(width) << row * col;
		}
		cout << endl;
	}
	return 0;
}
    
Because numbers are by default right-justified, in this case I only need to set and unset the "ios::left" flag. In other situations, you may want to use "ios::right" in code similar to this.

Unfortunately, there are two flags, "ios::left" and "ios::right". This leads to an obvious confusion about which flag is active. In some ambiguous cases, you may have to do this:
	cout << setiosflags(ios::right);
	cout << resetiosflags(ios::left);
    
3. Controlling Precision

"Precision" in this context means the number of decimal places in a floating-point variable. Compile and run the following program:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	double x = 800000.0/81.0;
	cout << setprecision(2) << x;
	return 0;
}
    
This program isn't broken — you asked for two places, it printed two places. If instead you want two decimal places (positions to the right of the decimal point), you must first choose the fixed-point format. Like this:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	double x = 800000.0/81.0;
	cout << setiosflags(ios::fixed) << setprecision(2) << x;
	return 0;
}
    
This is a very common student problem — the default C++ stream behavior for setprecision(n) is to apply the specification to the entire number, not the fractional part. If this is not what you want, set the fixed-point format first.

There is something very important to know about "setprecision(n)". If you choose a precision this way, the displayed number will be rounded off appropriately. Experiment with the following program:
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
	double x = 2.0/3.0;
	cout << setiosflags(ios::fixed) << setprecision(4) << x;
	return 0;
}
    
The result (.6667) is an appropriate four-digit representation of the repeating decimal fraction 2/3. If you do not want this behavior, you will need to think of a different method for display. In most cases, the default behavior is the correct one.

4. Leading zeros

The C++ stream classes provide an easy way to choose a character to fill the leading spaces in a number (and this program has a deliberate bug):

#include <iostream>
#include <iomanip>

using namespace std;

void showDate(int m, int d, int y)
{
	cout << setfill('0');
	// this line has an error
	cout << setw(2) << m << '/' << d << '/' << y << endl;
}

int main()
{
	showDate(1,1,1999);
	return 0;
}
    
The bug (no leading character in the month) is caused by the fact that "setw(n)" is volatile. "setw(n)" only works for a single subsequent variable. You must apply "setw(n)" for each variable. Do it this way:
#include <iostream>
#include <iomanip>

using namespace std;

void showDate(int m, int d, int y)
{
	cout << setfill('0');
	cout << setw(2) << m << '/'
	<< setw(2) << d << '/'
	<< setw(4) << y << endl;
}

int main()
{
	showDate(1,1,1999);
	return 0;
}
    
5. Number Bases other than 10

For base 8 and base 16, this is an easy problem to solve:
#include <iostream>

using namespace std;

int main()
{
	unsigned long x = 64206;
	cout << x
	<< " in base 8 is \"" << oct << x << "\"" 
	<< " and in base 16 is \"" << hex << x << "\"" << endl;
	return 0;
}
    
By the way, this stream formatting for different bases also works for input:
	int x;
	cin >> hex >> x;
    
There is no general arbitrary-base display solution built into the language. Here is an example of a solution:
#include <iostream>
#include <string>

using namespace std;

string convBase(unsigned long v, long base)
{
	string digits = "0123456789abcdef";
	string result;
	if((base < 2) || (base > 16)) {
		result = "Error: base out of range.";
	}
	else {
		do {
			result = digits[v % base] + result;
			v /= base;
		}
		while(v);
	}
	return result;
}

int main()
{
	unsigned long x = 64206;
	cout << "Hex:     " << convBase(x,16) << endl;
	cout << "Decimal: " << convBase(x,10) << endl;
	cout << "Octal:   " << convBase(x,8) << endl;
	cout << "Binary:  " << convBase(x,2) << endl;
	cout << "Test:    " << convBase(x,32) << endl;
	return 0;
}
    
6. Currency

This is an advanced topic, because displaying currency is more complex than it may appear at first sight. There is an advanced C++ feature called "locale" that can handle this problem in a powerful way, but it is not enabled on many compilers (and not yet on the very common compiler I have chosen for this tutorial). Here is a way to display currency:
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

void showCurrency(double dv, int width = 14)
{
	const string radix = ".";
	const string thousands = ",";
	const string unit = "$";
	unsigned long v = (unsigned long) ((dv * 100.0) + .5);
	string fmt,digit;
	int i = -2;
	do {
		if(i == 0) {
			fmt = radix + fmt;
		}
		if((i > 0) && (!(i % 3))) {
			fmt = thousands + fmt;
		}
		digit = (v % 10) + '0';
		fmt = digit + fmt;
		v /= 10;
		i++;
	}
	while((v) || (i < 1));
	cout << unit << setw(width) << fmt.c_str() << endl;
}

int main()
{
	double x = 12345678.90;
	while(x > .001) {
		showCurrency(x);
		x /= 10.0;
	}
	return 0;
}
    
This approach has many drawbacks. I have converted the double to an unsigned long before beginning my algorithm, which limits the range of possible currency amounts. Try experimenting with this code — test the various statements to see how they work. And if you are ambitious, see if you can make a double work directly, without converting to an unsigned long first (and good luck :) ).

Also notice the width argument to "showCurrency()". Notice it is given a default value, which makes it unnecessary to even specify a value when the function is called. This default argument syntax is a standard feature of C++. If you want a width other than 14, you may include your own value in your call to the function. Like this:
		showCurrency(x, 16);
    
7. Comprehension

This page has a number of rather complex examples. Do not simply read this page and go on. Be sure to compile and run the examples, and experiment with them too. Use any textbooks you may have to add to your understanding of the methods shown. And if you don't understand the examples, don't go on.

These pages are Copyright © 2000, P. Lutus. All rights reserved.

www.arachnoid.com Main Page
Home | C++ Tutorial | 6. Common Student Problems |     Share This Page