2

I'm getting compiler errors when I call abs() with GCC's 128 bit type. My line looks like:

__extension__ using int128_t = __int128;

int128_t mantissa_;

// Other code

using namespace std;
int128_t temp = abs(mantissa_);

The error I'm getting locally is:

 error: call of overloaded ‘abs(const int128_t&)’ is ambiguous
     int128_t temp = abs(mantissa_);
/usr/include/stdlib.h:840:12: note: candidate: ‘int abs(int)’
  840 | extern int abs (int __x) __THROW __attribute__ ((__const__)) __wur;

/usr/include/c++/11/bits/std_abs.h:56:3: note: candidate: ‘long int std::abs(long int)’
   56 |   abs(long __i) { return __builtin_labs(__i); }
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:61:3: note: candidate: ‘long long int std::abs(long long int)’
   61 |   abs(long long __x) { return __builtin_llabs (__x); }
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:71:3: note: candidate: ‘constexpr double std::abs(double)’
   71 |   abs(double __x)
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:75:3: note: candidate: ‘constexpr float std::abs(float)’
   75 |   abs(float __x)
      |   ^~~
/usr/include/c++/11/bits/std_abs.h:79:3: note: candidate: ‘constexpr long double std::abs(long double)’
   79 |   abs(long double __x)

so it's not considering my overload (below) as a candidate?

namespace std 
{
    __extension__ using int128_t = __int128;
    
    int128_t abs(int128_t x)
    {
        return x < 0 ? x * -1 : x;  // Not ideal but no builtin for 128
    }
}

Is the overload correct?

However, when I mock an example in Godbolt, even without overloading abs(), it compiles fine:

https://godbolt.org/z/P8T1fGxcK

#include <iostream>

__extension__ using int128_t = __int128;

struct Decimal
{
    Decimal(int128_t q) : mantissa_(q)
    {
        volatile int128_t x = abs(mantissa_);  // How is this compiling?
        std::cout << "ctor" << std::endl;
    }

    int128_t mantissa_;
};

int main()
{
    Decimal dec(-6);
}

Are Godbolt using a library like Abseil, they're providing a function and that's why it is compiling?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
intrigued_66
  • 16,082
  • 51
  • 118
  • 189
  • 3
    you cannot overload `std::abs`. Adding to namespace `std` is not allowed (with few exceptions) – 463035818_is_not_an_ai Dec 12 '22 at 18:16
  • 1
    why do you want to overload it? Why do you not call `myabs(__int128)` ? – 463035818_is_not_an_ai Dec 12 '22 at 18:17
  • Can you simplify your example and post it complete? This https://onlinegdb.com/jlw0sEzKti compiles in onlinegdb. Is your `std::abs(int128_t)` _declaration_ visible at the point of use? Either by the definition or an `extern int128_t std::abs(int128_t);` declaration being before use in the same translation unit. Clearly it will not be visible simply in including or if the definition is in a separate translation unit.. – Clifford Dec 12 '22 at 18:48
  • 1
    ... and what @463035818_is_not_a_number says - it is a bad idea even if it works. – Clifford Dec 12 '22 at 18:53
  • Re your godbolt question, there is a button "Libraries" which show what libraries are used. Abseil is available but not linked by default. Your example fails in Godbolt using clang https://godbolt.org/z/f6EoGnva4. My example does not https://godbolt.org/z/fzrboPvK3 so clearly a visibility thing. – Clifford Dec 12 '22 at 19:12

1 Answers1

4

1. How to get std::abs working for __int128

The following only applies for gcc version >= 7.

For __int128 gcc provides an overload of std::abs with the following code snippet:

libstdc++-v3/include/bits/std_abs.h

#if defined(__GLIBCXX_TYPE_INT_N_0)
  __extension__ inline _GLIBCXX_CONSTEXPR __GLIBCXX_TYPE_INT_N_0
  abs(__GLIBCXX_TYPE_INT_N_0 __x) { return __x >= 0 ? __x : -__x; }
#endif

(or one of the following 3 similar functions using __GLIBCXX_TYPE_INT_N_1, __GLIBCXX_TYPE_INT_N_2, etc...)

To get those macros defined you need to compile with gnu extensions enabled (i.e. not strict c++)
For this you need to build with -std=gnu++20 instead of -std=c++20 (or -std=gnu++17, -std=gnu++14, etc... depending on the c++ version you want to target)
(if no -std= option is given at all then gcc will default to std=gnu++xx (c++ version depending on compiler version))

When compiling with gnu extensions gcc will define those macros automatically for __int128 (assuming gcc supports 128-bit ints for your target platform):
godbolt

#define __GLIBCXX_BITSIZE_INT_N_0 128
#define __GLIBCXX_TYPE_INT_N_0 __int128

Unfortunately the c abs function-family (and the gcc builtin for it - __builtin_abs) do not support __int128 at all, and calling them would result in truncation of the result value.

Example: (compiled with -std=gnu++20 -Wconversion):
godbolt

#include <cmath>

__extension__ using int128_t = __int128;

int128_t mantissa_;

int main() {
    {
        using namespace std;
        // OK - calls std::abs(__int128)
        int128_t foo = abs(mantissa_);
    }

    // OK - calls std::abs(__int128)
    int128_t bar = std::abs(mantissa_);

    // WARNING: calls abs(int) --> truncation
    int128_t baz = abs(mantissa_);

    // WARNING: calls abs(int) --> truncation
    int128_t foobar = __builtin_abs(mantissa_);
}

2. Why the godbolt compiles

The godbolt you provided compiles due to it calling the c function int abs(int)
(the code does not include using namespace std;, so std::abs is not visible)

The c abs-family of functions have different names for the possible types, so a call to abs() with __int128 as argument will not be ambigous (but will result in truncation to int):

int        abs  ( int n );
long       labs ( long n );
long long  llabs( long long n );

/* floating-point versions would be fabs, ... */

The c++-variant of abs - std::abs - is implemented with overloads instead, so a call to std::abs with __int128 will be ambigous, assuming there is no overload for __int128 defined. (__int128 -> long long, __int128 -> long, __int128 -> int, etc... would be Integral conversions; __int128 -> double, __int128 -> float, etc.. would be Floating-integral conversions. Both Integral conversions and Floating-integral conversions and have the same rank (Conversion), so none of those overloads would be a better match than any other -> ambigous)

namespace std {
    int       abs( int n );
    long      abs( long n );
    long long abs( long long n );

    /* more overloads of abs for floating-point types */
}

3. Manually adding the std::abs function

Note that adding declarations to std is generally undefined behaviour (with a few exceptions), so i would not recommend this.

Here's a comparison of the gcc version vs your version:

// version provided by gcc (when compiling with gnu extensions)
// (macros substituted for better readability)
namespace std {
    __extension__ inline constexpr __int128 abs(__int128 __x) {
        return __x >= 0 ? __x : -__x;
    }
}

// your version
namespace std {
    __extension__ using int128_t = __int128;
    
    int128_t abs(int128_t x)
    {
        return x < 0 ? x * -1 : x;  // Not ideal but no builtin for 128
    }
}

They're functionally identical, the ternary condition is just inverted and you're multiplying by negative one whereas gcc uses the unary minus operator.

godbolt example

Turtlefight
  • 9,420
  • 2
  • 23
  • 40
  • 1
    I'm using `__builtin_abs` but i think it's getting interpreted as `int` because I'm getting warnings `warning: conversion from ‘long int’ to ‘int’ may change value [-Wconversion] 197 | return __builtin_abs(x);`. Code is `T template` then `T my_abs(T x) const` then `return __builtin_abs(x);` – intrigued_66 Dec 16 '22 at 00:19
  • 1
    @intrigued_66 sorry i messed up big time - i accidentially read the code for the `fabs`-builtin instead of `abs` (missed the f in the front), so unfortunately what i stated only applies to `__float128`, but not `__int128`. I'm increadibly sorry for this mistake, i'll update my post within the next hour with the correct information. – Turtlefight Dec 16 '22 at 01:25
  • No worries. Just thinking.... is there not a way of just over-writing the sign bit? That must be faster than a CMOV and multiplication? – intrigued_66 Dec 16 '22 at 14:18
  • @intrigued_66 unfortunately it's not that simple for two's complement. IEEE-754 floats do have a dedicated sign bit, so for those a simple and can be used to set the sign bit to zero ([godbolt](https://godbolt.org/z/eqffYTeEb) - note how `.LC0` and `.LC1` are bitmasks with all but 1 bit set (the sign-bit)). For two's complement it's a bit more tricky; the most significant bit denotes if the number is positive or negative, but the negative values are ordered inversely to the positive ones - so simply flipping the most significant bit will not work: [examples](https://godbolt.org/z/1b3MrPa9Y) – Turtlefight Dec 19 '22 at 18:06
  • The usual method for implementing `abs` in two's complement is to invert the value and add 1, i.e.: `abs(x) == x >= 0 ? x : (~x + 1)` ([here's a simple explanation](https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html#whyworks) of why this is the case) - this is also what gcc will produce for both your abs implementation and it's builtin one (assuming you're compiling with optimizations enabled): [godbolt](https://godbolt.org/z/h486c91TE) (all 4 functions produce the same instructions) – Turtlefight Dec 19 '22 at 18:18
  • due to x64 not having 128-bit registers gcc will use 2 64-bit registers to represent `_int128` - `rdi` + `rsi` are the `x` parameter, and `rax` + `rdx` will be used for the return value in the linked godbolt. So there are 2 `neg`'s (to invert both registers), one `adc` to add one and 2 `cmov`'s, to ensure we only return the inverted result in case the original number was negative. – Turtlefight Dec 19 '22 at 18:39