C++ provides standard mathematical functions from the C library in the header file <cmath>, such as std::sqrt, std::fabs, etc. Starting from C++11, several new mathematical functions have been introduced into the mathematical function library. This article will review these newly added mathematical functions.
1 Floating Point Rounding Calculations
1.1 std::ceilf, std::ceill, std::floorf, std::floorl
Why were these four functions introduced when C++ already has std::ceil and std::floor? There are two reasons: one is to avoid unintentional precision loss. The std::ceil function supports float, double, and long double, but when used, if various numeric types are mixed (float, double), the compiler silently chooses the appropriate overload for calculation without giving a warning about potential precision loss. This can easily lead to a loss of precision in the entire calculation due to a low-precision intermediate parameter type. For example, if a float type parameter is mixed into a calculation involving a bunch of double type data. The other reason is to align with the C99 standard library, which introduced these similarly named mathematical functions for compatibility. C++ can never completely let go of C.
1.2 std::trunc, std::truncf, std::truncl
std::trunc is a mathematical function newly introduced in C++11. Its purpose is to return the closest integer value that is not greater than the absolute value of a given number num. The std::trunc function itself provides overloads for float, double, and long double, treating integer types as double overloads. Clearly, std::truncf and std::truncl are for compatibility with the C standard library.
1.3 std::round, std::lround, std::llround
The std::round series of functions were also introduced in C++11. The std::round function returns the closest floating-point integer value to a given number num, while std::lround and std::llround serve a similar purpose, returning the closest integer value in integer format (representing long and long long respectively). The rounding method for these three functions is fixed to round half away from zero, regardless of the current floating-point environment’s rounding mode. For more information on the floating-point environment’s rounding mode, refer to the article “Floating Point Environment in C++”. All three functions have versions with suffixes l and f for compatibility with the C standard library.
Although these three functions are not affected by the current floating-point environment’s rounding mode, they can affect floating-point exception flags. The std::round function does not set the corresponding floating-point exception flags when encountering non-normal floating-point numbers, such as infinity or NAN; it simply returns them unchanged. However, when rounding non-integer finite values, if the result exceeds the expressible range of integer values, it may trigger the setting of the FE_INEXACT exception. The FE_INEXACT floating-point exception flag indicates that there was a loss of precision during rounding. For example, the following code:
std::fenv_t env;
std::fegetenv(&env); // Save the current floating-point environment
std::feclearexcept(FE_ALL_EXCEPT); // Clear all exception flags
double ro = std::round(std::numeric_limits<double>::max() / 2.0);
if (std::fetestexcept(FE_INEXACT)) { std::cout << "Precision loss exception occurred!" << std::endl; }
std::fesetenv(&env); // Restore the original floating-point environment
Triggering the setting of the FE_INEXACT exception is not a standard requirement and depends on the compiler’s implementation. If your compiler supports it, the above code will print a message. This is because half of the maximum value of double far exceeds the range that an integer can express. This process does not directly cause overflow since the result is still a floating-point number, but an actual overflow will occur during the conversion from floating-point to integer.
std::lround and std::llround do not set the FE_INEXACT exception flag when encountering non-normal values, but they will set the corresponding floating-point exception flags based on the value of num. For example, when num is infinity or NAN, the return value of std::lround is 0, but it will set the FE_INVALID floating-point exception flag. The following code will print “Invalid value exception occurred”:
long lo = std::lround(std::numeric_limits<double>::infinity());
if (std::fetestexcept(FE_INVALID)) { std::cout << "Invalid value exception occurred!" << std::endl; }
std::cout << "lo: " << lo << std::endl; // Output 0
1.4 std::nearbyint, std::nearbyintf, std::nearbyintl
The std::nearbyint function rounds the floating-point parameter num according to the rounding mode set by the current floating-point environment, which distinguishes it from std::lround. The following code demonstrates the impact of floating-point environment settings on this function:
std::fesetround(FE_TOWARDZERO);
std::cout << "rz: " << std::nearbyint(2.882611) << std::endl; // 2
std::fesetround(FE_TONEAREST);
std::cout << "rn: " << std::nearbyint(2.882611) << std::endl; // 3
The FE_TOWARDZERO flag indicates rounding towards 0, which has a truncation effect, while the FE_TONEAREST flag indicates rounding towards the nearest integer. The output results are 2 and 3, respectively.
1.5 std::rint, std::lrint, std::llrint
For example, the following code will only print “to precision loss exception occurred!”:
double ro = std::nearbyint(2.882611);
if (std::fetestexcept(FE_INEXACT)) { std::cout << "ro precision loss exception occurred!" << std::endl; }
double to = std::rint(2.882611);
if (std::fetestexcept(FE_INEXACT)) { std::cout << "to precision loss exception occurred!" << std::endl; }
References
[1] https://cppreference.cn/w/cpp/numeric/fenv
[2] ISO/IEC 14882:2011
[3] LWG 3905 https://cplusplus.github.io/LWG/issue3905
Information, code, and previous articlesFloating Point Environment in C++C++’s maybe_unused specifierC++’s monadic interfaceC++20’s designated initializersC++’s covariant return typesC++’s size and ssize functionsC++’s latch and barrierC++’s semaphoreC++’s lock() and try_lock() functionsC++’s various locksC++’s various std::mutexC++’s spanC++’s copy elisionC++’s time library part eight: format and formattingC++20’s PIC++’s [[noreturn]] specifierHow C++ declares lambda as friendsC++’s cache line interfaceC++’s clamp functionC++’s three and five rulesC++’s strict aliasing rulesC++’s construct_at functionContent and notification summary