how to print __uint128_t number using gcc?

2019-01-04 01:13发布

Is there PRIu128 that behaves similar to PRIu64 from <inttypes.h>:

printf("%" PRIu64 "\n", some_uint64_value);

Or converting manually digit by digit:

int print_uint128(uint128_t n) {
  if (n == 0)  return printf("0\n");

  char str[40] = {0}; // log10(1 << 128) + '\0'
  char *s = str + sizeof(str) - 1; // start at the end
  while (n != 0) {
    if (s == str) return -1; // never happens

    *--s = "0123456789"[n % 10]; // save last digit
    n /= 10;                     // drop it
  }
  return printf("%s\n", s);
}

is the only option?

Note that uint128_t is my own typedef for __uint128_t.

标签: c gcc
11条回答
贪生不怕死
2楼-- · 2019-01-04 02:03

Based on sebastian's answer, this is for signed int128 in g++, not thread safe.

// g++ -Wall fact128.c && a.exe
// 35! overflows 128bits

#include <stdio.h>

char * sprintf_int128( __int128_t n ) {
    static char str[41] = { 0 };        // sign + log10(2**128) + '\0'
    char *s = str + sizeof( str ) - 1;  // start at the end
    bool neg = n < 0;
    if( neg )
        n = -n;
    do {
        *--s = "0123456789"[n % 10];    // save last digit
        n /= 10;                // drop it
    } while ( n );
    if( neg )
        *--s = '-';
    return s;
}

__int128_t factorial( __int128_t i ) {
    return i < 2 ? i : i * factorial( i - 1 );
}

int main(  ) {
    for( int i = 0; i < 35; i++ )
        printf( "fact(%d)=%s\n", i, sprintf_int128( factorial( i ) ) );
    return 0;
} 
查看更多
孤傲高冷的网名
3楼-- · 2019-01-04 02:04

Here's a modified version of Leffler's answer that supports from 0 to UINT128_MAX

/*      UINT64_MAX 18446744073709551615ULL */
#define P10_UINT64 10000000000000000000ULL /* 19 zeroes */
#define E10_UINT64 19

#define STRINGIZER(x) # x
#define TO_STRING(x) STRINGIZER(x)

int print_uint128_decimal(__uint128_t big) {
  size_t rc = 0;
  size_t i = 0;
  if (big >> 64) {
    char buf[40];
    while (big / P10_UINT64) {
      rc += sprintf(buf + E10_UINT64 * i, "%." TO_STRING(E10_UINT64) PRIu64, (uint64_t)(big % P10_UINT64));
      ++i;
      big /= P10_UINT64;
    }
    rc += printf("%" PRIu64, (uint64_t)big);
    while (i--) {
      fwrite(buf + E10_UINT64 * i, sizeof(char), E10_UINT64, stdout);
    }
  } else {
    rc += printf("%" PRIu64, (uint64_t)big);
  }
  return rc;
}

And try this:

print_uint128_decimal(-1); // Assuming -1's complement being 0xFFFFF...
查看更多
叼着烟拽天下
4楼-- · 2019-01-04 02:07

I think your print_uint128 function is awfully complex.

Isn't this one simpler to write and run?

void print_uint128(uint128_t n)
{
    if (n == 0) {
      return;
    }

    print_uint128(n/10);
    putchar(n%10+0x30);
}
查看更多
可以哭但决不认输i
5楼-- · 2019-01-04 02:12

The GCC 4.7.1 manual says:

6.8 128-bits integers

As an extension the integer scalar type __int128 is supported for targets having an integer mode wide enough to hold 128-bit. Simply write __int128 for a signed 128-bit integer, or unsigned __int128 for an unsigned 128-bit integer. There is no support in GCC to express an integer constant of type __int128 for targets having long long integer with less then [sic] 128 bit width.

Interestingly, although that does not mention __uint128_t, that type is accepted, even with stringent warnings set:

#include <stdio.h>

int main(void)
{
    __uint128_t u128 = 12345678900987654321;
    printf("%llx\n", (unsigned long long)(u128 & 0xFFFFFFFFFFFFFFFF));
    return(0);
}

Compilation:

$ gcc -O3 -g -std=c99 -Wall -Wextra -pedantic xxx.c -o xxx  
xxx.c: In function ‘main’:
xxx.c:6:24: warning: integer constant is so large that it is unsigned [enabled by default]
$

(This is with a home-compiled GCC 4.7.1 on Mac OS X 10.7.4.)

Change the constant to 0x12345678900987654321 and the compiler says:

xxx.c: In function ‘main’:
xxx.c:6:24: warning: integer constant is too large for its type [enabled by default]

So, it isn't easy manipulating these creatures. The outputs with the decimal constant and hex constants are:

ab54a98cdc6770b1
5678900987654321

For printing in decimal, your best bet is to see if the value is larger than UINT64_MAX; if it is, then you divide by the largest power of 10 that is smaller than UINT64_MAX, print that number (and you might need to repeat the process a second time), then print the residue modulo the largest power of 10 that is smaller than UINT64_MAX, remembering to pad with leading zeroes.

This leads to something like:

#include <stdio.h>
#include <inttypes.h>

/*
** Using documented GCC type unsigned __int128 instead of undocumented
** obsolescent typedef name __uint128_t.  Works with GCC 4.7.1 but not
** GCC 4.1.2 (but __uint128_t works with GCC 4.1.2) on Mac OS X 10.7.4.
*/
typedef unsigned __int128 uint128_t;

/*      UINT64_MAX 18446744073709551615ULL */
#define P10_UINT64 10000000000000000000ULL   /* 19 zeroes */
#define E10_UINT64 19

#define STRINGIZER(x)   # x
#define TO_STRING(x)    STRINGIZER(x)

static int print_u128_u(uint128_t u128)
{
    int rc;
    if (u128 > UINT64_MAX)
    {
        uint128_t leading  = u128 / P10_UINT64;
        uint64_t  trailing = u128 % P10_UINT64;
        rc = print_u128_u(leading);
        rc += printf("%." TO_STRING(E10_UINT64) PRIu64, trailing);
    }
    else
    {
        uint64_t u64 = u128;
        rc = printf("%" PRIu64, u64);
    }
    return rc;
}

int main(void)
{
    uint128_t u128a = ((uint128_t)UINT64_MAX + 1) * 0x1234567890ABCDEFULL +
                      0xFEDCBA9876543210ULL;
    uint128_t u128b = ((uint128_t)UINT64_MAX + 1) * 0xF234567890ABCDEFULL +
                      0x1EDCBA987654320FULL;
    int ndigits = print_u128_u(u128a);
    printf("\n%d digits\n", ndigits);
    ndigits = print_u128_u(u128b);
    printf("\n%d digits\n", ndigits);
    return(0);
}

The output from that is:

24197857200151252746022455506638221840
38 digits
321944928255972408260334335944939549199
39 digits

We can verify using bc:

$ bc
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
ibase = 16
1234567890ABCDEFFEDCBA9876543210
24197857200151252746022455506638221840
F234567890ABCDEF1EDCBA987654320F
321944928255972408260334335944939549199
quit
$

Clearly, for hex, the process is simpler; you can shift and mask and print in just two operations. For octal, since 64 is not a multiple of 3, you have to go through analogous steps to the decimal operation.

The print_u128_u() interface is not ideal, but it does at least return the number of characters printed, just as printf() does. Adapting the code to format the result into a string buffer is a not wholly trivial exercise in programming, but not dreadfully difficult.

查看更多
Evening l夕情丶
6楼-- · 2019-01-04 02:12

C++ variant. You may use it as a template to derive specialized C-version of the function:

template< typename I >
void print_uint(I value)
{
    static_assert(std::is_unsigned< I >::value, "!");
    if (value == 0) {
        putchar_unlocked('0');
        return;
    }
    I rev = value;
    I count = 0;
    while ((rev % 10) == 0) {
        ++count;
        rev /= 10;
    }
    rev = 0;
    while (value != 0) {
        rev = (rev * 10) + (value % 10);
        value /= 10;
    }
    while (rev != 0) {
        putchar_unlocked('0' + (rev % 10));
        rev /= 10;
    }
    while (0 != count) {
        --count;
        putchar_unlocked('0');
    }
}
查看更多
登录 后发表回答