1

By accident I was calling round() and fabs() instead of std::round() and std::fabs() and for the largest integer a long double can hold without loosing precision there was a difference.

Consider this test program round.cpp:

#include <iostream>
#include <iomanip>
#include <cstdint>
#include <limits>
#include <cmath>

using std::cout;
using std::endl;
using std::setw;
using std::setprecision;

void print(const char* msg, const long double ld)
{
    cout << msg << setprecision(20) << ld << endl;
}

void test(const long double ld)
{
    const long double ldRound = round(ld);
    const long double ldStdRound = std::round(ld);
    const long double ldFabs = fabs(ld);
    const long double ldStdFabs = std::fabs(ld);

    print("Rounding using 'round()':                     ", ldRound);
    print("Rounding using 'std::round()':                ", ldStdRound);
    print("Absolute value using 'fabs()':                ", ldFabs);
    print("Absolute value using 'std::fabs()':           ", ldStdFabs);
}

int main()
{
    const int maxDigits = std::numeric_limits<long double>::digits;
    const int64_t maxPosInt = 0xffffffffffffffff >> (64 - maxDigits + 1);
    const long double maxPosLongDouble = (long double) maxPosInt;

    cout << setw(20);
    cout << "Max decimal digits in long double:            " << maxDigits << endl;
    cout << "Max positive integer to store in long double: " << maxPosInt << endl;
    print("Corresponding long double:                    ", maxPosLongDouble);

    test(maxPosLongDouble);

    return 0;
}

When compiling with g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36.0.1)

/usr/bin/g++ -std=c++11 round.cpp -o round

and then running it, the results are one larger for the non-std function compared to the std functions:

Max decimal digits in long double:            64
Max positive integer to store in long double: 9223372036854775807
Corresponding long double:                    9223372036854775807
Rounding using 'round()':                     9223372036854775808 <== one larger
Rounding using 'std::round()':                9223372036854775807
Absolute value using 'fabs()':                9223372036854775808 <== one larger
Absolute value using 'std::fabs()':           9223372036854775807

I get the exact same output (including 64 bits for long double) when I compile for 32 bits using option -m32. Looking at the disassembly (using gbd on the 32 bit executable) for function test() I get:

(gdb) disassemble test(long double) 
Dump of assembler code for function _Z4teste:
   0x080488c0 <+0>: push   %ebp
   ...
   0x080488d2 <+18>:    call   0x8048690 <round@plt>
   ...
   0x080488ee <+46>:    call   0x8048b59 <_ZSt5rounde> (demangled: std::round(long double))
   ...
   0x080488ff <+63>:    fabs   
   ...
   0x08048918 <+88>:    call   0x8048b4f <_ZSt4fabse> (demangled: std::fabs(long double))

   ...
   0x080489a4 <+228>:   leave  
   0x080489a5 <+229>:   ret    
End of assembler dump.

So it seems different function are called for round() and std::round(). For fabs() a floating point instruction is emitted whereas for std::fabs() a function call is emitted.

Can someone explain what is causing this difference and please tell me whether using std::round() and std::fabs() is the preferred portable choice?

mgd
  • 4,114
  • 3
  • 23
  • 32
  • 2
    `::round` and `::fabs` take arguments of type `double`. The C++ versions are overloaded for different data types, so you're not calling the same functions. Try using `::roundl` and `::absl` instead. – Praetorian May 12 '20 at 16:35
  • @Praetorian Where does `::round` and `::fabs` (and `::roundl` and `::fabsl`) live. Or rather what have I included to bring them into scope? – mgd May 12 '20 at 16:41
  • 1
    It's implementation specific. Your `cmath` is probably including `math.h` which brings in the C versions of these functions that are defined in the global namespace. There's an answer to a similar question [here](https://stackoverflow.com/a/11086087/241631) that has the relevant standardese. – Praetorian May 12 '20 at 16:51
  • Ah, header `` includes all the functions from the C header `` including the C functions `float roundf(float)`, `double round(double)`, and `long double roundl(long double)` and likewise for `fabs`. – mgd May 12 '20 at 16:52
  • @Praetorian Would you like to answer to get the credits? – mgd May 12 '20 at 16:58
  • Nah, feel free to add your own answer – Praetorian May 12 '20 at 17:12

1 Answers1

0

As @Praetorian explains in the comments to the question above, the answer is very simple.

When including the C++ header <cmath> GCC brings a number of C++ math functions in the std namespace with appropriate overloads, for example:

float std::round(float)
double std::round(double)
long double std::round(long double)

float std::fabs(float)
double std::fabs(double)
long double std::fabs(long double)

However, it also brings into global scope the corresponding old C functions (same names) and as C does not support overloading these functions are only taking double as argument and returning double:

double round(double)
double fabs(double)

Therefore, the calls to round() and fabs() with no explicit namespace (and no using namespace std in the program) are calls to ::round() and ::fabs() which are the C functions that will then truncate the argument of type long double (64 bit precision) to double (53 bit precision) which explains the incorrect results.

Therefore, in C++ always ensure you either prefix with std:: or have an appropriate using declaration. I would recommend to be explicit calling std::round() and std::fabs().

P.S. In C there are also these functions if you need to handle float or long double:

float roundf(float)
long double roundl(long double)

float fabsf(float)
long double fabsl(long double)

P.P.S. In C++-14 you can also use std::abs() for any floating point type whereas in C++-11 std::abs() in only for integer types and std::fabs() is for floating point types.

mgd
  • 4,114
  • 3
  • 23
  • 32