24

What is the difference between the nextafter and the nexttoward functions of the C++ 2011 standard library ?

Vincent
  • 57,703
  • 61
  • 205
  • 388

3 Answers3

18

Since the functions originate from C, they can't be overloaded, which means two different names for functions that do the same but have different parameter(-type)s. Here are the original signatures:

float nextafter(float, float);
float nexttoward(float, long double);

And now the standard just says there should be a few overloads to make things nicer in C++ (ยง26.8 [c.math] p11):

Moreover, there shall be additional overloads sufficient to ensure:

  1. If any argument corresponding to a double parameter has type long double, then all arguments corresponding to double parameters are effectively cast to long double.
  2. Otherwise, if any argument corresponding to a double parameter has type double or an integer type, then all arguments corresponding to double parameters are effectively cast to double.
  3. Otherwise, all arguments corresponding to double parameters are effectively cast to float.

See also: ISO C 7.5, 7.10.2, 7.10.6.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    A bit confusing to say they can't be overloaded (which isn't true), then in the next sentence to say there are overloads. In fact, C++11 has overloads, and C99 has type-generic macros. โ€“ Mark Gates Mar 29 '22 at 04:47
6

A crucial difference that isn't apparent from other answers is the return type. With nextafter, the return type is std::common_type of the from and to types. With nexttoward, the return type is the from type; since to is promoted to long double, its type doesn't matter. Some test code is instructive.

Output:

nextafter ( from, to ) vs.
nexttoward( from, to )

float       s = 1.401e-45
double      d = 4.941e-324
long double q = 3.645e-4951

----- `from` is float
nextafter ( 0.0f, s ) = 1.401e-45 float
nexttoward( 0.0f, s ) = 1.401e-45 float

nextafter ( 0.0f, d ) = 4.941e-324 double
nexttoward( 0.0f, d ) = 1.401e-45 float

nextafter ( 0.0f, q ) = 3.645e-4951 long double
nexttoward( 0.0f, q ) = 1.401e-45 float

----- `from` is double
nextafter ( 0.0,  s ) = 4.941e-324 double
nexttoward( 0.0,  s ) = 4.941e-324 double

nextafter ( 0.0,  d ) = 4.941e-324 double
nexttoward( 0.0,  d ) = 4.941e-324 double

nextafter ( 0.0,  q ) = 3.645e-4951 long double
nexttoward( 0.0,  q ) = 4.941e-324 double

----- `from` is long double
nextafter ( 0.0L, s ) = 3.645e-4951 long double
nexttoward( 0.0L, s ) = 3.645e-4951 long double

nextafter ( 0.0L, d ) = 3.645e-4951 long double
nexttoward( 0.0L, d ) = 3.645e-4951 long double

nextafter ( 0.0L, q ) = 3.645e-4951 long double
nexttoward( 0.0L, q ) = 3.645e-4951 long double

Code:

// Test nextafter and nextforward.
// nextafter's  return type is std::common_type( from, to ).
// nexttoward's return type is same as `from`;
// `to` is promoted to long double, which doesn't really have any external effect.

#include <cmath>
#include <iomanip>
#include <iostream>

//------------------------------------------------------------------------------
// For type(), see
// https://stackoverflow.com/questions/81870/is-it-possible-to-print-a-variables-type-in-standard-c
#include <type_traits>
#include <typeinfo>
#include <memory>
#include <string>
#include <cstdlib>

// for demangling on non-Microsoft platforms
#ifndef _MSC_VER
    #include <cxxabi.h>
#endif

template <typename T>
std::string type()
{
    using TR = typename std::remove_reference<T>::type;

    std::unique_ptr< char, void(*)(void*) > own(
        #ifndef _MSC_VER
            abi::__cxa_demangle( typeid(TR).name(), nullptr, nullptr, nullptr ),
        #else
            nullptr,
        #endif
        std::free
    );

    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}


template <typename T>
std::string type( T x )
{
    return type<T>();
}

//------------------------------------------------------------------------------
int main( int argc, char** argv )
{
    std::cout << std::setprecision( 4 );
    std::cout << "nextafter ( from, to ) vs.\n"
              << "nexttoward( from, to )\n\n";

    float       s = std::nextafter( 0.0f, 1.0f );
    double      d = std::nextafter( 0.0,  1.0  );
    long double q = std::nextafter( 0.0L, 1.0L );
    std::cout << "float       s = " << s << '\n';
    std::cout << "double      d = " << d << '\n';
    std::cout << "long double q = " << q << "\n\n";

    std::cout << "----- `from` is float\n"; // Result type
    auto x1 = std::nextafter ( 0.0f, s );   // float
    auto y1 = std::nexttoward( 0.0f, s );   // float
    std::cout <<  "nextafter ( 0.0f, s ) = " << x1 << " " << type( x1 ) << '\n';
    std::cout <<  "nexttoward( 0.0f, s ) = " << y1 << " " << type( y1 ) << "\n\n";

    auto x2 = std::nextafter ( 0.0f, d );   // double
    auto y2 = std::nexttoward( 0.0f, d );   // float
    std::cout <<  "nextafter ( 0.0f, d ) = " << x2 << " " << type( x2 ) << '\n';
    std::cout <<  "nexttoward( 0.0f, d ) = " << y2 << " " << type( y2 ) << "\n\n";

    auto x3 = std::nextafter ( 0.0f, q );   // long double
    auto y3 = std::nexttoward( 0.0f, q );   // float
    std::cout <<  "nextafter ( 0.0f, q ) = " << x3 << " " << type( x3 ) << '\n';
    std::cout <<  "nexttoward( 0.0f, q ) = " << y3 << " " << type( y3 ) << "\n\n";

    std::cout << "----- `from` is double\n";
    auto x4 = std::nextafter ( 0.0,  s );   // double
    auto y4 = std::nexttoward( 0.0,  s );   // double
    std::cout <<  "nextafter ( 0.0,  s ) = " << x4 << " " << type( x4 ) << '\n';
    std::cout <<  "nexttoward( 0.0,  s ) = " << y4 << " " << type( y4 ) << "\n\n";

    auto x5 = std::nextafter ( 0.0,  d );   // double
    auto y5 = std::nexttoward( 0.0,  d );   // double
    std::cout <<  "nextafter ( 0.0,  d ) = " << x5 << " " << type( x5 ) << '\n';
    std::cout <<  "nexttoward( 0.0,  d ) = " << y5 << " " << type( y5 ) << "\n\n";

    auto x6 = std::nextafter ( 0.0,  q );   // long double
    auto y6 = std::nexttoward( 0.0,  q );   // double
    std::cout <<  "nextafter ( 0.0,  q ) = " << x6 << " " << type( x6 ) << '\n';
    std::cout <<  "nexttoward( 0.0,  q ) = " << y6 << " " << type( y6 ) << "\n\n";

    std::cout << "----- `from` is long double\n";
    auto x7 = std::nextafter ( 0.0L, s );   // long double
    auto y7 = std::nexttoward( 0.0L, s );   // long double
    std::cout <<  "nextafter ( 0.0L, s ) = " << x7 << " " << type( x7 ) << '\n';
    std::cout <<  "nexttoward( 0.0L, s ) = " << y7 << " " << type( y7 ) << "\n\n";

    auto x8 = std::nextafter ( 0.0L, d );   // long double
    auto y8 = std::nexttoward( 0.0L, d );   // long double
    std::cout <<  "nextafter ( 0.0L, d ) = " << x8 << " " << type( x8 ) << '\n';
    std::cout <<  "nexttoward( 0.0L, d ) = " << y8 << " " << type( y8 ) << "\n\n";

    auto x9 = std::nextafter ( 0.0L, q );   // long double
    auto y9 = std::nexttoward( 0.0L, q );   // long double
    std::cout <<  "nextafter ( 0.0L, q ) = " << x9 << " " << type( x9 ) << '\n';
    std::cout <<  "nexttoward( 0.0L, q ) = " << y9 << " " << type( y9 ) << "\n\n";

    return 0;
}

On my laptop at least (2.3 GHz Core i9, macOS 12), for normal numbers the performance was similar (3.0e-9 sec), but if from is subnormal, nexttoward is about 11x slower than nextafter (5.0e-8 versus 4.5e-9 sec).

Mark Gates
  • 452
  • 5
  • 8
3

Read man page:

The nexttoward() functions do the same as the nextafter() functions, except that they have a long double second argument.

hired777
  • 485
  • 3
  • 10
  • 4
    I think you forgot to quote the part about how the second argument is used. โ€“ sblom Aug 21 '12 at 22:16
  • 2
    Without discussing the implications, this is not very helpful. Are there corner cases where the behavior can differ? Is there a performance overhead of converting to long double internally? โ€“ bluenote10 Oct 23 '20 at 09:38