1. Getting to know <cmath>
An introductory experience with math functions usually looks like this: a student writes a formula, the compiler complains about sqrt, the student adds something at random, the compiler stops complaining, and everyone is happy… until the first strange result. The problem is that mathematics in programming is not only “school formulas”, but careful work with calculation limits, types and special values.
<cmath> is part of the C++ standard library. It contains tested implementations of math functions. This matters for two reasons. First, you get correct and optimized algorithms, not a “homemade root” that “seems to work until it breaks”. Second, <cmath> sets a common style: if another developer sees std::sqrt(x), they understand what is happening. If they see mySuperRoot(x), they only understand that someone liked adventures.
In this lecture we will think pragmatically: a function is a tool. A tool has an instruction. If you try to hammer nails with a microscope, it's not the microscope's fault.
Include <cmath> and get used to std::
Before you start calculating, you need to include the header correctly and call functions in the way customary for C++. A small confusion often happens here: someone writes sqrt(x) without std::, sometimes it compiles “by accident”, sometimes not, and sometimes it invokes the wrong overload.
The basic rule is simple: for math functions use #include <cmath> and write std::sqrt, std::abs, std::pow. This is not “pedantry for its own sake”: it makes code portable and predictable.
Mini template worth remembering:
#include <iostream>
#include <cmath>
int main() {
double x = 9.0;
std::cout << std::sqrt(x) << '\n'; // 3
}
If you see the error "sqrt was not declared in this scope" — almost always it's either a missing #include <cmath>, or an attempt to call sqrt without std:: in an environment where an accidental “pickup” did not occur.
Map of the three functions
Before diving into details, it's useful to create a “map of the area”: what these functions are, what they are used for and where their limitations are. This reduces the chance of writing code like “I pressed the button and it did things by itself”.
| Function | Header | Idea in plain words | What to remember |
|---|---|---|---|
|
|
Makes a number non-negative (absolute value) | Great for eps-comparisons |
|
|
Square root | Normal root expected for x >= 0 |
|
|
Raises a to the power b | Result is double, inaccuracies possible |
With this map we'll proceed: first abs (the safest), then sqrt (it has a domain), then pow (the most “flexible”, and therefore the trickiest).
2. std::abs: absolute value as the best friend of precision and conditions
Absolute value seems like a childish topic: “remove the minus”. But in floating‑point computations std::abs becomes a workhorse, because “error” is almost always a difference that is convenient to take in absolute value. When you check whether numbers are close, you almost certainly write abs(a - b) < eps.
Let's start with a simple example: the absolute difference of two numbers. This is useful even without any particular eps, for example when you want to understand “how far off we were”.
#include <iostream>
#include <cmath>
int main() {
double planned = 10.0;
double actual = 9.6;
double diff = std::abs(planned - actual);
std::cout << diff << '\n'; // 0.4
}
Now — a practical comparison pattern using eps. We've discussed the idea of “almost equal” before, here we just make it shorter and cleaner.
#include <iostream>
#include <cmath>
int main() {
double a = 0.1 + 0.2;
double b = 0.3;
const double eps = 1e-12;
bool equal = (std::abs(a - b) < eps);
std::cout << equal << '\n'; // 1 (true)
}
It's important to capture the idea: std::abs here is not a “mathematical toy”, but a way to stabilize the logic of an if. You no longer depend on how “nicely” the number was represented in memory — you compare the meaning, not the binary snapshot.
3. std::sqrt: root and the function domain
When you first use sqrt, usually everything is fine: sqrt(9) gives 3, and you're in a good mood. But in real code sqrt more often appears not with perfectly nice numbers, but with expressions: discriminant, distance, physics formulas, calculation of standard deviation. And here the key concept appears: domain of the function.
The domain is the set of input values for which the operation has the expected meaning in your model. In “usual” real arithmetic the square root is expected for x >= 0. If you call std::sqrt(-1.0), the program often does not crash, but you will most likely get NaN, and then NaN can silently spread through your calculations.
The simplest and most honest way is to check the input before calling:
#include <iostream>
#include <cmath>
int main() {
double x;
std::cin >> x;
if (x < 0.0) {
std::cout << "sqrt domain error\n";
} else {
std::cout << std::sqrt(x) << '\n';
}
}
Here we do an important thing: we don't “treat the symptoms” after getting NaN, we prevent the problem before the calculation.
Safe usage pattern for sqrt
Sometimes it's useful to present the logic not only as code, but as a small flowchart — it's easier for a beginner's mind.
flowchart TD
A[Have a value x] --> B{ x >= 0 ? }
B -- no --> C[Report domain error]
B -- yes --> D["Compute y = sqrt(x)"]
D --> E[Use y further]
In real code “report domain error” can mean anything: print text, set a default value, terminate the program. For now we make the most obvious variant — print a message.
Nuance: slightly negative due to rounding
A typical situation: mathematically you should get 0, but due to rounding you got -1e-16. Mathematically that's “almost zero”, but for sqrt it's already negative.
Here common sense and the same eps come into play. You can do a “soft” check: if the number is slightly below zero but its absolute value is less than eps, treat it as zero.
#include <iostream>
#include <cmath>
int main() {
double x = -1e-16;
const double eps = 1e-12;
if (x < 0.0 && std::abs(x) > eps) {
std::cout << "sqrt domain error\n";
} else {
if (x < 0.0) x = 0.0; // clamped to 0
std::cout << std::sqrt(x) << '\n'; // 0
}
}
This is not “cheating mathematics”, but a pragmatic engineering agreement: we acknowledge that computations are inexact, and we guard against microscopic artifacts.
Example: distance between two points
To avoid staying in abstraction, let's embed sqrt into a small practical scenario. Let the program compute the distance between points (x1, y1) and (x2, y2):
\[ dist = \sqrt{(x2-x1)^2 + (y2-y1)^2} \]
#include <iostream>
#include <cmath>
int main() {
double x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
double dx = x2 - x1;
double dy = y2 - y1;
double dist = std::sqrt(dx * dx + dy * dy);
std::cout << dist << '\n';
}
Note: for the square we used dx * dx, not pow(dx, 2). Why — we'll discuss in the next section about pow. For now just remember: “square of a number” is often written as multiplication; that's normal and readable.
4. std::pow: the power function that makes things pretty
std::pow(a, b) is a powerful function: it can raise to fractional powers, negative powers, powers like 0.5 (which is similar to a root), and in general it gives a feeling of a mathematical calculator.
But due to this versatility there are two practical caveats. First: the result is almost always double, even if you think “I squared it, it should be exact”. Second: pow can be heavier than simple operations, because a “universal power” is often more complex than “multiply twice”.
Common scenario: compound interest
A very real formula — growth of a deposit with compounding:
\[ S = P \cdot (1 + r)^{n} \]
where P is the principal, r is the rate per period (e.g., 0.05), n is the number of periods.
#include <iostream>
#include <cmath>
int main() {
double p = 1000.0;
double r = 0.05;
int n = 3;
double s = p * std::pow(1.0 + r, n);
std::cout << s << '\n'; // 1157.63 (approx)
}
This is a perfect example of where pow is truly “in its place”: the formula reads almost like in a textbook.
pow(x, 2) vs x * x
When the exponent is an integer and small, it's often simpler and faster to write multiplication. And, unexpectedly important for a beginner, it's sometimes clearer: dx * dx reads as “square of dx”, even if you don't know the word “exponent”.
Compare:
double a = dx * dx + dy * dy;
double b = std::pow(dx, 2.0) + std::pow(dy, 2.0);
In educational examples both variants are acceptable, but for our level it's useful to remember: pow — when the exponent is not just 2 or 3, or when the formula should read like a mathematical expression.
pow and domain
There are domain surprises with pow as well. For example, pow(negative, 0.5) — by meaning that's the square root of a negative number, and you again risk getting NaN. We won't dive into complex numbers here, but the practical rule is: if you use pow as a substitute for sqrt, the domain is roughly the same — the argument should be non-negative (in the usual real model).
#include <iostream>
#include <cmath>
int main() {
double x = -9.0;
double y = std::pow(x, 0.5);
std::cout << y << '\n'; // nan (often)
}
This is not a “compiler error”, it's just a signal: you asked for an operation that in the realm of real numbers yields “not a number”.
5. Building a mini‑application “MathLab”
Now let's create a small program skeleton that lets you choose a mode and perform a calculation. We won't turn this into a “huge project”, but we keep the idea of our application: the same main.cpp that you gradually extend with new features.
We start with a simple command-based branching. We consciously use only what you've already seen: strings, if/else, input/output and basic calculations.
#include <iostream>
#include <string>
#include <cmath>
int main() {
std::string mode;
std::cin >> mode;
if (mode == "dist") {
double x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
double dx = x2 - x1;
double dy = y2 - y1;
std::cout << std::sqrt(dx * dx + dy * dy) << '\n';
} else if (mode == "deposit") {
double p, r;
int n;
std::cin >> p >> r >> n;
std::cout << p * std::pow(1.0 + r, n) << '\n';
} else {
std::cout << "unknown mode\n";
}
}
Example usage (mentally, like in a Web‑IDE):
Input:
dist 0 0 3 4
Output:
// 5
Input:
deposit 1000 0.05 3
Output:
// 1157.63... (exact display depends on formatting, we'll cover that in the next lecture)
Note one thing: we included <cmath> once and use all three functions as parts of applied scenarios, not as “functions for the sake of functions”. This is an important style: mathematics in code should serve the task, not show off in a vacuum.
Add a check for sqrt
In the distance formula the expression under the root is a sum of squares, so it's mathematically non-negative. But caution is still useful, because you will copy this style to other places where the expression under the root may be “more interesting”.
#include <iostream>
#include <string>
#include <cmath>
int main() {
std::string mode;
std::cin >> mode;
if (mode == "dist") {
double x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
double dx = x2 - x1;
double dy = y2 - y1;
double value = dx * dx + dy * dy;
if (value < 0.0) {
std::cout << "sqrt domain error\n";
} else {
std::cout << std::sqrt(value) << '\n';
}
}
}
Yes, the check here seems almost “redundant”. But it's like a seat belt: most of the time you forget about it, but on a rare bad day it is very useful.
6. Common mistakes when working with <cmath> and these three functions
Mistake #1: forgetting #include <cmath> and trying to “fix” it with strange hacks.
When the compiler says that sqrt or pow is not declared, sometimes there's a desire to add a random header “just in case”. The correct cure is simple: math functions are in <cmath>. And it's better to write std::sqrt, std::pow, std::abs immediately, so you don't depend on accidental inclusions.
Mistake #2: calling sqrt without checking the domain, then being surprised that if behaves oddly.
If NaN enters calculations, comparisons start to behave unintuitively: x < 0 and x > 0 can both be false at the same time. So a good habit is to think “what can end up under the root?” and validate the input before sqrt, especially if the expression under the root comes from user data.
Mistake #3: believing that pow gives an “exact square” and it is safe to compare with ==.
Even if mathematically the result should be integer, the computation is in double, and rounding can occur. If you then compare the result with ==, you may get unexpected branches in your if. If a comparison is essential, use the eps-based approach: std::abs(a - b) < eps.
Mistake #4: using pow(x, 2) everywhere, including places where x*x is simpler.
This is not catastrophic, but it often makes code heavier and less transparent. pow is great for “real exponents” and formulas like compound interest, and for square/cube it's usually more readable and simpler to write multiplication. In exercises that isn't always critical, but the habit “don't complicate without a reason” will pay off later.
Mistake #5: confusing “calculation accuracy” with “pretty output”.
Often a student prints a number and sees 1157.63, thinks “perfect”, then uses == and is surprised. Printing is presentation, not a guarantee of storage accuracy. We'll cover formatting in the next lecture, but already now remember: first calculations, then output; output may “hide” small fractional tails.
GO TO FULL VERSION