2.3.1. ftoc.cpp

Time: 00:11:05 | Download: Large, Large (CC), Small | Streaming, Streaming (CC)
Review 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.

\[c = {5 \over 9} (f - 32) \]
The formula to convert Fahrenheit to Celsius. When mathematicians write a formula or equation, a fraction represents a division operation, and multiplication is implied by writing two elements adjacent or next to each other. Programmers must translate these mathematical conventions into the corresponding C++ operators, forming executable statements. The variable f is the input temperature in Fahrenheit, and the variable c is the temperature expressed on the Celsius scale.

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:

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.
  1. 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.
  2. It doesn't matter what value (f-32) produces because when (f-32) is multiplied by 0 the result is always 0.
  3. 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.
  1. 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.
  2. The result of 5.0/9.0 multiplied by (f-32) produces the correct result.
  3. 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.
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  1. 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)
  2. Following the cast and the subtraction, the division and multiplication run without error.
  3. The result is stored in c.