Exercise 4-4

Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns.

Solution

As the nature of this question is somewhat similar to Exercise 4-3, it makes sense to re-use (some aspect of) Solution to Exercise 4-3, such as the program skeleton structure.

This time however, the question asks us to use double values instead of ints. In other words, the final output will contain two columns. Column 1 lists a defined set of double values. Column 2 lists the square of column 1. We can write the program in a way that the two columns have suitable (fixed) widths and precision values (number of significant). So the output will consistently look “neat”.

In this post I will describe the C++ program that I write to solve this particular problem. I would like to emphasise that the program has its limitation. It works pretty well EXCEPT the range of between -1 and +1. More work may be required to make the program cope with this “taboo” range. For now, and for simplicity sake, I will assume the program is “good enough” as it covers pretty most types of scenarios – the program works range for most negative and positive ranges. (We will avoid this taboo range for now).

Algorithm

Before writing the program it is very important to have a mental image of what the width and precision mean / look like, which will enable us to form the underlying algorithm.

Algorithm – Compute Width

Width is the total number of characters required to fit the output “number-string” to the output console window. The width needs to be “big enough” to fit the number string – otherwise the right part of the string is trimmed away.

We compute the total width as the sum of the followings:

• Negative sign (for negative number only): takes up 1 character width.
• Leading space: takes up 1 character width.
• Integer width: can be computed by couting number of “division by 10” required to make the number less than 1.
• Decimal point: takes up 1 character width.
• Decimals: we define how many number of decimals we wish to display.

Examples:

• e.g. if the number is 123.45 (and we set the Decimals to 2), then total width required is 7 (1 leading space + 3 integer width + 1 decimal point + 2 decimals).
• e.g. if the number is -123.45 (and we set the Decimals to 2), then total width required is 8 (1 negative sign + 1 leading space + 3 integer width + 1 decimal point + 2 decimals).

Algorithm – Compute Precision

Precision is the total number of significant and is used to control how we display the number, keeping the width constant.

Precision = Width – Gap

• If negative number: Gap = 3 (1 negative sign + 1 leading space + 1 decimal point)
• If positive number: Gap = 2 (1 leading space + 1 decimal point)

Using the same examples

• if the number is 123.45 and we wish to display the whole 123.45 string, we need a precision of 5 (7 Width – 2 Gap).
• if the number is -123.45 and we wish to display the whole 1-23.45 string, we need a precision of 5 (8 Width – 3 Gap).

Being able to visualise Width and Precision, we are now in a position to form our solution strategy.

Solution Strategy

1. We re-use the skeleton program structure as per Solution to Exercise 4-3 by (1) amending the main program, (2) overloading the getStreamWidth function, and (3) updating the required header files.
2. The output will consist of column 1 and column 2. Column 1 is the number (for squaring). Column 2 is the square of column 1.
3. We define important properties: the asymmetric range [const double m, const double n), and the interval const double i.
4. We also define the core format of the columns by specifying the max number of decimals const streamsize d. This will aid the width computation of each column.
5. We create a new (overloaded) getStreamWidth function to return a total width (of type std::streamsize)  required to fit a number. For learning sake, I have decided to overload the getStreamWidth function (as per previous exercise). The function will require two input parameters (1) double number, and (2) std::streamsize numDecimals. The double number will tell us whether we require an extra space for the negative sign. It also helps us to compute the integer width (by counting number of “division by 10” required to make the number less than 1). The std::streamsize numDecimals provides the remaining information for us to compute the total width. See the Algorithm – Compute Width section above to see the algorithm used.
6. We identify the max width required for column 1 and column 2. For negative range, the start number will likely have more digits (and bigger width). For positive range, the end number is likely to have more digits (and bigger width). To find the max width, we therefore compute the width for both start and end number, and pick out the bigger width with the std::max function.
7. We dynamically compute the precision required for column 1 and column 2, using the algorithm described in the Algorithm – Compute Precision section above.
8. We use a while loop to create column 1, dynamically create column 2, and apply the pre-computed widths and precisions against the columns – so the output displays as expected.
9. (Optional) We end the output step by displaying some core parameters used, such as the widths and precisions computed for column 1 and 2. This will aid us understand the program better. It’s purely for educational purposes.

The Project

Like my Solution to Exercise 4-3, I have decided to partition the program as follows. I have (1) amended the main program, (2) overloaded the getStreamWidth function, and (3) updated the required header files.

C++ Source Files

• main.cpp – this is the first program that is run during the implementation phase.
• getStreamWidth.cpp – contains all functions relating to obtaining the stream widths.

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Source Files

main.cpp

{
// Adjust these initial values for different tests
const double m = 5;          // start of range (inclusive)
const double n = 6;          // end of range (exclusive)
const double i = 0.005;      // incremental interval
const streamsize d = 3;      // number of decimal places

// find the maxwidth for column 1 and 2
const streamsize col1Width = max(getStreamWidth(m, d), getStreamWidth(n, d));
const streamsize col2Width = max(getStreamWidth(m * m, d), getStreamWidth(n * n, d));

// Compute precision.
// Precision = width - gap
// If involves negative region: precision = width - (leading space + decimal point + negative sign) = width - 3.
// else, precision = width - ( leading space + decimal point ) = width - 2.
streamsize gap;
if (m < 0 || n < 0)
gap = 3; // leading space + decimal point + negative sign
else
gap = 2; // leading space + decimal point

const streamsize col1Precision = col1Width - gap;
const streamsize col2Precision = col2Width - 2;   // the square of a number is always positive. i.e. no negative sign!

// get ready to print report
double y = m;
while (y < n)
{
cout << setw(col1Width) << setprecision(col1Precision) << y
<< setw(col2Width) << setprecision(col2Precision) << (y * y)
<< setw(0) << endl;
y += i;
}

// display a summary
cout << "start = " << m << ", end = " << n << ", increment = " << i << ", decimalPlaces = " << d << endl;
cout << "Column 1 width = " << col1Width << " | Column 2 width = " << col2Width << endl;
return 0;
}


getStreamWidth.cpp

#include <ios>

using std::streamsize;

// Original function for Solution to Exercise 4-3
// return the required streamsize to fit a particular integer number
streamsize getStreamWidth(int number)
{

streamsize numDigits;

// initialise numDigits and number depending on whether value is positive / negative.
// If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
// If positive, require at least 1 space to fit the leading empty space string
if (number < 0)
{
numDigits = 2;
number *= -1;
}
else numDigits = 1;

// numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
// i.e. this is equivalent to the total stream width required
while (number != 0)
{
++numDigits;
number /= 10;
}

return numDigits;
}

// overloaded function for Solution to Exercise 4-4
// return the required streamsize to fit a particular double number, given number of decimals
streamsize getStreamWidth(double number, streamsize numDecimals)
{
streamsize numDigits = numDecimals;

// initialise numDigits and number depending on whether value is positive / negative.
// If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
// If positive, require at least 1 space to fit the leading empty space string
if (number < 0)
{
numDigits = 3 + numDecimals;  // (leading space + negative sign + decimal point) + decimals
number *= -1;
}
else numDigits = 2 + numDecimals;  // (leading space + decimal point) + decimals

// numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
// i.e. this is equivalent to the total stream width required
while (number >= 1)
{
++numDigits;
number /= 10;
}
return numDigits;
}


getStreamWidth.h

#ifndef GUARD_GETSTREAMWIDTH_H
#define GUARD_GETSTREAMWIDTH_H

std::streamsize getStreamWidth(int number);
std::streamsize getStreamWidth(double number, std::streamsize numDecimals);

#endif // GUARD_GETSTREAMWIDTH_H


Result

I shall adjust the values [const double m, const double n), increment const double i, and decimals const streamsize d. To give us some assurance that the program is fairly stable and consistent.

Experiment 1 – Define Parameters

    const double m = 5;          // start of range (inclusive)
const double n = 6;          // end of range (exclusive)
const double i = 0.005;      // incremental interval
const streamsize d = 3;      // number of decimal places


Experiment 1 – Result

     5     25
5.005  25.05
5.01   25.1
5.015  25.15
5.02   25.2
5.025 25.251
5.03 25.301
5.035 25.351
5.04 25.402
5.045 25.452
5.05 25.502
5.055 25.553
5.06 25.604
5.065 25.654
5.07 25.705
5.075 25.756
5.08 25.806
5.085 25.857
5.09 25.908
5.095 25.959
5.1  26.01
5.105 26.061
5.11 26.112
5.115 26.163
5.12 26.214
5.125 26.266
5.13 26.317
5.135 26.368
5.14  26.42
5.145 26.471
5.15 26.522
5.155 26.574
5.16 26.626
5.165 26.677
5.17 26.729
5.175 26.781
5.18 26.832
5.185 26.884
5.19 26.936
5.195 26.988
5.2  27.04
5.205 27.092
5.21 27.144
5.215 27.196
5.22 27.248
5.225 27.301
5.23 27.353
5.235 27.405
5.24 27.458
5.245  27.51
5.25 27.562
5.255 27.615
5.26 27.668
5.265  27.72
5.27 27.773
5.275 27.826
5.28 27.878
5.285 27.931
5.29 27.984
5.295 28.037
5.3  28.09
5.305 28.143
5.31 28.196
5.315 28.249
5.32 28.302
5.325 28.356
5.33 28.409
5.335 28.462
5.34 28.516
5.345 28.569
5.35 28.622
5.355 28.676
5.36  28.73
5.365 28.783
5.37 28.837
5.375 28.891
5.38 28.944
5.385 28.998
5.39 29.052
5.395 29.106
5.4  29.16
5.405 29.214
5.41 29.268
5.415 29.322
5.42 29.376
5.425 29.431
5.43 29.485
5.435 29.539
5.44 29.594
5.445 29.648
5.45 29.702
5.455 29.757
5.46 29.812
5.465 29.866
5.47 29.921
5.475 29.976
5.48  30.03
5.485 30.085
5.49  30.14
5.495 30.195
5.5  30.25
5.505 30.305
5.51  30.36
5.515 30.415
5.52  30.47
5.525 30.526
5.53 30.581
5.535 30.636
5.54 30.692
5.545 30.747
5.55 30.802
5.555 30.858
5.56 30.914
5.565 30.969
5.57 31.025
5.575 31.081
5.58 31.136
5.585 31.192
5.59 31.248
5.595 31.304
5.6  31.36
5.605 31.416
5.61 31.472
5.615 31.528
5.62 31.584
5.625 31.641
5.63 31.697
5.635 31.753
5.64  31.81
5.645 31.866
5.65 31.922
5.655 31.979
5.66 32.036
5.665 32.092
5.67 32.149
5.675 32.206
5.68 32.262
5.685 32.319
5.69 32.376
5.695 32.433
5.7  32.49
5.705 32.547
5.71 32.604
5.715 32.661
5.72 32.718
5.725 32.776
5.73 32.833
5.735  32.89
5.74 32.948
5.745 33.005
5.75 33.062
5.755  33.12
5.76 33.178
5.765 33.235
5.77 33.293
5.775 33.351
5.78 33.408
5.785 33.466
5.79 33.524
5.795 33.582
5.8  33.64
5.805 33.698
5.81 33.756
5.815 33.814
5.82 33.872
5.825 33.931
5.83 33.989
5.835 34.047
5.84 34.106
5.845 34.164
5.85 34.222
5.855 34.281
5.86  34.34
5.865 34.398
5.87 34.457
5.875 34.516
5.88 34.574
5.885 34.633
5.89 34.692
5.895 34.751
5.9  34.81
5.905 34.869
5.91 34.928
5.915 34.987
5.92 35.046
5.925 35.106
5.93 35.165
5.935 35.224
5.94 35.284
5.945 35.343
5.95 35.402
5.955 35.462
5.96 35.522
5.965 35.581
5.97 35.641
5.975 35.701
5.98  35.76
5.985  35.82
5.99  35.88
5.995  35.94
6     36
start = 5, end = 6, increment = 0.005, decimalPlaces = 3
Column 1 width = 6 | Column 2 width = 7

Process returned 0 (0x0)   execution time : 0.509 s
Press any key to continue.


Experiment 2 – Define Parameters

    const double m = -6;          // start of range (inclusive)
const double n = -5;          // end of range (exclusive)
const double i = 0.005;      // incremental interval
const streamsize d = 3;      // number of decimal places


Experiment 2 – Result

     -6     36
-5.995  35.94
-5.99  35.88
-5.985  35.82
-5.98  35.76
-5.975 35.701
-5.97 35.641
-5.965 35.581
-5.96 35.522
-5.955 35.462
-5.95 35.403
-5.945 35.343
-5.94 35.284
-5.935 35.224
-5.93 35.165
-5.925 35.106
-5.92 35.046
-5.915 34.987
-5.91 34.928
-5.905 34.869
-5.9  34.81
-5.895 34.751
-5.89 34.692
-5.885 34.633
-5.88 34.574
-5.875 34.516
-5.87 34.457
-5.865 34.398
-5.86  34.34
-5.855 34.281
-5.85 34.223
-5.845 34.164
-5.84 34.106
-5.835 34.047
-5.83 33.989
-5.825 33.931
-5.82 33.872
-5.815 33.814
-5.81 33.756
-5.805 33.698
-5.8  33.64
-5.795 33.582
-5.79 33.524
-5.785 33.466
-5.78 33.408
-5.775 33.351
-5.77 33.293
-5.765 33.235
-5.76 33.178
-5.755  33.12
-5.75 33.063
-5.745 33.005
-5.74 32.948
-5.735  32.89
-5.73 32.833
-5.725 32.776
-5.72 32.718
-5.715 32.661
-5.71 32.604
-5.705 32.547
-5.7  32.49
-5.695 32.433
-5.69 32.376
-5.685 32.319
-5.68 32.262
-5.675 32.206
-5.67 32.149
-5.665 32.092
-5.66 32.036
-5.655 31.979
-5.65 31.923
-5.645 31.866
-5.64  31.81
-5.635 31.753
-5.63 31.697
-5.625 31.641
-5.62 31.584
-5.615 31.528
-5.61 31.472
-5.605 31.416
-5.6  31.36
-5.595 31.304
-5.59 31.248
-5.585 31.192
-5.58 31.136
-5.575 31.081
-5.57 31.025
-5.565 30.969
-5.56 30.914
-5.555 30.858
-5.55 30.803
-5.545 30.747
-5.54 30.692
-5.535 30.636
-5.53 30.581
-5.525 30.526
-5.52  30.47
-5.515 30.415
-5.51  30.36
-5.505 30.305
-5.5  30.25
-5.495 30.195
-5.49  30.14
-5.485 30.085
-5.48  30.03
-5.475 29.976
-5.47 29.921
-5.465 29.866
-5.46 29.812
-5.455 29.757
-5.45 29.703
-5.445 29.648
-5.44 29.594
-5.435 29.539
-5.43 29.485
-5.425 29.431
-5.42 29.376
-5.415 29.322
-5.41 29.268
-5.405 29.214
-5.4  29.16
-5.395 29.106
-5.39 29.052
-5.385 28.998
-5.38 28.944
-5.375 28.891
-5.37 28.837
-5.365 28.783
-5.36  28.73
-5.355 28.676
-5.35 28.623
-5.345 28.569
-5.34 28.516
-5.335 28.462
-5.33 28.409
-5.325 28.356
-5.32 28.302
-5.315 28.249
-5.31 28.196
-5.305 28.143
-5.3  28.09
-5.295 28.037
-5.29 27.984
-5.285 27.931
-5.28 27.878
-5.275 27.826
-5.27 27.773
-5.265  27.72
-5.26 27.668
-5.255 27.615
-5.25 27.563
-5.245  27.51
-5.24 27.458
-5.235 27.405
-5.23 27.353
-5.225 27.301
-5.22 27.248
-5.215 27.196
-5.21 27.144
-5.205 27.092
-5.2  27.04
-5.195 26.988
-5.19 26.936
-5.185 26.884
-5.18 26.832
-5.175 26.781
-5.17 26.729
-5.165 26.677
-5.16 26.626
-5.155 26.574
-5.15 26.523
-5.145 26.471
-5.14  26.42
-5.135 26.368
-5.13 26.317
-5.125 26.266
-5.12 26.214
-5.115 26.163
-5.11 26.112
-5.105 26.061
-5.1  26.01
-5.095 25.959
-5.09 25.908
-5.085 25.857
-5.08 25.806
-5.075 25.756
-5.07 25.705
-5.065 25.654
-5.06 25.604
-5.055 25.553
-5.05 25.503
-5.045 25.452
-5.04 25.402
-5.035 25.351
-5.03 25.301
-5.025 25.251
-5.02   25.2
-5.015  25.15
-5.01   25.1
-5.005  25.05
-5     25
start = -6, end = -5, increment = 0.005, decimalPlaces = 3
Column 1 width = 7 | Column 2 width = 7

Process returned 0 (0x0)   execution time : 0.364 s
Press any key to continue.


Experiment 3 – Define Parameters

    const double m = 1;          // start of range (inclusive)
const double n = 1000;          // end of range (exclusive)
const double i = 0.1;      // incremental interval
const streamsize d = 2;      // number of decimal places


Experiment 3 – Result

       1          1
1.1       1.21
1.2       1.44
1.3       1.69
1.4       1.96
1.5       1.25
.....etc ....
990.1  980298.01
990.2  980496.04
990.3  980694.09
990.4  980892.16
990.5  981090.25
990.6  981288.36
990.7  981486.49
990.8  981684.64
990.9  981882.81
991     982081
991.1  982279.21
991.2  982477.44
991.3  982675.69
991.4  982873.96
991.5  983072.25
991.6  983270.56
991.7  983468.89
991.8  983667.24
991.9  983865.61
992     984064
992.1  984262.41
992.2  984460.84
992.3  984659.29
992.4  984857.76
992.5  985056.25
992.6  985254.76
992.7  985453.29
992.8  985651.84
992.9  985850.41
993     986049
993.1  986247.61
993.2  986446.24
993.3  986644.89
993.4  986843.56
993.5  987042.25
993.6  987240.96
993.7  987439.69
993.8  987638.44
993.9  987837.21
994     988036
994.1  988234.81
994.2  988433.64
994.3  988632.49
994.4  988831.36
994.5  989030.25
994.6  989229.16
994.7  989428.09
994.8  989627.04
994.9  989826.01
995     990025
995.1  990224.01
995.2  990423.04
995.3  990622.09
995.4  990821.16
995.5  991020.25
995.6  991219.36
995.7  991418.49
995.8  991617.64
995.9  991816.81
996     992016
996.1  992215.21
996.2  992414.44
996.3  992613.69
996.4  992812.96
996.5  993012.25
996.6  993211.56
996.7  993410.89
996.8  993610.24
996.9  993809.61
997     994009
997.1  994208.41
997.2  994407.84
997.3  994607.29
997.4  994806.76
997.5  995006.25
997.6  995205.76
997.7  995405.29
997.8  995604.84
997.9  995804.41
998     996004
998.1  996203.61
998.2  996403.24
998.3  996602.89
998.4  996802.56
998.5  997002.25
998.6  997201.96
998.7  997401.69
998.8  997601.44
998.9  997801.21
999     998001
999.1  998200.81
999.2  998400.64
999.3  998600.49
999.4  998800.36
999.5  999000.25
999.6  999200.16
999.7  999400.09
999.8  999600.04
999.9  999800.01
start = 1, end = 1000, increment = 0.1, decimalPlaces = 2
Column 1 width = 8 | Column 2 width = 11

Process returned 0 (0x0)   execution time : 4.964 s
Press any key to continue.


Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000