52

I am using the GCC built-in type __int128 for a few things in my C++ program, nothing really significant, at least not enough to justify to use BigInt library only for that and, yet, enough to prevent to remove it totally.

My problem comes when I run into the printing parts my classes, here is a minimal example:

#include <iostream>

int main()
{
  __int128 t = 1234567890;

  std::cout << t << std::endl;

  return t;
}

Commenting out the std::cout line will make this code to compile nicely with g++, but having it will cause the following error message:

int128.c: In function ‘int main()’:
int128.c:7:13: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘__int128’)
   std::cout << t << std::endl;
             ^
int128.c:7:13: note: candidates are:
In file included from /usr/include/c++/4.9/iostream:39:0,
                 from int128.c:1:
/usr/include/c++/4.9/ostream:108:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
       operator<<(__ostream_type& (*__pf)(__ostream_type&))
       ^
/usr/include/c++/4.9/ostream:108:7: note:   no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__ostream_type& (*)(std::basic_ostream<char>::__ostream_type&) {aka std::basic_ostream<char>& (*)(std::basic_ostream<char>&)}’
/usr/include/c++/4.9/ostream:117:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ios_type& (*)(std::basic_ostream<_CharT, _Traits>::__ios_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>; std::basic_ostream<_CharT, _Traits>::__ios_type = std::basic_ios<char>] <near match>
       operator<<(__ios_type& (*__pf)(__ios_type&))
       ^
/usr/include/c++/4.9/ostream:117:7: note:   no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__ios_type& (*)(std::basic_ostream<char>::__ios_type&) {aka std::basic_ios<char>& (*)(std::basic_ios<char>&)}’
/usr/include/c++/4.9/ostream:127:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
       operator<<(ios_base& (*__pf) (ios_base&))
       ^
/usr/include/c++/4.9/ostream:127:7: note:   no known conversion for argument 1 from ‘__int128’ to ‘std::ios_base& (*)(std::ios_base&)’
/usr/include/c++/4.9/ostream:166:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long __n)
       ^
/usr/include/c++/4.9/ostream:170:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned long __n)
       ^
/usr/include/c++/4.9/ostream:174:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(bool __n)
       ^
In file included from /usr/include/c++/4.9/ostream:609:0,
                 from /usr/include/c++/4.9/iostream:39,
                 from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:91:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char; _Traits = std::char_traits<char>]
     basic_ostream<_CharT, _Traits>::
     ^
In file included from /usr/include/c++/4.9/iostream:39:0,
                 from int128.c:1:
/usr/include/c++/4.9/ostream:181:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned short __n)
       ^
In file included from /usr/include/c++/4.9/ostream:609:0,
                 from /usr/include/c++/4.9/iostream:39,
                 from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:105:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char; _Traits = std::char_traits<char>]
     basic_ostream<_CharT, _Traits>::
     ^
In file included from /usr/include/c++/4.9/iostream:39:0,
                 from int128.c:1:
/usr/include/c++/4.9/ostream:192:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned int __n)
       ^
/usr/include/c++/4.9/ostream:201:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long long __n)
       ^
/usr/include/c++/4.9/ostream:205:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(unsigned long long __n)
       ^
/usr/include/c++/4.9/ostream:220:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(double __f)
       ^
/usr/include/c++/4.9/ostream:224:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(float __f)
       ^
/usr/include/c++/4.9/ostream:232:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
       operator<<(long double __f)
       ^
/usr/include/c++/4.9/ostream:245:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
       operator<<(const void* __p)
       ^
/usr/include/c++/4.9/ostream:245:7: note:   no known conversion for argument 1 from ‘__int128’ to ‘const void*’
In file included from /usr/include/c++/4.9/ostream:609:0,
                 from /usr/include/c++/4.9/iostream:39,
                 from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:119:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__streambuf_type*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__streambuf_type = std::basic_streambuf<char>] <near match>
     basic_ostream<_CharT, _Traits>::
     ^
/usr/include/c++/4.9/bits/ostream.tcc:119:5: note:   no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__streambuf_type* {aka std::basic_streambuf<char>*}’
In file included from /usr/include/c++/4.9/iostream:39:0,
                 from int128.c:1:
/usr/include/c++/4.9/ostream:493:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, unsigned char) [with _Traits = std::char_traits<char>]
     operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
     ^
/usr/include/c++/4.9/ostream:488:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, signed char) [with _Traits = std::char_traits<char>]
     operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
     ^
/usr/include/c++/4.9/ostream:482:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, char) [with _Traits = std::char_traits<char>]
     operator<<(basic_ostream<char, _Traits>& __out, char __c)
     ^
/usr/include/c++/4.9/ostream:476:5: note: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, char) [with _CharT = char; _Traits = std::char_traits<char>]
     operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
     ^

Yes, I know, a lot of lines to explain that __int128 is just not properly handled...

Is there a simple way to get __int128 to be printed by the iostream as any other numeric types ?

EDIT: For those who are still confusing C and C++, yes, I read the question: how to print __uint128_t number using gcc? But, this was for C and not for C++ as I am asking now.

Community
  • 1
  • 1
perror
  • 6,737
  • 16
  • 59
  • 77
  • 2
    I read this question (and answer), my question is **specifically** about `g++` and **not** for `gcc`... I would like to be able to use `iostream`, not the `printf` familly. – perror Aug 04 '14 at 08:39
  • That other question seems to apply equally to C as it does to C++: there is no standard library support for this type. You will have to create something yourself. –  Aug 04 '14 at 08:48
  • 1
    The answer that I expect goes through overloading the operator `< – perror Aug 04 '14 at 08:51
  • 1
    this problem is harder than it looks, you have to consider `std::hex` and `setw` manipulators as well – TemplateRex Aug 04 '14 at 08:58
  • @TemplateRex You are right... I didn't even came to consider all these... – perror Aug 04 '14 at 09:20
  • @perror I have updated my older answer. – TemplateRex Aug 04 '14 at 09:21

6 Answers6

41

If you don't need any of the fancy formatting options, writing your own << operator is trivial. Formally, I suspect that writing one for __int128_t would be considered undefined behavior, but practically, I think it would work, up until the library starts providing actual support for it (at which point, you'd retire your conversion operator).

Anyway, something like the following should work:

std::ostream&
operator<<( std::ostream& dest, __int128_t value )
{
    std::ostream::sentry s( dest );
    if ( s ) {
        __uint128_t tmp = value < 0 ? -value : value;
        char buffer[ 128 ];
        char* d = std::end( buffer );
        do
        {
            -- d;
            *d = "0123456789"[ tmp % 10 ];
            tmp /= 10;
        } while ( tmp != 0 );
        if ( value < 0 ) {
            -- d;
            *d = '-';
        }
        int len = std::end( buffer ) - d;
        if ( dest.rdbuf()->sputn( d, len ) != len ) {
            dest.setstate( std::ios_base::badbit );
        }
    }
    return dest;
}

Note that this is just a quicky, temporary fix, until the time the g++ library supports the type. It counts on 2's complement, wrap around on overflow, for __int128_t, but I'd be very surprised if that wasn't the case (formally, it's undefined behavior). If not, you'll need to fix up the initialization of tmp. And of course, it doesn't handle any of the formatting options; you can add as desired. (Handling padding and the adjustfield correctly can be non-trivial.)

Paebbels
  • 14,791
  • 12
  • 61
  • 127
James Kanze
  • 146,674
  • 16
  • 175
  • 326
  • 3
    "but I'd be very surprised if that wasn't the case" -- gcc is well-known for aggressively optimising based on the assumption that signed overflow doesn't occur, so I wouldn't be nearly as surprised as you. Something like `-(value + 1) + __uint128_t(1)` should be valid, and has a good chance of being spotted by the optimiser as equivalent to the wraparound version of `-value`. –  Aug 04 '14 at 09:36
  • @hvd Yes. I'd probably want to see the generated code. In this particular case, however, I've never actually seen it fail, possibly because there really aren't any optimizations that could be triggered by assuming that there is no overflow. But your suggestion is a good one, and I'll doubtlessly adopt it in similar cases in the future. (It's a lot simpler than all of the work-arounds I'd come up with in the past.) – James Kanze Aug 04 '14 at 10:06
  • I guess it is a bit of a stretch, but knowing that `tmp` is always in the range of `int128_t` (because signed integer overflow cannot happen) could cause `tmp % 10` and `tmp /= 10` to use a signed division instruction, if that one is more efficient than the unsigned version. Which would then fail for obvious reasons. More likely would be a user doing a debug build compiling with `-ftrapv` or equivalent, and the negation blows up right there and then. But you could make an argument that the user shouldn't be doing that if you don't support it. So basically, I sort of agree: it's likely to work. –  Aug 04 '14 at 16:28
  • @hvd Yes. It's really a question of how much work should you put into temporary code, just to ensure compliance. (In permanent code, I'd definitely use something more robust. And less simple to write and less simple to understand.) – James Kanze Aug 04 '14 at 17:47
  • 1
    Here's an example that compiles in clang++ just fine, but not in g++. It is somewhat contrived, and I don't fully understand why g++ fails (ambiguous overload)... I made it as small as I could: http://coliru.stacked-crooked.com/a/4314762aa663718a – amdn Dec 26 '14 at 16:27
  • @amdn The reason it fails is clear from the error messages (although you have to read all of them): g++ doesn't have an overload for `<< __int128`, so tries to convert it to something for which it does have an overload. And there is more than one conversion that is equally good (e.g. `__int128` to `long`, `__int128` to `int`, etc.).. – James Kanze Dec 27 '14 at 13:15
  • @JamesKanze yes, but I have defined an exactly matching overload for the operator at global namespace in line 8, like in your answer - that's what baffles me. – amdn Dec 29 '14 at 02:24
  • @amdn Yes, but in your example, you're not using it in global namespace, so the compiler won't look there. The problem is that `__int128` isn't in any namespace, and so doesn't add any namespaces (not even the global one) to the lookup. Although formally undefined behavior, since we're talking about a temporary solution (until g++ gets their library fixed to conform to the compiler), you could put the overload in namespace `std`. (I don't like this solution, but as a temporary, or limited to one compiler, it may be acceptable.) – James Kanze Dec 30 '14 at 18:19
  • @JamesKanze, thanks for the explanation, I understand. I'm guessing that in clang++ __int128 is in the global namespace, therefore clang++ does look there for the operator<< and finds it. – amdn Dec 31 '14 at 14:39
  • @amdn I rather suspect that in clang++, there is a member function `std::ostream::operator< – James Kanze Jan 04 '15 at 17:44
  • c++11 is required using std::end, one can use buffer + 128 instead. – fmnijk Oct 20 '19 at 15:52
  • There could be at most 39 decimal digits in an int128, so a buffer of 40 chars should be enough (might even fit in the same cache line). – rustyx Oct 07 '20 at 20:59
22

I would recommend against overloading operator<< for __int128_t. The reason is that whenever you see cout << x for some integer type, you'd expect that all kinds of manipulators like std::hex or std::setw should also work. The most important guideline when overloading operators is: "do as the ints do".

As an alternative, I would recommend using a decimal_string(__int128_t) function that you can use as cout << decimal_string(x); in your code. For the string conversion, you can use the algorithm from any of the C-related Q&As. This makes it clear that you have special code for your 128-bit ints. Whenever the Standard Library upgrades to 128-bit support, you can drop it (and it's easy to grep for these functions).

TemplateRex
  • 67,479
  • 19
  • 160
  • 290
3

The stock cout does not handle __int128, but you may extends it with your own function.

For starter, code something like this:

std::ostream& operator<<(std::ostream& os, __int128 t) {
    // TODO: Convert t to string
    return os << str;
}

There are many solution on SO to convert 128 bit number to string, I'll not repeat here.

About library compatibility in comment:

You only need to roll your own function if the standard library does not provide such handler. Once the library support the type, you should then see a conflict when building, something like [ note: built-in candidate operator<< ], go try that with int64_t.

Non-maskable Interrupt
  • 3,721
  • 1
  • 18
  • 26
  • what string? decimal representation or hexadecimal? this is not compatible with the standard manipulators e.g., and can therefore be surprising – TemplateRex Aug 04 '14 at 09:04
  • I think that this is the most promising answer. Then, I need to get the combine it with this [answer](http://stackoverflow.com/questions/11656241/how-to-print-uint128-t-number-using-gcc/11660651#11660651) and it should work. Thanks – perror Aug 04 '14 at 09:06
  • You should at least mention that it's undefined behavior, and that it will break and must be retired as soon as the library adds proper support for the type. (And that if you want to do it correctly, handling all of the flags, it's decidedly non-trivial.) – James Kanze Aug 04 '14 at 09:09
  • @TemplateRex Whether it is compatible with the standard manipulators or not depends on the contents of his TODO. I've written insertion operators which are compatible (although it involves a lot of extra code). But since it's basically a temporary solution anyway, he may not need full compatibility; I'd only add the support on an as needed basis. – James Kanze Aug 04 '14 at 09:12
  • 1
    @JamesKanze because it is temporary, I would recommend writing a function string_int128 so as to emphasize that it's not a full-fledged printable type. Usage as `cout << string_int128(x)` When overloading operators, one should strive to "do as the ints do". – TemplateRex Aug 04 '14 at 09:14
  • @TemplateRex There _are_ good arguments for using the temporary. As for overloading the operator, "do as the ints do" is a good general rule (at least for int-like types, which is clearly the case here), but it may be more work that is needed for a temporary solution. – James Kanze Aug 04 '14 at 09:23
  • @TemplateRex With regards to "do as the ints do", should this include support for `wchar_t` as well, and putting the conversion routines in a facet, so that they can at least in theory be used outside of `< – James Kanze Aug 04 '14 at 17:55
  • @JamesKanze In principle, yes, but I'd take the easy way out and let the StdLib implementers worry about it. For now, I prefer a small named function. – TemplateRex Aug 04 '14 at 18:35
3

A deceptively simple approach

std::ostream& operator<<(std::ostream& o, const __int128& x) {
    if (x == std::numeric_limits<__int128>::min()) return o << "-170141183460469231731687303715884105728";
    if (x < 0) return o << "-" << -x;
    if (x < 10) return o << (char)(x + '0');
    return o << x / 10 << (char)(x % 10 + '0');
}
HolKann
  • 549
  • 7
  • 13
  • Recursivity can be neat but it can add a lot of stress to the stack (although in this case it's limited to the size of the integer, it's a pretty large integer...) – Alexis Wilke Jan 26 '21 at 16:40
  • 1
    Also the `-x` is boggus if the number is `0x800...000`, since `x == -x` so it remains negative. – Alexis Wilke Jan 26 '21 at 16:47
  • 1
    Yeah, you're right, this can increase the stack by 40 in the worst case. It's definitely not the most efficient approach. I've fixed the edge case. – HolKann Jan 26 '21 at 18:16
1

If it's not performance-critical, here's a simple, readable way to convert a non-negative int128 to a base-10 string (which can then be printed of course):

std::string toString(__int128 num) {
    std::string str;
    do {
        int digit = num % 10;
        str = std::to_string(digit) + str;
        num = (num - digit) / 10;
    } while (num != 0);
    return str;
}

We can make this several times faster by getting the digits in larger chunks instead of one at a time. But it requires us to check each chunk for any leading zeroes that have been lost and add them back in:

std::string toString(__int128 num) {
    auto tenPow18 = 1000000000000000000;
    std::string str;
    do {
        long long digits = num % tenPow18;
        auto digitsStr = std::to_string(digits);
        auto leading0s = (digits != num) ? std::string(18 - digitsStr.length(), '0') : "";
        str = leading0s + digitsStr + str;
        num = (num - digits) / tenPow18;
    } while (num != 0);
    return str;
}

Note: I've also posted a version of this answer for unsigned int128s here.

Gumby The Green
  • 539
  • 5
  • 10
1

The answers so far are good, but I just wanted to add to the answer from James Kanze. Firstly note that because of the unsigned conversion, it will not work for the number -0x80000000000000000000000000000000. Secondly, you can advantage the fact that printing with 64-bit integers works, to optimize the function implementation as follows:

std::ostream& operator<<(std::ostream& os, __int128_t value) {
    if (value < 0) {
        os << '-';
        value = -value;
    }
    // save flags to restore them
    std::ios_base::fmtflags flags(os.flags());
    // set zero fill
    os << std::setfill('0') << std::setw(13);

    // 128-bit number has at most 39 digits,
    // so the below loop will run at most 3 times
    const int64_t modulus = 10000000000000; // 10**13
    do {
        int64_t val = value % modulus;
        value /= modulus;
        if (value == 0) {
            os.flags(flags);
            return os << val;
        }
        os << val;
    } while (1);
}
HackerBoss
  • 813
  • 7
  • 15