std::nextaf...exttowardf,(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸŽ€ std::nextafter, std::nextafterf, std::nextafterl, std::nexttoward, std::nexttowardf β€” C++ Standard Libary (section 3) πŸ’…

okay bestie, part 1 of 2 is here. The page header is literally std::nextaf...exttowardf,(3) β€” yes, it’s giving truncated man-page chaos β€” and the manual name is C++ Standard Libary, section 3. (The source spells it β€œLibary”; we are not fixing it, we are respecting the iconique typo.) ✨


✨ NAME ✨

The NAME line prints this exact list twice, with a hyphen in the middle:

std::nextafter,std::nextafterf,std::nextafterl,std::nexttoward,std::nexttowardf, β€” std::nextafter,std::nextafterf,std::nextafterl,std::nexttoward,std::nexttowardf,

So the named crew is std::nextafter, std::nextafterf, std::nextafterl, std::nexttoward, and std::nexttowardf. The NAME line does not list std::nexttowardl, but the synopsis later absolutely brings std::nexttowardl into the group chat β€” keep that in your notes, iykyk. πŸ¦‹


🌸 Synopsis 🌸

All of this lives in #include <cmath>. Here’s the synopsis code, with the overload numbers and since-tags spelled out right after, because the original formatting is doing The Most.

Defined in header <cmath>

float       nextafter ( float from, float to );
double      nextafter ( double from, double to );
long double nextafter ( long double from, long double to );
constexpr /* floating-point-type */ nextafter ( /* floating-point-type */ from, /* floating-point-type */ to );

float       nextafterf( float from, float to );
long double nextafterl( long double from, long double to );

float       nexttoward ( float from, long double to );
double      nexttoward ( double from, long double to );
long double nexttoward ( long double from, long double to );
constexpr /* floating-point-type */ nexttoward ( /* floating-point-type */ from, long double to );

float       nexttowardf( float from, long double to );
long double nexttowardl( long double from, long double to );

Additional overloads
Defined in header <cmath>

template< class Arithmetic1, class Arithmetic2 >
/* common-floating-point-type */ nextafter( Arithmetic1 from, Arithmetic2 to );

template< class Integer >
double nexttoward( Integer from, long double to );

Now the annotations, fr fr:

  • (1) is floating around the nexttoward lines in the original synopsis. It’s there, we keep it.
  • The first float nextafter overload has no extra tag printed beside it.
  • The double nextafter overload is (since C++11) and (until C++23).
  • The constexpr /* floating-point-type */ nextafter overload is (since C++23).
  • nextafterf is (since C++11), (2), and (constexpr since C++23).
  • nextafterl is (since C++11), (3), and (constexpr since C++23).
  • The double nexttoward overload is (since C++11) and (until C++23).
  • The constexpr /* floating-point-type */ nexttoward overload is (since C++23).
  • nexttowardf is (since C++11), (4) (5), and (constexpr since C++23) β€” yes, two numbers, we don’t make the rules.
  • nexttowardl is (since C++11), (6), and (constexpr since C++23).
  • The additional std::nextafter template is (since C++11), (A), and (constexpr since C++23).
  • The additional std::nexttoward template is (since C++11), (B), and (constexpr since C++23).

The actual vibe of these functions: return the next representable value of from in the direction of to. That’s the whole mission. 🎯

  • 1-3) β€” if from equals to, to is returned. Also, (since C++23) the library provides overloads of std::nextafter for all cv-unqualified floating-point types as the type of the parameters from and to.
  • 4-6) β€” if from equals to, to is returned, converted from long double to the return type of the function without loss of range or precision.

(since C++23) the library provides overloads of std::nexttoward for all cv-unqualified floating-point types as the type of the parameter from. However, an invocation of std::nexttoward is ill-formed if the argument corresponding to from has extended floating-point type, because the next representable value (or to) is not guaranteed to be representable as long double. That’s not a drill, bestie. πŸ’€

  • A) β€” additional std::nextafter overloads are provided for all other combinations of arithmetic types.
  • B) β€” additional std::nexttoward overloads are provided for all integer types, which are treated as double.

πŸ“ Parameters πŸ“

  • from, to β€” floating-point or integer values.

πŸ’œ Return value πŸ’œ

If no errors occur, the next representable value of from in the direction of to is returned. If from equals to, then to is returned.

If a range error due to overflow occurs, Β±HUGE_VAL, Β±HUGE_VALF, or Β±HUGE_VALL is returned (with the same sign as from).

If a range error occurs due to underflow, the correct result is returned.


πŸ’€ Error handling πŸ’€

Errors are reported as specified in math_errhandling.

If the implementation supports IEEE floating-point arithmetic (IEC 60559):

  • if from is finite, but the expected result is an infinity, raises FE_INEXACT and FE_OVERFLOW.
  • if from does not equal to and the result is subnormal or zero, raises FE_INEXACT and FE_UNDERFLOW.
  • in any case, the returned value is independent of the current rounding mode.
  • if either from or to is NaN, NaN is returned.

πŸ¦‹ Notes πŸ¦‹

POSIX specifies that the overflow and the underflow conditions are range errors (errno may be set).

IEC 60559 recommends that from is returned whenever from == to. These functions return to instead, which makes the behavior around zero consistent: std::nextafter(-0.0, +0.0) returns +0.0 and std::nextafter(+0.0, -0.0) returns -0.0.

std::nextafter is typically implemented by manipulation of IEEE representation (glibc, musl).

The additional std::nextafter overloads are not required to be provided exactly as (A). They only need to be sufficient to ensure that for their first argument num1 and second argument num2:

  • If num1 or num2 has type long double, then std::nextafter(num1, num2) has the same effect as std::nextafter(static_cast<long double>(num1), static_cast<long double>(num2)).
  • Otherwise, if num1 and/or num2 has type double or an integer type, then std::nextafter(num1, num2) has the same effect as std::nextafter(static_cast<double>(num1), static_cast<double>(num2)). (until C++23)
  • Otherwise, if num1 or num2 has type float, then std::nextafter(num1, num2) has the same effect as std::nextafter(static_cast<float>(num1), static_cast<float>(num2)).

(since C++23) If num1 and num2 have arithmetic types, then std::nextafter(num1, num2) has the same effect as std::nextafter(static_cast</* common-floating-point-type */>(num1), static_cast</* common-floating-point-type */>(num2)), where /* common-floating-point-type */ is the floating-point type with the greatest floating-point conversion rank and greatest floating-point conversion subrank between the types of num1 and num2, arguments of integer type are considered to have the same floating-point conversion rank as double.

If no such floating-point type with the greatest rank and subrank exists, then overload resolution does not result in a usable candidate from the overloads provided.

The additional std::nexttoward overloads are not required to be provided exactly as (B). They only need to be sufficient to ensure that for their argument num of integer type, std::nexttoward(num) has the same effect as std::nexttoward(static_cast<double>(num)).


🌈 Example 🌈

// Run this code

#include <cfenv>
#include <cfloat>
#include <cmath>
#include <concepts>
#include <iomanip>
#include <iostream>

int main()
{
    float from1 = 0, to1 = std::nextafter(from1, 1.f);
    std::cout << "The next representable float after " << std::setprecision(20) << from1
              << " is " << to1
              << std::hexfloat << " (" << to1 << ")\n" << std::defaultfloat;

    float from2 = 1, to2 = std::nextafter(from2, 2.f);
    std::cout << "The next representable float after " << from2 << " is " << to2
              << std::hexfloat << " (" << to2 << ")\n" << std::defaultfloat;

    double from3 = std::nextafter(0.1, 0), to3 = 0.1;
    std::cout << "The number 0.1 lies between two valid doubles:\n"
              << std::setprecision(56) << "    " << from3
              << std::hexfloat << " (" << from3 << ')' << std::defaultfloat
              << "\nand " << to3 << std::hexfloat << "  (" << to3 << ")\n"
              << std::defaultfloat << std::setprecision(20);

    std::cout << "\nDifference between nextafter and nexttoward:\n";
    long double dir = std::nextafter(from1, 1.0L); // first subnormal long double
    float x = std::nextafter(from1, dir); // first converts dir to float, giving 0
    std::cout << "With nextafter, next float after " << from1 << " is " << x << '\n';
    x = std::nexttoward(from1, dir);
    std::cout << "With nexttoward, next float after " << from1 << " is " << x << '\n';

    std::cout << "\nSpecial values:\n";
    {
        // #pragma STDC FENV_ACCESS ON
        std::feclearexcept(FE_ALL_EXCEPT);
        double from4 = DBL_MAX, to4 = std::nextafter(from4, INFINITY);
        std::cout << "The next representable double after " << std::setprecision(6)
                  << from4 << std::hexfloat << " (" << from4 << ')'
                  << std::defaultfloat << " is " << to4
                  << std::hexfloat << " (" << to4 << ")\n" << std::defaultfloat;

        if (std::fetestexcept(FE_OVERFLOW))
            std::cout << "   raised FE_OVERFLOW\n";
        if (std::fetestexcept(FE_INEXACT))
            std::cout << "   raised FE_INEXACT\n";
    } // end FENV_ACCESS block

    float from5 = 0.0, to5 = std::nextafter(from5, -0.0);
    std::cout << "std::nextafter(+0.0, -0.0) gives " << std::fixed << to5 << '\n';

    auto precision_loss_demo = []<std::floating_point Fp>(const auto rem, const Fp start)
    {
        std::cout << rem;
        for (Fp from = start, to, Ξ”;
            (Ξ” = (to = std::nextafter(from, +INFINITY)) - from) < Fp(10.0);
            from *= Fp(10.0))
            std::cout << "nextafter(" << std::scientific << std::setprecision(0) << from
                      << ", INF) gives " << std::fixed << std::setprecision(6) << to
                      << "; Ξ” = " << Ξ” << '\n';
    };

    precision_loss_demo("\nPrecision loss demo for float:\n", 10.0f);
    precision_loss_demo("\nPrecision loss demo for double:\n", 10.0e9);
    precision_loss_demo("\nPrecision loss demo for long double:\n", 10.0e17L);
}

Part 1 ends here, bestie β€” part 2 is pulling up next, so keep scrolling. 🫢

Okay so here's what that whole program spits out when you actually run it β€” and honestly? icon status. Look at all these digits, she's giving precision πŸ’…

Output:
        The next representable float after 0 is 1.4012984643248170709e-45
       (0x1p-149)
        The next representable float after 1 is 1.0000001192092895508
       (0x1.000002p+0)
        The number 0.1 lies between two valid doubles:
            0.09999999999999999167332731531132594682276248931884765625
       (0x1.9999999999999p-4)
        and 0.1000000000000000055511151231257827021181583404541015625
       (0x1.999999999999ap-4)

        Difference between nextafter and nexttoward:
        With nextafter, next float after 0 is 0
        With nexttoward, next float after 0 is 1.4012984643248170709e-45

        Special values:
        The next representable double after 1.79769e+308
       (0x1.fffffffffffffp+1023) is inf (inf)
           raised FE_OVERFLOW
           raised FE_INEXACT
        std::nextafter(+0.0, -0.0) gives -0.000000

        Precision loss demo for float:
        nextafter(1e+01, INF) gives 10.000001; Ξ” = 0.000001
        nextafter(1e+02, INF) gives 100.000008; Ξ” = 0.000008
        nextafter(1e+03, INF) gives 1000.000061; Ξ” = 0.000061
        nextafter(1e+04, INF) gives 10000.000977; Ξ” = 0.000977
        nextafter(1e+05, INF) gives 100000.007812; Ξ” = 0.007812
        nextafter(1e+06, INF) gives 1000000.062500; Ξ” = 0.062500
        nextafter(1e+07, INF) gives 10000001.000000; Ξ” = 1.000000
        nextafter(1e+08, INF) gives 100000008.000000; Ξ” = 8.000000

        Precision loss demo for double:
        nextafter(1e+10, INF) gives 10000000000.000002; Ξ” = 0.000002
        nextafter(1e+11, INF) gives 100000000000.000015; Ξ” = 0.000015
        nextafter(1e+12, INF) gives 1000000000000.000122; Ξ” = 0.000122
        nextafter(1e+13, INF) gives 10000000000000.001953; Ξ” = 0.001953
        nextafter(1e+14, INF) gives 100000000000000.015625; Ξ” = 0.015625
        nextafter(1e+15, INF) gives 1000000000000000.125000; Ξ” = 0.125000
        nextafter(1e+16, INF) gives 10000000000000002.000000; Ξ” = 2.000000

        Precision loss demo for long double:
        nextafter(1e+18, INF) gives 1000000000000000000.062500; Ξ” = 0.062500
        nextafter(1e+19, INF) gives 10000000000000000001.000000; Ξ” = 1.000000
        nextafter(1e+20, INF) gives 100000000000000000008.000000; Ξ” = 8.000000

Let's walk through it because some of this is so good πŸ’–

The next representable float after 0 is 1.4012984643248170709e-45, which is 0x1p-149 β€” tiny little baby number, we love her. And the next representable float after 1 is 1.0000001192092895508, aka 0x1.000002p+0.

Then the number 0.1 β€” our girl β€” turns out to be lowkey homeless, just chilling between two valid doubles: 0.09999999999999999167332731531132594682276248931884765625 (0x1.9999999999999p-4) and 0.1000000000000000055511151231257827021181583404541015625 (0x1.999999999999ap-4). Not me getting emotional about a decimal πŸ₯²

Then the difference between nextafter and nexttoward, spelled out for the girlies: with nextafter, next float after 0 is 0. With nexttoward, next float after 0 is 1.4012984643248170709e-45. Different behaviour, same vibe.

Special values section, and okay this one's a little dramatic πŸ”₯: the next representable double after 1.79769e+308 (0x1.fffffffffffffp+1023) is inf (inf), and it raised FE_OVERFLOW and raised FE_INEXACT. Also std::nextafter(+0.0, -0.0) gives -0.000000 β€” yes, negative zero, she said what she said.

And then the precision loss demos, which are genuinely the main character of this whole page 🌈 β€” watch the delta grow as the exponent climbs:

For float: from nextafter(1e+01, INF) giving 10.000001 with Ξ” = 0.000001 all the way up to nextafter(1e+08, INF) giving 100000008.000000 with Ξ” = 8.000000.

For double: from nextafter(1e+10, INF) giving 10000000000.000002 with Ξ” = 0.000002 up to nextafter(1e+16, INF) giving 10000000000000002.000000 with Ξ” = 2.000000.

For long double: from nextafter(1e+18, INF) giving 1000000000000000000.062500 with Ξ” = 0.062500, through nextafter(1e+19, INF) giving 10000000000000000001.000000 with Ξ” = 1.000000, to nextafter(1e+20, INF) giving 100000000000000000008.000000 with Ξ” = 8.000000.

Bigger numbers, bigger jumps. That's the whole tea β˜•


πŸ‘€ See also πŸ‘€

  • C documentation for nextafter β€” go read the C version too bestie, she's the blueprint 🫢

That's the page, bestie! This one's from http://cppreference.com, dated 2024.06.10 β€” so if anything looks off, take it up with them and not me πŸ’€ Anyway, go forth and nudge your floats, one representable value at a time πŸ’œβœ¨πŸŽ€

πŸ“¦ where this page comes from