Why this matters
Division by zero happens when a count is zero. An average of no test scores divides by the number of scores, which is zero. A rate per day in a period with no days divides by zero. Programs that read data from users or files meet empty input all the time, so this case is common.
In C++ the result depends on the type of the numbers. Whole numbers and decimal numbers behave differently:
- Dividing a whole number by zero stops the program.
- Dividing a decimal number by zero gives a special value, either infinity or NaN, and the program keeps running.
The second case is harder to notice, because nothing stops and the wrong value can spread through later calculations. This lesson shows both cases, then shows the usual fix: check the divisor before you divide.
Whole numbers and decimal numbers are the types from earlier in this module. This lesson only needs the idea that int holds whole numbers and double holds decimal numbers.
Whole numbers: the program stops
Save this program as divide.cpp:
#include <iostream>
int main() {
int total = 10;
int people = 0;
std::cout << "before the division\n";
std::cout << total / people << "\n";
std::cout << "after the division\n";
}Compile and run it:
g++ -std=c++20 -Wall -o divide divide.cpp
./divide
echo $?The output is empty when the output goes to a pipe or a file. The program does not print before the division, does not print the result, and does not print after the division. In a terminal, the first line may still appear, as explained below. The echo $? command prints the exit code of the last program, which is the number the program hands back to the shell when it ends:
136An exit code of 136 means the program was stopped by a signal. A signal is a message the operating system sends to a running program to tell it that something went wrong. Here the signal is number 8, called SIGFPE, short for floating-point exception. The name is misleading, because the same signal is used for whole-number division too. On a Linux system with the x86-64 processor (the common Intel and AMD chip design) used here, the processor itself refuses the division and the operating system ends the program.
Why the first line did not print
The missing before the division line is a surprise, because that line ran before the crash. The reason is the output buffer. A buffer is a small block of memory that holds output until there is enough to send at once. std::cout keeps the text in a buffer and sends it later, not at every <<. The "\n" character moves to a new line, but it does not always send the buffer.
When the program is stopped by the signal, it does not get the chance to send the buffer. The text is lost when the output goes to a pipe or a file. A terminal sends each line when it ends, so the first line can still appear there.
std::endl is a different newline. It moves to a new line and also sends the buffer right away. Save this version as divide_flush.cpp:
#include <iostream>
int main() {
int total = 10;
int people = 0;
std::cout << "before the division" << std::endl;
std::cout << total / people << std::endl;
std::cout << "after the division" << std::endl;
}Output:
before the divisionNow the first line appears, because std::endl sent it before the crash. The division still stops the program, and after the division never prints. When you debug a crash, use std::endl on the lines near the suspected problem, or look at what the program printed before the signal.
Remainder by zero also stops the program
The remainder operator % divides too, and it has the same problem. Save this as remainder.cpp:
#include <iostream>
int main() {
int n = 17;
int d = 0;
std::cout << "remainder: " << n % d << "\n";
}Running it gives no output and exit code 136, the same as the first example. The remainder of a division by zero has no meaningful value, so the program is stopped the same way.
The compiler can see some cases
If the zero is written directly in the code, the compiler notices it. Save this as constant.cpp:
#include <iostream>
int main() {
int a = 10 / 0;
std::cout << a << "\n";
}Compile it:
g++ -std=c++20 -Wall -o constant constant.cppThe compiler warns, and still builds the program:
constant.cpp: In function 'int main()':
constant.cpp:4:16: warning: division by zero [-Wdiv-by-zero]
4 | int a = 10 / 0;
| ~~~^~~Running it gives exit code 136 again, with no output. The warning is useful, but the compiler only sees the zero when it is written in the code. Most zeros come from data the program reads while it runs, so the compiler cannot warn about them. The check has to be in the program.
The C++ standard does not define what happens when a whole number is divided by zero. The language calls this undefined behaviour. The compiler is allowed to produce any result, so a program that divides by zero is wrong even when the result looks harmless. On the system used for this lesson, the result is a stopped program.
Decimal numbers: infinity and NaN
A double holds a decimal number, such as 2.5 or -0.75. The type is stored in a form that can hold very large and very small values, and it has special values for results that cannot be a normal number.
Save this program as float.cpp:
#include <cmath>
#include <iostream>
int main() {
double one = 1.0;
double zero = 0.0;
std::cout << one / zero << "\n";
std::cout << -one / zero << "\n";
std::cout << zero / zero << "\n";
double n = zero / zero;
std::cout << std::boolalpha;
std::cout << "n == n is " << (n == n) << "\n";
std::cout << "isnan(n) is " << std::isnan(n) << "\n";
std::cout << "isinf(one / zero) is " << std::isinf(one / zero) << "\n";
}Compile and run it:
g++ -std=c++20 -Wall -o float float.cpp
./floatOutput:
inf
-inf
-nan
n == n is false
isnan(n) is true
isinf(one / zero) is trueThe program does not stop. Each line reports what happened:
1.0 / 0.0givesinf, short for infinity. It is larger than any number the type can hold.-1.0 / 0.0gives-inf, a negative infinity.0.0 / 0.0gives NaN, which stands for "not a number". The output here shows-nan. The minus sign is part of how this processor stores the value. Other systems may printnanwithout the sign. Either way the value means the same thing: the result of the division has no number value.
NaN is not equal to itself
The line n == n is false is the surprise. A NaN is not equal to anything, including itself. So the way to test for NaN is not ==. Use std::isnan, a function from the <cmath> header that returns true when a value is NaN. Use std::isinf in the same way to test for infinity.
This matters for checks that look reasonable. Suppose a program writes if (avg == 0.0) to catch an empty average. An empty average produces NaN, and NaN is not equal to 0.0, so the check does nothing. The program then carries NaN into the next line of the calculation.
Infinity and NaN spread
Once a program has an infinity or a NaN, arithmetic with it usually gives another special value. Save this as spread.cpp:
#include <cmath>
#include <iostream>
int main() {
double inf = 1.0 / 0.0;
std::cout << inf + 1.0 << "\n";
std::cout << inf - inf << "\n";
std::cout << std::isnan(inf - inf) << "\n";
std::cout << (0.0 / 0.0 == 0.0 / 0.0) << "\n";
}Output:
inf
-nan
1
0Adding 1.0 to infinity is still infinity. Subtracting infinity from infinity has no clear answer, so the result is NaN. The line (0.0 / 0.0 == 0.0 / 0.0) prints 0 here, because this program does not set std::boolalpha, so the false is printed as 0. A NaN compared with a NaN is false.
Because the bad value keeps going, the program may print a long list of inf or nan values, far from the division that caused them. The check belongs at the division, before the bad value exists.
Guard the divisor
A guard is a check that runs before a risky step. For division, the guard tests the divisor, which is the number on the bottom of the fraction. If it is zero, the program takes a different path.
Here is a common case: the average of a list of scores. The average is the sum divided by the count, so the count must not be zero. Save this as average.cpp:
#include <iostream>
#include <stdexcept>
#include <vector>
double average(const std::vector<int>& values) {
if (values.empty()) {
throw std::invalid_argument("average of an empty list");
}
int sum = 0;
for (int v : values) {
sum += v;
}
return static_cast<double>(sum) / static_cast<double>(values.size());
}
int main() {
std::vector<int> scores = {70, 80, 90};
double first = average(scores);
std::cout << "average: " << first << "\n";
std::vector<int> none;
try {
double second = average(none);
std::cout << "average: " << second << "\n";
} catch (const std::invalid_argument& e) {
std::cout << "error: " << e.what() << "\n";
}
}Output:
average: 80
error: average of an empty listSome terms here:
std::vector<int>is a list of whole numbers.values.empty()is true when the list has no items, andvalues.size()gives the number of items.throwstops the function and sends an error to whoever called it.std::invalid_argumentis a standard error type for a bad input.tryandcatchrun the risky call and handle the error. Thecatchblock runs only whenaveragethrew an error.static_cast<double>converts a value to a decimal type before the division. Without it,sum / values.size()would divide two whole numbers and drop the decimal part.
The guard is in average, so every caller gets the check. The caller decides what to do with the error. This is a choice with a trade-off:
- Returning 0 instead of throwing keeps the program running, but a 0 average looks like a real score. Nobody sees that the list was empty.
- Throwing an error forces the caller to handle the case, but it adds code around every call.
- Returning NaN is also possible, and the program keeps running. The bad value then spreads, as shown above, unless every later step checks for it.
The choice depends on the program. In this lesson the caller can show a message, so throwing fits.
A second example: a percentage
A quiz app shows the percentage of correct answers. A student may have a quiz with zero questions. Save this as percent.cpp:
#include <iostream>
int percent(int correct, int total) {
if (total == 0) {
return 0;
}
return correct * 100 / total;
}
int main() {
std::cout << percent(7, 10) << "\n";
std::cout << percent(3, 0) << "\n";
}Output:
70
0The guard returns 0 for an empty quiz. This is a plain choice: the function returns a value for every input, and the caller does not need a try block. The cost is that 0 percent and "no questions" look the same on the screen. If the difference matters, return a different signal, such as the error used in average.
The guard uses total == 0 and not a decimal comparison, because total is a whole number. For whole numbers, == is exact.
A third example: a divisor from user input
The first two examples used a zero that was written in the program. In real programs the zero usually comes from the user or from a file, so the compiler cannot see it. Here the program reads both numbers with std::cin, which is the standard input stream, and checks the count before it divides. Save this as share.cpp:
#include <iostream>
int main() {
int total = 0;
int people = 0;
std::cout << "Total amount: ";
std::cin >> total;
std::cout << "Number of people: ";
std::cin >> people;
if (people <= 0) {
std::cout << "Please enter at least one person.\n";
return 1;
}
double share = static_cast<double>(total) / people;
std::cout << "Each share: " << share << "\n";
}Compile it and feed it two numbers. Each run below sends the input from a pipe, so the typed numbers do not appear in the output. When you type at a keyboard, the terminal shows your typing after each prompt.
g++ -std=c++20 -Wall -o share share.cpp
printf '10\n4\n' | ./shareOutput:
Total amount: Number of people: Each share: 2.5With 0 as the count:
printf '10\n0\n' | ./shareOutput:
Total amount: Number of people: Please enter at least one person.The program ends with exit code 1 in this case, which the shell can check. The return 1; in main sets that exit code. Zero means success, and any other value tells the caller that something went wrong.
The check uses <= instead of ==. A negative count is also wrong for a number of people, and <= catches it along with zero. The division only runs when the count is at least 1.
Dividing 10 by 3 gives this output:
Total amount: Number of people: Each share: 3.33333The default output shows six significant digits, so the printed value is rounded. The stored double keeps more digits than are shown.
Common mistakes
Checking after the division. A line such as int avg = total / count; if (count == 0) ... has already divided by zero when the check runs. The guard must come before the division.
Testing a decimal result for zero. if (avg == 0.0) does not catch a NaN, because NaN is not equal to anything. Use std::isnan or std::isinf on the value that might be bad.
Expecting output before a crash. If the program prints a line with "\n" and then stops with exit code 136, the line may be missing. Use std::endl while debugging, or move the risky division to a function and print the inputs before the call.
Dividing whole numbers when a decimal result is wanted. 7 / 2 gives 3 in C++, because both values are whole numbers. Convert one side with static_cast<double> before dividing, as average does.
Ignoring the exit code. A program that stops with a signal exits with code 136 on this system. A shell script that does not check the exit code can carry on as if nothing went wrong.
Practice
Exercise 1. What does this program print, and what is its exit code?
#include <iostream>
int main() {
int a = 7;
int b = a - 7;
std::cout << "result: " << a / b << "\n";
}Show the answer
It prints nothing, and the exit code is 136. The value of b is 0, so a / b divides by zero. The text result: was written to the buffer but not sent before the program stopped.
Exercise 2. What does this program print?
#include <iostream>
int main() {
double x = 5.0 / 0.0;
std::cout << x << "\n";
}Show the answer
It prints inf. The number 5.0 is positive and the divisor is positive zero, so the result is positive infinity. The program does not stop.
Exercise 3. What does this program print? Explain the answer in one sentence.
#include <iostream>
int main() {
std::cout << (0.0 / 0.0 == 0.0 / 0.0) << "\n";
}Show the answer
It prints 0. Both sides are NaN, and NaN is not equal to anything, so the comparison is false. The output is 0 because std::boolalpha is not set.
Exercise 4. Write a function int percent(int correct, int total) that returns the percentage of correct answers as a whole number. It must return 0 when total is zero. Check it with (7, 10) and (3, 0).
Show the answer
#include <iostream>
int percent(int correct, int total) {
if (total == 0) {
return 0;
}
return correct * 100 / total;
}
int main() {
std::cout << percent(7, 10) << "\n";
std::cout << percent(3, 0) << "\n";
}Output:
70
0The guard runs before the division. The whole-number division correct * 100 / total drops any fraction, so 7 out of 10 gives 70 exactly here. For other values the result is rounded down.
Exercise 5. A program computes double ratio = total / count; where total and count are both int. Describe two changes that give a decimal result, and say which one the guard should come before.
Show the answer
Assigning the result to a double does not help, because the division runs first on two int values and gives 3. Two changes fix it. Convert one side before dividing, for example static_cast<double>(total) / count, or multiply by 1.0 first, as in total * 1.0 / count. Both divisions use decimal arithmetic, so 7 / 2 gives 3.5 rather than 3. The guard, if (count == 0), must come before either version of the division, because dividing by zero stops the program for whole numbers and gives infinity for decimals.
Summary
- Dividing a whole number by zero, or taking its remainder by zero, stops the program with exit code 136.
std::endlsends buffered output before a crash. Plain"\n"may leave the text in the buffer.- Dividing a decimal number by zero gives
inf,-infor NaN, and the program keeps running. - NaN is not equal to itself, so test for it with
std::isnan, not==. - Check the divisor before the division. Choose between returning a value, throwing an error or returning NaN based on what the caller needs to know.