Concepts / Error Handling in Mathematical Functions

Error Handling in Mathematical Functions

Mathematical functions such as sqrt() and pow() can fail, and C++ requires your code to check for those failures explicitly.

  • Programming

When Math Functions Fail

Some mathematical operations cannot produce a meaningful answer for every input. Taking the square root of a negative number is one example. Raising a very large number to a power is another, because the result can be too large to store as a double. C++ gives you a way to find out after the fact that one of these problems happened, but it does not stop your program on its own. Your code has to look for the problem and respond to it.

In this article you will follow a short piece of code that calls two mathematical functions and checks for one specific error condition after each call. The goal is not only to learn the rules but to practice watching the program move from line to line, so that you can see exactly when each check happens and what it reports.

Why errno Starts From a Clean Slate

Error state is kept in a value called errno. An error value from an earlier call can still be sitting in errno when you reach a later operation. If you skip the reset and check errno afterward, you may read that old error and conclude that the current operation failed, even when it did not.

The fix is to assign zero to errno before the operation you want to check. The source code does this with a comment that reads clear old error state, which describes its purpose exactly. After the reset, the next check reflects only the operation you are about to perform. Resetting errno to 0 before each operation means that a non-zero value afterward points to a problem that happened during that operation.

Matching Each Check to Its Function

The source uses two named constants, and each one identifies a different kind of failure. EDOM is checked after sqrt() to detect a domain error, meaning the input lies outside the values the function can accept. The example input sqrt(-1) is such a case. ERANGE is checked after pow() to detect a result too large to represent as a double. Raising the largest representable double to the power 2 is the example used in the source.

When you read a check, ask two questions: which function was called just before it, and which constant is being compared. A check against EDOM belongs after sqrt(). A check against ERANGE belongs after pow(). Using the wrong constant for a call means the check is looking for the wrong kind of problem.

Tracing the Error-Handling Sequence

Before you look at the trace, make a prediction. The sequence below calls sqrt() with a negative argument, then calls pow() with the largest representable double as its base and 2 as its exponent. Decide which of the two checks will find a problem and which message the program will print for each.

cpp
Notes on the code
  • errno = 0 clears old error state before the sqrt call
  • EDOM is compared after sqrt() because sqrt() reports domain errors
  • errno = 0 is repeated so the pow() check does not read the earlier result
  • ERANGE is compared after pow() because pow() reports results too large to store as a double
  • handle_domain_error and handle_range_error are placeholder names for whatever response your program needs
Worked example

Finding where the output departs from the prediction

You predict that both checks will report a problem for the sequence above. Trace it step by step and find the first point where the behavior does not match your prediction.

  1. 1
    Step 1: first reset

    errno is set to 0, so any earlier error is cleared before sqrt runs.

  2. 2
    Step 2: sqrt(-1.0)

    The argument is negative, which lies outside the domain of sqrt(). The check that follows is the EDOM check.

  3. 3
    Step 3: EDOM check

    If errno equals EDOM, the domain error handler runs. This is the first place a departure from the prediction can appear, so compare the reported result with what you expected here.

  4. 4
    Step 4: second reset

    errno is set to 0 again. If this reset were missing, the ERANGE check would read whatever the first call left behind rather than the result of pow().

  5. 5
    Step 5: pow and ERANGE check

    The largest representable double raised to 2 can exceed what a double can store. The ERANGE check after pow() is the one that should report it.

Result

The departure from the prediction is found at a specific reset or comparison. Check at each line whether errno has been reset, whether the call has happened, and whether the comparison uses the constant that matches that call.

Changing the First Input

This variant is an original example created for practice. Suppose the first call is changed from sqrt(-1) to sqrt(4). The argument now lies inside the domain of sqrt(), so the call succeeds and returns 2. Predict what happens to the EDOM check that follows it.

What do you think happens?

After sqrt(4) returns 2, what does the EDOM check report?

Reveal answer
Answer:

The EDOM check does not report a domain error, because errno was reset to 0 before the call and the call did not fail.

The reset means errno reflects only the sqrt() call. Since that call succeeded, errno is still 0 when the check runs, so the domain error branch is not taken.

Mistakes in Error Checks

  • Common mistake

    Checking errno without resetting it first

    What it looks like

    A check runs after a later math call, but errno still holds an error from an earlier call.

    Why it fails

    The check then reflects the old error rather than the current operation, so the program may believe the current call failed.

    Do this instead

    Assign errno = 0 immediately before the operation you want to check.

  • Common mistake

    Comparing against the wrong constant

    What it looks like

    Checking for ERANGE directly after sqrt(-1), or checking for EDOM after pow().

    Why it fails

    EDOM identifies a domain error and ERANGE identifies a result too large to represent as a double. Each check looks for a different kind of problem.

    Do this instead

    Compare against EDOM after sqrt() and against ERANGE after pow().

  • Common mistake

    Assuming C++ stops the program when a math function fails. The source states that C++ does not stop your program on its own, so your code must look for the problem and respond to it.

Key Takeaways

  • Some math operations, such as sqrt() on a negative number or pow() with a very large result, can fail, and C++ does not stop the program on its own; your code must check for these failures.
  • Assign errno = 0 before each operation you want to check so that the check reflects only that operation and not an earlier error.
  • After sqrt(), the check looks for EDOM, a domain error. After pow(), the check looks for ERANGE, a result too large to represent as a double.
  • When tracing error-handling code, locate the exact reset or comparison where the behavior departs from your prediction.

Related concepts

More concepts starting with E