Skip to content
C++
Library
since C++98
Basic

<cmath>

Standard C++ header providing mathematical functions for floating-point arithmetic, rounding, classification, and error handling.

<cmath>since C++98

The <cmath> header provides the C++ standard mathematical function library β€” a superset of C's <math.h> β€” placing all names in namespace std and adding overloads for float, double, and long double argument types.

Overview

<cmath> is C++'s primary header for floating-point mathematics. It wraps and extends the C <math.h> library, organising its contents into several functional groups:

  • Basic: abs, fabs, fmod, remainder, remquo, fma, fmax, fmin, fdim
  • Exponential / logarithmic: exp, exp2, expm1, log, log2, log10, log1p
  • Power / root: pow, sqrt, cbrt, hypot
  • Trigonometric: sin, cos, tan, asin, acos, atan, atan2
  • Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
  • Rounding: ceil, floor, trunc, round, lround, llround, nearbyint, rint, lrint, llrint
  • Floating-point manipulation: frexp, ldexp, modf, scalbn, scalbln, ilogb, logb, nextafter, nexttoward, copysign
  • Classification / comparison: fpclassify, isfinite, isinf, isnan, isnormal, signbit, isgreater, isless, isunordered

Prior to C++11 the header provided only double overloads; C++11 added float and long double overloads for every function, plus the classification macros were promoted to proper function templates. Several functions gained constexpr qualification in C++20 and C++23 (see individual notes below).

Relationship to <math.h>

Including <cmath> is preferred over <math.h> in C++ code. Both ultimately expose the same functions, but <cmath> guarantees placement in namespace std, eliminating naming collisions and enabling argument-dependent lookup. The <math.h> form additionally injects names into the global namespace β€” a behaviour that is implementation-defined for <cmath> and not to be relied upon.

Syntax

cpp
#include <cmath>

// All functions live in namespace std
double r = std::sqrt(2.0);
float  s = std::sqrtf(2.0f);   // C-style suffix form β€” still valid
float  t = std::sqrt(2.0f);    // C++11: preferred float overload

Since C++11 every function is overloaded for float, double, and long double. When an integer argument is passed the result type is double:

cpp
double x = std::sqrt(4);    // C++11: int promoted to double β†’ 2.0

Examples

Geometry: distance and angle

cpp
#include <cmath>
#include <numbers>  // C++20: std::numbers::pi
#include <print>    // C++23: std::println

struct Vec2 { double x, y; };

double magnitude(Vec2 v) {
    return std::hypot(v.x, v.y);  // avoids intermediate overflow
}

double angle_deg(Vec2 v) {
    double rad = std::atan2(v.y, v.x);
    return rad * (180.0 / std::numbers::pi);  // C++20
}

int main() {
    Vec2 v{3.0, 4.0};
    std::println("magnitude: {}", magnitude(v));   // 5.0
    std::println("angle: {}Β°", angle_deg(v));       // 53.13...
}

std::hypot computes √(x²+y²) without the overflow risk of writing std::sqrt(x*x + y*y). Since C++17, a three-argument form std::hypot(x, y, z) computes the 3-D Euclidean norm.

Floating-point classification

cpp
#include <cmath>
#include <limits>

bool safe_log(double x, double& result) {
    if (!std::isfinite(x) || x <= 0.0)
        return false;
    result = std::log(x);
    return true;
}

void inspect(double v) {
    switch (std::fpclassify(v)) {
        case FP_INFINITE:  /* handle inf  */ break;
        case FP_NAN:       /* handle NaN  */ break;
        case FP_SUBNORMAL: /* handle denormal */ break;
        case FP_ZERO:      /* handle zero */ break;
        case FP_NORMAL:    /* ordinary value  */ break;
    }
}

std::isnan, std::isinf, and std::isfinite (C++11 function templates) replace the isnan / isinf macros from C. Prefer the std:: versions; the global-namespace macros may not exist in all translation units.

Rounding modes

cpp
#include <cmath>

// C++11: all rounding variants exist
double v = 2.7;

std::floor(v);      // 2.0  β€” toward -∞
std::ceil(v);       // 3.0  β€” toward +∞
std::trunc(v);      // 2.0  β€” toward zero (C++11)
std::round(v);      // 3.0  β€” nearest, halfway away from zero
std::nearbyint(v);  // 3.0  β€” nearest, respects current rounding mode, no FE_INEXACT
std::rint(v);       // 3.0  β€” nearest, respects current rounding mode, raises FE_INEXACT

std::nearbyint and std::rint differ only in whether they raise the FE_INEXACT floating-point exception. If you need to suppress that exception (e.g., in hot inner loops where <cfenv> error tracking is active), prefer std::nearbyint.

Fused multiply-add

cpp
#include <cmath>

// C++11: std::fma computes (x * y) + z with a single rounding step
double dot(double ax, double bx, double ay, double by) {
    return std::fma(ax, bx, ay * by);
}

std::fma (C++11) is exact to within one ULP when the hardware provides a native FMA instruction. On platforms without native FMA it is emulated but still produces the correct mathematical result. Use it whenever intermediate rounding in x*y + z would be a problem β€” polynomial evaluation being the classic case.

Error handling

cpp
#include <cmath>
#include <cerrno>
#include <cstring>

double safe_sqrt(double x) {
    errno = 0;
    double r = std::sqrt(x);
    if (errno == EDOM)
        throw std::domain_error("sqrt of negative");
    return r;
}

Error reporting from <cmath> is controlled by math_errhandling (defined in <cmath>, not in namespace std). It holds a bitwise OR of:

ConstantMeaning
MATH_ERRNOErrors reported via errno
MATH_ERREXCEPTErrors raised as floating-point exceptions (<cfenv>)

At least one of the two bits is set by any conforming implementation. On most Linux/glibc targets both are set; on MSVC only MATH_ERRNO is. Do not assume a specific combination.

Best Practices

Prefer std::hypot over manual Pythagorean sums. Writing std::sqrt(x*x + y*y) overflows when x or y exceeds √DBL_MAX β‰ˆ 1.3Γ—10¹⁡⁴. std::hypot is required by the standard to avoid spurious overflow and underflow.

Use std::fma for compensated arithmetic. When building polynomial evaluators (Horner's method) or compensated summations, std::fma eliminates the intermediate rounding step and typically maps to a single hardware instruction on x86-64 (VFMADD), ARM (FMADD), and POWER (fmadd).

Classify before computing. Always guard std::log, std::sqrt, std::asin, and similar domain-restricted functions with std::isfinite and range checks on untrusted input. Passing a negative value to std::sqrt sets errno = EDOM and returns NaN β€” it does not throw.

Match argument and return types. Mixing float arguments with a double result variable triggers an implicit widening conversion that silently defeats the purpose of using float for performance. Assign to the same type: float r = std::sqrtf(x) or float r = std::sqrt(x) (C++11 overload).

Do not use using namespace std; to shorten math calls in headers. Importing the entire std namespace in a header drags in hundreds of names and is a well-known source of ODR violations.

Common Pitfalls

std::abs vs std::fabs for floating-point. std::abs with a double argument is declared in <cmath> since C++11. Before C++11, std::abs for floating-point was only guaranteed by including <cstdlib> (which provided the integer version) or <cmath>. With C++11 or later, prefer std::abs with <cmath> included. Including only <cstdlib> and calling std::abs(3.7) silently truncates to integer abs on older compilers.

NaN propagation is asymmetric for min/max. std::fmin(NaN, 1.0) returns 1.0 (NaN is treated as missing data per IEEE 754), whereas std::min(NaN, 1.0) has unspecified behaviour and frequently returns the wrong value. Use std::fmin/std::fmax when one operand may be NaN.

std::pow(base, 2) is not an optimisation. Many compilers do not special-case integer exponents in std::pow; the call is typically slower and less accurate than base * base. Write the multiplication explicitly or use std::fma(base, base, 0.0).

math_errhandling is not in namespace std. Unlike nearly everything else in <cmath>, math_errhandling, MATH_ERRNO, and MATH_ERREXCEPT are macros defined at global scope. Do not write std::math_errhandling.

Integer literals produce double. std::sqrt(2) is valid C++11 (integer argument overload resolves to double), but std::sqrt(2.f) selects the float overload. Ensure literal suffixes match the intended precision.

Fused Multiply-Add: std::fma

std::fma(x, y, z) (C++11) computes (x * y) + z as if with infinite intermediate precision, then rounds the final result once. Ordinary x * y + z incurs two rounding steps β€” one after the multiplication and one after the addition β€” so the two expressions can give different answers.

cpp
#include <cmath>
#include <cfloat>

// naive:   two roundings, up to 2 ULP error
double naive = x * y + z;

// fma:     one rounding, at most 0.5 ULP error (correctly rounded)
double exact = std::fma(x, y, z);

Hardware mapping

When the compiler can prove that FP_FAST_FMA (or FP_FAST_FMAF / FP_FAST_FMAL) is defined, std::fma maps to a single native instruction:

ArchitectureInstructionAvailability
x86-64 FMA3VFMADD132SD / VFMADD213SDIntel Haswell (2013) and later, AMD Piledriver (2012) and later
x86-64 FMA4VFMADDSDAMD Bulldozer (2011) only
ARM / AArch64FMADD / VFMAARMv8-A and Cortex-M4F with VFPv4
POWERfmaddAll modern POWER cores

When no native FMA is available, the implementation falls back to a software emulation that still produces the correctly rounded result but is significantly slower. Pass -mfma (GCC/Clang) or /arch:AVX2 (MSVC) to unlock hardware FMA on x86.

Check at compile time whether fast FMA is available:

cpp
#include <cmath>

#ifdef FP_FAST_FMA
    double r = std::fma(a, b, c);   // single instruction
#else
    double r = a * b + c;           // fast but two-rounded; or accept slower std::fma
#endif

When std::fma matters most

Horner's method (polynomial evaluation). Each step of Horner's scheme is a multiply-add, so using std::fma halves the number of rounding errors:

cpp
// Evaluate p(x) = c0 + c1*x + c2*x^2 + c3*x^3
// Horner form: ((c3*x + c2)*x + c1)*x + c0
double horner(double x, double c0, double c1, double c2, double c3) {
    double r = c3;
    r = std::fma(r, x, c2);
    r = std::fma(r, x, c1);
    r = std::fma(r, x, c0);
    return r;
}

Dot products. Accumulating a[i] * b[i] with std::fma keeps each product exact before adding it to the running sum:

cpp
double dot(const double* a, const double* b, std::size_t n) {
    double sum = 0.0;
    for (std::size_t i = 0; i < n; ++i)
        sum = std::fma(a[i], b[i], sum);
    return sum;
}

Kahan summation alternative. The classic Kahan compensated-sum algorithm requires two additions per element. With FMA, a single std::fma can replace the correction step and achieve similar accuracy at lower cost.

Exact difference of products. The expression a*b - c*d is notoriously ill-conditioned when the two products nearly cancel. Using two FMA calls gives the exact result:

cpp
// Exactly computes a*b - c*d without intermediate cancellation error
double diff_of_products(double a, double b, double c, double d) {
    double w  = c * d;
    double err = std::fma(-c, d, w);   // exact rounding error of c*d
    double r  = std::fma(a, b, -w);
    return r + err;
}

Overflow-Safe Norms: std::hypot

The problem with the naive formula

Computing sqrt(x*x + y*y) directly overflows when |x| or |y| exceeds roughly sqrt(DBL_MAX) β‰ˆ 1.34Γ—10¹⁡⁴, and underflows to zero when both are subnormal. std::hypot is required by the standard to produce a correctly rounded result without spurious overflow or underflow.

cpp
#include <cmath>
#include <limits>

double big = std::numeric_limits<double>::max() / 2.0;

// Naive overflows to inf even though the true answer is finite:
double bad = std::sqrt(big * big + big * big);   // +inf

// std::hypot handles it correctly:
double good = std::hypot(big, big);              // 1.27...e+307

Signatures

cpp
// Two-argument form (C++11)
double      std::hypot(double x, double y);
float       std::hypot(float x, float y);
long double std::hypot(long double x, long double y);

// Three-argument form (C++17): sqrt(x^2 + y^2 + z^2)
double      std::hypot(double x, double y, double z);
float       std::hypot(float x, float y, float z);
long double std::hypot(long double x, long double y, long double z);

Internal algorithm

A typical implementation scales all inputs by the maximum of |x| and |y| (or |z|), computes the norm, then scales back. This keeps the squared terms inside the representable range:

cpp
// Conceptual implementation (simplified)
// The standard library version handles all IEEE 754 edge cases.
double safe_hypot2(double x, double y) {
    x = std::fabs(x);
    y = std::fabs(y);
    if (x < y) std::swap(x, y);
    if (x == 0.0) return 0.0;
    double t = y / x;
    return x * std::sqrt(std::fma(t, t, 1.0));
}

std::fma in the inner sqrt call eliminates a rounding step and, combined with the scaling, keeps the result within 1 ULP of the true value on conforming implementations.

Practical use

cpp
#include <cmath>
#include <array>

// 3-D distance between two points (C++17)
struct Vec3 { double x, y, z; };

double distance(Vec3 a, Vec3 b) {
    return std::hypot(a.x - b.x, a.y - b.y, a.z - b.z);
}

// Magnitude of a complex number without <complex>
double complex_abs(double re, double im) {
    return std::hypot(re, im);
}

Error Handling in Depth

math_errhandling, MATH_ERRNO, and MATH_ERREXCEPT

Every <cmath> function must report errors through at least one of two channels, selected by the implementation-defined macro math_errhandling:

cpp
// math_errhandling is a macro, NOT in namespace std
// It holds a bitwise OR of:
//   MATH_ERRNO    (1) β€” errors set errno
//   MATH_ERREXCEPT (2) β€” errors raise IEEE 754 floating-point exceptions

if (math_errhandling & MATH_ERRNO) {
    // errors are reported via errno
}
if (math_errhandling & MATH_ERREXCEPT) {
    // errors raise FE_INVALID, FE_DIVBYZERO, or FE_OVERFLOW
}
PlatformTypical value
Linux / glibc`MATH_ERRNO
macOS / libm`MATH_ERRNO
MSVC / WindowsMATH_ERRNO only
Embedded (newlib)Varies; often MATH_ERRNO only

Never hard-code which channel to use. Check math_errhandling at runtime.

Checking floating-point exception flags with std::fetestexcept

When math_errhandling & MATH_ERREXCEPT is non-zero, you can check exception flags from <cfenv> instead of (or alongside) errno:

cpp
#include <cmath>
#include <cfenv>

#pragma STDC FENV_ACCESS ON    // required pragma: tell the compiler not to
                               // reorder or optimise away fenv accesses

double checked_log(double x) {
    std::feclearexcept(FE_ALL_EXCEPT);         // clear all flags first

    double result = std::log(x);

    if (std::fetestexcept(FE_INVALID)) {
        // x was NaN β€” result is NaN
    }
    if (std::fetestexcept(FE_DIVBYZERO)) {
        // x was exactly 0.0 β€” result is -inf (pole error)
    }
    if (std::fetestexcept(FE_OVERFLOW)) {
        // result too large for double
    }
    return result;
}

The five standard exception flags are:

FlagRaised when
FE_INVALIDOperation on NaN or invalid domain (sqrt(-1))
FE_DIVBYZEROExact infinity result from finite inputs (log(0))
FE_OVERFLOWResult magnitude exceeds DBL_MAX
FE_UNDERFLOWResult magnitude is a nonzero subnormal
FE_INEXACTResult is not exactly representable

The #pragma STDC FENV_ACCESS ON directive is mandatory to prevent the compiler from eliding or reordering floating-point operations that would otherwise make the flags unobservable.


NaN Propagation and IEEE 754 Classification

Quiet NaN propagation rules

Under IEEE 754, any arithmetic operation that receives a quiet NaN (qNaN) as an input produces a quiet NaN as its output. This "NaN poison" property means that errors propagate silently through long computations and can be detected with a single std::isnan check at the end:

cpp
#include <cmath>
#include <limits>

double x = std::numeric_limits<double>::quiet_NaN();

// All of these produce NaN β€” the "poison" propagates:
double a = x + 1.0;         // NaN
double b = std::sqrt(x);    // NaN
double c = std::fma(x, 2.0, 3.0); // NaN
double d = std::sin(x);     // NaN

// But comparison operators always return false with NaN:
bool eq = (x == x);  // false  β€” the classic NaN self-inequality test
bool ne = (x != x);  // true   β€” reliable NaN check without <cmath>

// Prefer the standard predicate:
bool is_bad = std::isnan(x);  // true

Important exceptions to NaN propagation:

  • std::fmax(NaN, 1.0) returns 1.0 (treats NaN as missing data, per IEEE 754-2008).
  • std::fmin(NaN, 1.0) returns 1.0 for the same reason.
  • std::isunordered(x, y) returns true if either argument is NaN; comparison predicates (std::isgreater, etc.) never raise FE_INVALID for NaN inputs.

Signaling NaN

A signaling NaN (sNaN) raises FE_INVALID immediately on any arithmetic use. They are rarely produced by computations but can be constructed via std::numeric_limits<T>::signaling_NaN(). Their primary use is initialising memory to a trap value during debugging: any accidental read of an uninitialised floating-point variable immediately raises an exception.

cpp
#include <limits>

float trap = std::numeric_limits<float>::signaling_NaN();
// Reading 'trap' in arithmetic raises FE_INVALID on platforms
// that support signaling NaN (most x86 and ARM systems do).

Full classification with std::fpclassify

cpp
#include <cmath>

void describe(double v) {
    switch (std::fpclassify(v)) {
        case FP_NAN:       // either quiet or signaling NaN
        case FP_INFINITE:  // +inf or -inf; use std::signbit to distinguish
        case FP_ZERO:      // +0.0 or -0.0; std::signbit(v) for signed zero
        case FP_SUBNORMAL: // |v| < DBL_MIN (gradual underflow)
        case FP_NORMAL:    // ordinary representable value
    }
}

The four predicates std::isnan, std::isinf, std::isfinite, and std::isnormal are typically faster than std::fpclassify because they compile to a single comparison or bit-test instruction on modern targets. Use std::fpclassify only when you need to distinguish subnormals or signed zeros.


C++17 Mathematical Special Functions

C++17 added 17 families of mathematical special functions directly to <cmath>. They were previously available only through third-party libraries (Boost.Math, GSL). All follow the naming convention of the underlying mathematical function; the f and l suffix forms select float and long double, respectively.

cpp
#include <cmath>  // special functions are in <cmath> from C++17

// All signatures follow the pattern:
//   double   func_name(args...);
//   float    func_namef(args...);
//   long double func_namel(args...);

Orthogonal polynomials

These appear throughout physics as solutions to differential equations arising from separation of variables in various coordinate systems.

cpp
// Laguerre polynomials L_n(x) β€” quantum harmonic oscillator,
// hydrogen atom radial wave functions
double std::laguerre(unsigned n, double x);

// Associated Laguerre polynomials L_n^m(x) β€” hydrogen atom
// wave functions with orbital angular momentum quantum number m
double std::assoc_laguerre(unsigned n, unsigned m, double x);

// Legendre polynomials P_l(x) β€” solutions on [-1, 1],
// gravitational/electrostatic multipole expansions, x in [-1,1]
double std::legendre(unsigned l, double x);

// Associated Legendre functions P_l^m(x) β€” building block
// for spherical harmonics
double std::assoc_legendre(unsigned l, unsigned m, double x);

// Hermite polynomials H_n(x) β€” quantum harmonic oscillator
// energy eigenstates, probabilists' form
double std::hermite(unsigned n, double x);

// Spherical harmonics angular part Y_l^m(theta, phi=0):
// sph_legendre gives the theta-dependent factor
double std::sph_legendre(unsigned l, unsigned m, double theta);

Bessel functions

Bessel functions are solutions to Bessel's differential equation. They model wave propagation in cylindrical and spherical coordinates, heat conduction in cylinders, and signal processing filter design.

cpp
// Bessel functions of the first kind J_nu(x):
// bounded at the origin; oscillatory for large x
double std::cyl_bessel_j(double nu, double x);

// Neumann functions (Bessel functions of the second kind) Y_nu(x):
// diverge at the origin; oscillatory for large x
double std::cyl_neumann(double nu, double x);

// Modified Bessel functions of the first kind I_nu(x):
// exponentially growing; arise in probability (Rice distribution)
double std::cyl_bessel_i(double nu, double x);

// Modified Bessel functions of the second kind K_nu(x):
// exponentially decaying; arise in heat transfer, elasticity
double std::cyl_bessel_k(double nu, double x);

// Spherical Bessel functions of the first kind j_n(x):
// appear in quantum scattering, acoustic resonators
double std::sph_bessel(unsigned n, double x);

// Spherical Neumann functions n_n(x)
double std::sph_neumann(unsigned n, double x);

Elliptic integrals

Elliptic integrals arise in computing arc lengths of ellipses, pendulum periods for large amplitudes, and inductance of coils.

cpp
// Complete elliptic integral of the first kind K(k):
// period of a nonlinear pendulum, k is the modulus in [0,1)
double std::comp_ellint_1(double k);

// Complete elliptic integral of the second kind E(k):
// arc length of an ellipse with eccentricity k
double std::comp_ellint_2(double k);

// Complete elliptic integral of the third kind Pi(nu, k)
double std::comp_ellint_3(double k, double nu);

// Incomplete forms with upper limit phi:
double std::ellint_1(double k, double phi);
double std::ellint_2(double k, double phi);
double std::ellint_3(double k, double nu, double phi);

Other special functions

cpp
// Beta function B(x, y) = Gamma(x)*Gamma(y)/Gamma(x+y):
// normalisation constant for the beta distribution,
// combinatorics, Bayesian statistics
double std::beta(double x, double y);

// Exponential integral Ei(x) = -P.V. integral from -x to inf of e^{-t}/t dt:
// radiative transfer, plasma physics
double std::expint(double x);

// Riemann zeta function zeta(s):
// analytic number theory, quantum field theory, statistical mechanics
double std::riemann_zeta(double s);

Quick example β€” hydrogen s-orbital probability density

cpp
#include <cmath>
#include <numbers>  // C++20

// Radial probability density |R_10(r)|^2 for hydrogen 1s orbital
// using the associated Laguerre polynomial (n=1, l=0, so L_0^1)
double hydrogen_1s_radial_density(double r_over_a0) {
    double rho = 2.0 * r_over_a0;          // dimensionless radius
    double L   = std::assoc_laguerre(0, 1, rho);  // L_0^1(rho) = 1
    double R   = 2.0 * std::exp(-r_over_a0) * L;
    return R * R;  // |R|^2 is the probability density
}

C++20 Utilities: std::lerp and std::midpoint

std::lerp β€” linear interpolation

std::lerp(a, b, t) (defined in <cmath> since C++20) returns a + t*(b-a) with the following guarantees that the naive formula cannot provide:

  • Exactness at boundaries: std::lerp(a, b, 0.0) == a and std::lerp(a, b, 1.0) == b are guaranteed (no rounding surprise at the endpoints).
  • Monotonicity: if t1 <= t2 then std::lerp(a, b, t1) <= std::lerp(a, b, t2) when a <= b.
  • Consistency: std::lerp(a, b, t) for t outside [0, 1] gives a well-defined extrapolated value without undefined behaviour.
cpp
#include <cmath>   // C++20: std::lerp

float blend_colour(float from, float to, float t) {
    return std::lerp(from, to, t);  // guaranteed exact at t=0 and t=1
}

// Why the naive formula breaks:
float a = 0.1f, b = 0.2f;
float t = 1.0f;

float naive = a + t * (b - a);   // may not equal b exactly due to
                                  // floating-point subtraction error
float safe  = std::lerp(a, b, t); // == b exactly

The naive formula a + t*(b-a) fails at t=1 because b - a is not exactly representable in binary floating-point when a and b differ in magnitude. The standard library implementation uses a branch to return b directly when t == 1.0, and switches between two algebraic forms for the general case to preserve monotonicity.

std::midpoint β€” overflow-safe average

std::midpoint(a, b) (defined in <numeric> since C++20) computes the exact midpoint of two values without the intermediate overflow that afflicts (a + b) / 2:

cpp
#include <numeric>  // C++20: std::midpoint

// Classic bug in binary search:
int mid_bad  = (lo + hi) / 2;     // overflows for large lo and hi
int mid_good = std::midpoint(lo, hi);  // never overflows

// Works for floating-point too:
double fmid = std::midpoint(1e308, -1e308);  // 0.0, not NaN

For integers, std::midpoint always rounds toward the first argument, making it deterministic. For pointers, both arguments must point into the same array; the result is the pointer to the middle element. For floating-point, std::midpoint(a, b) is guaranteed to be at most 1 ULP from the true mathematical midpoint (a + b) / 2.

cpp
// Binary search using std::midpoint (C++20):
#include <algorithm>
#include <numeric>
#include <span>

int binary_search_safe(std::span<const int> arr, int target) {
    int lo = 0, hi = static_cast<int>(arr.size()) - 1;
    while (lo <= hi) {
        int mid = std::midpoint(lo, hi);
        if (arr[mid] == target)  return mid;
        if (arr[mid] < target)   lo = mid + 1;
        else                     hi = mid - 1;
    }
    return -1;
}

See Also

  • <numbers> β€” C++20 mathematical constants (std::numbers::pi, std::numbers::e, …)
  • <cfenv> β€” floating-point environment control (rounding mode, exception flags)
  • <complex> β€” complex-number overloads of most <cmath> functions
  • <numeric> β€” std::gcd, std::lcm, std::midpoint, std::lerp (C++17/20)
  • <valarray> β€” element-wise application of math functions to arrays