Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to print __uint128_t number using gcc?

Tags:

c

gcc

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.

like image 283
jfs Avatar asked Jul 25 '12 18:07

jfs


People also ask

What is __ uint128_t?

Since the __uint128_t type is a GCC extension, the proper thing to do is probably to check for some known-good version of GCC. See this page for information about the macros used to version-check the GCC compiler. Follow this answer to receive notifications.

What is int128 C++?

The int128 type defines a signed 128-bit integer. The API is meant to mimic an intrinsic integer type as closely as possible, so that any forthcoming int128_t can be a drop-in replacement. The int128 type supports the following: Implicit conversion from signed integral types and unsigned types narrower than 128 bits.


1 Answers

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.

like image 189
Jonathan Leffler Avatar answered Oct 07 '22 20:10

Jonathan Leffler