58

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
  • 7,071
  • 16
  • 58
  • 85
  • 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 `<<`. You may refer to the code given in the quoted question but, the C++ context makes it quite different (just believe me). – 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
  • for C++20: [Output 128 bit integer using stream operator](https://stackoverflow.com/q/74735391/995714) – phuclv Dec 10 '22 at 11:05

7 Answers7

44

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
  • 15,573
  • 13
  • 70
  • 139
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 4
    "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<<(__int128)` (or a free function in `std::`), which is found by ADL based on `std::ostream`. – 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
24

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
  • 69,038
  • 19
  • 164
  • 304
  • When you template the integral type, the same argument is exactly why one *should* overload `operator<<`: `__int128` should behave the same way as `int`, so there should be support for `operator<<`. – HolKann Jan 29 '20 at 11:25
  • @HolKann if you do overload `operator<<`, you better make sure you also support the various manipulators like `hex` and `setw`. – TemplateRex Jan 29 '20 at 15:16
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,841
  • 1
  • 19
  • 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
  • 678
  • 9
  • 15
  • 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
  • 2
    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
3

Noting the warning that overloading << can be misleading, due to possibly not providing some expected manipulation support, here is a version that supports the manipulations:

#include <iostream>
#include <iomanip>

// Write the 128-bit integer val to out, with a minus sign if decimal and neg
// is true. Obey all of the ostream settings of out for integer display: octal
// or hexadecimal, upper case letters, plus sign, fill character and width, and
// fill placement.
static void out128(std::ostream& out, __uint128_t val, int neg) {
    // Note if the number is zero. (No hex or octal prefix in this case.)
    auto zero = val == 0;

    // Note if upper-case letters requested.
    auto state = out.flags();
    auto upper = (state & std::ios_base::uppercase) != 0;

    // Set base for digits.
    unsigned base = state & std::ios_base::hex ? 16 :
                    state & std::ios_base::oct ? 8 :
                    10;

    // Space for digits and prefix. Generate digits starting at the end of the
    // string, going backwards. num will be the digit string. Terminate it.
    char str[47];
    auto end = str + sizeof(str), num = end;
    *--num = 0;

    // Compute and place digits in base base.
    do {
        char dig = val % base;
        val /= base;
        dig += dig < 10 ? '0' : (upper ? 'A' : 'a') - 10;
        *--num = dig;
    } while (val);

    // Prepend octal number with a zero if requested.
    if (state & std::ios_base::showbase && base == 8 && !zero)
        *--num = '0';

    // pre will be the prefix string. Terminate it.
    auto pre = num;
    *--pre = 0;

    // Put a plus or minus sign in the prefix as appropriate.
    if (base == 10) {
        if (neg)
            *--pre = '-';
        else if (state & std::ios_base::showpos)
            *--pre = '+';
    }

    // Prefix a hexadecimal number if requested.
    else if (state & std::ios_base::showbase && base == 16 && !zero) {
        *--pre = upper ? 'X' : 'x';
        *--pre = '0';
    }

    // Compute the number of pad characters and get the fill character.
    auto len = (num - pre) + (end - num) - 2;
    auto pad = out.width();
    out.width(0);
    pad = pad > len ? pad - len : 0;
    char fill = out.fill();

    // Put the padding before prefix if neither left nor internal requested.
    if (!(state & (std::ios_base::internal | std::ios_base::left)))
        while (pad) {
            out << fill;
            pad--;
        }

    // Write prefix.
    out << pre;

    // Put the padding between the prefix and the digits if requested.
    if (state & std::ios_base::internal)
        while (pad) {
            out << fill;
            pad--;
        }

    // Write digits.
    out << num;

    // Put number to the left of padding, if requested.
    if (state & std::ios_base::left)
        while (pad) {
            out << fill;
            pad--;
        }
}

// Overload << for an unsigned 128-bit integer.
std::ostream& operator<<(std::ostream& out, __uint128_t val) {
    out128(out, val, 0);
    return out;
}

// Overload << for a signed 128-bit integer. Negation of the most negative
// signed value gives the correct unsigned absolute value.
std::ostream& operator<<(std::ostream& out, __int128_t val) {
    auto state = out.flags();
    if (val < 0 && !(state & (std::ios_base::hex | std::ios_base::oct)))
        out128(out, (__uint128_t)-val, 1);
    else
        out128(out, (__uint128_t)val, 0);
    return out;
}
Mark Adler
  • 101,978
  • 13
  • 118
  • 158
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
  • 603
  • 7
  • 10
0

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
  • 829
  • 7
  • 16
  • For some reason the flag restore doesn't seem to work correctly. I don't know exactly what the problem is, but if anyone figures it out let me know – HackerBoss Dec 05 '19 at 19:45
  • Oh my gosh, no. You are printing the three pieces backwards! Least significant first. Also the `setw()` does not survive past the first number printed, losing leading zeros. Yet you keep the leading zeros for the first one, so, for example, 3 prints as `0000000000003`. If you're going to post code on stackoverflow, I'd recommend at least a tiny, tiny bit of testing first. – Mark Adler Jun 16 '22 at 20:34
  • Oh, and by the way, James Kanze's code works perfectly fine with the most negative `__int128_t` value. Apparently you didn't test that either. – Mark Adler Jun 17 '22 at 23:37