I wrote the ftoc.cpp programming example in the video using Visual Studio 2015. Like all IDEs, VS evolves with each new release. Please review Visual Studio Demonstration from the previous chapter for an up-to-date (at the time of this writing) example of VS (2019). The programming description, validation, errors, and debugging remain relevant for all IDEs - even those running on different operating systems.
It is much easier to learn how to use the arithmetic operators by seeing them used in a real example. The following program converts a temperature entered in Fahrenheit to an equivalent temperature expressed in Celsius by implementing the formula in Figure 1. It demonstrates the division operator's uncommon behavior and how programmers inadvertently introduce truncation errors when they fail to understand that behavior.
Four slightly different programs attempt to convert the Fahrenheit-to-Celsius formula into C++ code. The first program is mostly correct but fails with a truncation error. Programs 2-4 illustrate different ways of solving the truncation error in the first program. Several concepts are the same in each program:
The assignment operator is right associative and has a relatively low precedence. The remaining arithmetic operators are left-associative; multiplication and division have a higher precedence than addition and subtraction.
Grouping parentheses override the default operator precedence, so the subtraction operation in (f - 32) occurs before any adjacent multiplication or division operations.
The two values, f and 32, are not the same type (f is type double while the constant 32 is type int). The compiler cannot combine different data types, but it can promote the int to a double, allowing the program to complete the evaluation.
The result of (f - 32) is type double (i.e., it is a double-valued expression).
Test Cases
A test case is a set of input values for a given problem and the corresponding output values. Sometimes, the problem itself will suggest obvious test cases; other times, the software designer must arbitrarily choose test cases and manually calculate the output values. The Fahrenheit to Celsius problem has two obvious test cases based on two well-known physical constants: the freezing and boiling points of water on both scales.
Scale
Freezing
Boiling
Fahrenheit
32
212
Celsius
0
100
Incorrect Program
Program
Output
#include <iostream>
using namespace std;
int main()
{
double f;
cout << "Enter a temperature in Fahrenheit: ";
cin >> f;
double c = 5 / 9 * (f - 32); // error
cout << "The temperature in Celsius = " << c << endl;
return 0;
}
Enter a temperature in Fahrenheit: 32
The temperature in Celsius = 0
Enter a temperature in Fahrenheit: 212
The temperature in Celsius = 0
An incorrect program. The statement c = 5 / 9 * (f - 32)looks like a correct implementation of the formula in Figure 1. The only change is translating the mathematical division and multiplication to C++ operators. Furthermore, the first test case seems to validate the program. But, the failure of the second case signals a logical error in the program, and we must find and understand it.
All of the operators are left-associative, so 5/9 is evaluated first. The compiler reads both 5 and 9 as integers, so the program completes the division operation with integer arithmetic, causing a truncation error resulting in a 0.
It doesn't matter what value (f-32) produces because when (f-32) is multiplied by 0 the result is always 0.
0 is stored in c.
Correct Programs
The following programs illustrate different ways of solving the truncation error in the previous program. Only the statement calculating the Celsius value varies between the examples. Each correct version produces the same output.
Program
Output
#include <iostream>
using namespace std;
int main()
{
double f;
cout << "Enter a temperature in Fahrenheit: ";
cin >> f;
double c = 5.0 / 9.0 * (f - 32); // correct
cout << "The temperature in Celsius = " << c << endl;
return 0;
}
Enter a temperature in Fahrenheit: 32
The temperature in Celsius = 0
Enter a temperature in Fahrenheit: 212
The temperature in Celsius = 100
Correcting a truncation error with floating-point constants. Replacing 5/9 with 5.0/9.0 causes the program to use floating point arithmetic when completing the division operation, correcting the truncation error. Both test cases validate the corrected program. Two test cases are probably sufficient for a short, simple program like this one, but larger, more complex programs require more. The order of operations remains the same as the previous version, but the result is quite different.
5.0/9.0 is evaluated first, but the compiler treats 5.0 and 9.0 as type double, so the division operation evaluates to 0.5555... . Changing one operand is sufficient, so either 5.0/9 or 5/9.0 solves the truncation error. When one operand is a double and the other an int, the compiler promotes the int to complete the operation.
The result of 5.0/9.0 multiplied by (f-32) produces the correct result.
The final, correct result is stored in c.
Program
Output
#include <iostream>
using namespace std;
int main()
{
double f;
cout << "Enter a temperature in Fahrenheit: ";
cin >> f;
double c = (f - 32) * 5 / 9; // correct
cout << "The temperature in Celsius = " << c << endl;
return 0;
}
Enter a temperature in Fahrenheit: 32
The temperature in Celsius = 0
Enter a temperature in Fahrenheit: 212
The temperature in Celsius = 100
Correcting a truncation error with the order of operations. We can correct the truncation error by changing the sub-expression evaluation order. In this version, we retain the expression 5/9 (with both operands as type int) but move it to the end of the statement. Associativity and automatic type promotion are the primary reasons this version works.
All operators on the right-hand side of the assignment operator are left-associative. The program evaluates the expression left to right, beginning with the sub-expression (f-32). The two values are not the same type (f is a double while 32 is an int), so the compiler converts 32 to double before it performs the subtraction operation. The result of (f-32) is a double, and the operation completes without error.
Multiplication and division have the same precedence, so the result of (f - 32) is multiplied by 5. The two operands are different types, and the compiler promotes 5 to a double.
Finally, (f - 32) * 5, which is a double-valued expression, is divided by 9. The compiler promotes 9 to a double, allowing the division operation to complete.
The final, correct result is stored in c.
There are other ways to reorder the operations. This version also works: c = 5 * (f - 32) / 9;. Can you explain why?
Program
Output
#include <iostream>
using namespace std;
int main()
{
double f;
cout << "Enter a temperature in Fahrenheit: ";
cin >> f;
double c = (double)5 / 9 * (f - 32); // correct
//double c = double(5) / 9 * (f - 32); // correct
cout << "The temperature in Celsius = " << c << endl;
return 0;
}
Enter a temperature in Fahrenheit: 32
The temperature in Celsius = 0
Enter a temperature in Fahrenheit: 212
The temperature in Celsius = 100
Correcting a truncation error with casting. When at least one operand is a constant, programmers generally prefer solving the truncation error by making the constant a double (by adding a decimal point) rather than casting. However, this solution is impossible if both operands are variables, and this version demonstrates how to solve the truncation problem with casting.
The program evaluates the expression left to right, first performing the casting operation (highlighted). The cast makes the 5 a double, so the program promotes the 9 to a double before evaluating 5/9. Casting 9 to a double will also work: 5/(double)9 or 5/double(9). The program may cast both constants: (double)5/(double)9. However, neither of the following work (can you explain why?):
double(5/9)
(double)(5/9)
Following the cast and the subtraction, the division and multiplication run without error.