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);