Как напечатать номер __uint128_t с помощью gcc?

Есть ли PRIu128, который ведет себя аналогично PRIu64 из <inttypes.h>:

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

Или преобразование вручную по цифре:

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);
}

- единственный вариант?

Обратите внимание, что uint128_t - это мой собственный typedef для __uint128_t.

Ответ 1

Нет никакой поддержки в библиотеке для печати этих типов. Они не являются даже расширенными целыми типами в смысле стандарта C.

Ваша идея начать печать со спины хорошая, но вы можете использовать гораздо большие куски. В некоторых тестах для P99 у меня есть такая функция, которая использует

uint64_t const d19 = UINT64_C(10000000000000000000);

как наибольшая степень 10, которая вписывается в uint64_t.

Как десятичные числа, эти большие числа становятся нечитаемыми очень скоро, поэтому другой, более простой вариант - напечатать их в шестнадцатеричном формате. Тогда вы можете сделать что-то вроде

  uint64_t low = (uint64_t)x;
  // This is UINT64_MAX, the largest number in 64 bit
  // so the longest string that the lower half can occupy
  char buf[] = { "18446744073709551615" };
  sprintf(buf, "%" PRIX64, low);

чтобы получить нижнюю половину, а затем в основном то же самое с

  uint64_t high = (x >> 64);

для верхней половины.

Ответ 2

Руководство GCC 4.7.1:

6.8 128-битовые целые числа

В качестве расширения поддерживается целочисленный скалярный тип __int128 для целей, имеющих целое число достаточно широко, чтобы удерживать 128-бит. Просто напишите __int128 для подписанного 128-битного целого числа или unsigned __int128 для неподписанного 128-битного целого числа. В GCC нет поддержки для выражения целочисленная константа типа __int128 для целей, имеющих long long integer с меньшим количеством [sic] Ширина 128 бит.

Интересно, что, хотя это не упоминается __uint128_t, этот тип принимается даже при строгих предупреждениях:

#include <stdio.h>

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

Компиляция:

$ 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]
$

(Это с компиляцией GCC 4.7.1 с домашней компиляцией в Mac OS X 10.7.4.)

Измените константу на 0x12345678900987654321, и компилятор говорит:

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

Таким образом, управлять этими существами непросто. Выходы с десятичной константой и шестнадцатеричными константами:

ab54a98cdc6770b1
5678900987654321

Для печати в десятичной форме лучше всего посмотреть, превышает ли значение значение UINT64_MAX; если это так, то вы делите на наибольшую мощность 10, меньшую, чем UINT64_MAX, напечатайте это число (и вам может понадобиться повторить процесс второй раз), затем распечатайте вычет по модулю наибольшей мощности 10, которая меньше UINT64_MAX, не забывая проложить нулевые нули.

Это приводит к чему-то вроде:

#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);
}

Выход из этого:

24197857200151252746022455506638221840
38 digits
321944928255972408260334335944939549199
39 digits

Мы можем проверить, используя 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
$

Очевидно, что для hex процесс проще; вы можете перемещать и маскировать и печатать всего за две операции. Для восьмеричного, поскольку 64 не кратно 3, вам нужно пройти аналогичные шаги в десятичную операцию.

Интерфейс print_u128_u() не идеален, но, по крайней мере, возвращает количество напечатанных символов, как это делает printf(). Адаптация кода для форматирования результата в строковый буфер - это не совсем тривиальное упражнение в программировании, но не ужасно трудное.

Ответ 3

У меня нет встроенного решения, но деление/модуль дорого. Вы можете преобразовать двоичный код в десятичный с помощью только сдвигов.

static char *qtoa(uint128_t n) {
    static char buf[40];
    unsigned int i, j, m = 39;
    memset(buf, 0, 40);
    for (i = 128; i-- > 0;) {
        int carry = !!(n & ((uint128_t)1 << i));
        for (j = 39; j-- > m + 1 || carry;) {
            int d = 2 * buf[j] + carry;
            carry = d > 9;
            buf[j] = carry ? d - 10 : d;
        }
        m = j;
    }
    for (i = 0; i < 38; i++) {
        if (buf[i]) {
            break;
        }
    }
    for (j = i; j < 39; j++) {
        buf[j] += '0';
    }
    return buf + i;
}

(Но, по-видимому, 128-битное деление/модуль не так дорого, как я думал. На Phenom 9600 с GCC 4.7 и Clang 3.1 на -O2 это, похоже, работает на 2x-3x медленнее, чем OP-метод.)

Ответ 4

Вы можете использовать этот простой макрос:

typedef __int128_t int128 ;
typedef __uint128_t uint128 ;

uint128  x = (uint128) 123;

printf("__int128 max  %016"PRIx64"%016"PRIx64"\n",(uint64)(x>>64),(uint64)x);

Ответ 5

Я думаю, что ваша функция print_uint128 ужасно сложна.

Не проще ли писать и запускать?

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

    print_uint128(n/10);
    putchar(n%10+0x30);
}

Ответ 6

Основываясь на ответе sebastian, это для подписанного int128 в g++, а не для потоковой безопасности.

// 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;
} 

Ответ 7

Отключение ответа abelenky выше, я придумал это.

void uint128_to_str_iter(uint128_t n, char *out,int firstiter){
    static int offset=0;
    if (firstiter){
        offset=0;
    }
    if (n == 0) {
      return;
    }
    uint128_to_str_iter(n/10,out,0);
    out[offset++]=n%10+0x30;
}

char* uint128_to_str(uint128_t n){
    char *out=calloc(sizeof(char),40);
    uint128_to_str_iter(n, out, 1);
    return out;
}

Кажется, что он работает по назначению.

Ответ 8

Здесь приведена модифицированная версия ответа Леффлера, которая поддерживает от 0 до 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;
}

И попробуйте следующее:

print_uint128_decimal(-1); // Assuming -1 complement being 0xFFFFF...

Ответ 9

C++ вариант. Вы можете использовать его как шаблон для получения специализированной C-версии функции:

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');
    }
}

Ответ 10

Как напечатать номер __uint128_t с помощью GCC?
Есть ли PRIu128, который ведет себя подобно PRIu64 из:

Нет. Вместо того, чтобы печатать в десятичном виде, печатать в строку.

Необходимого размера строкового буфера достаточно, чтобы выполнить работу в соответствии со значением x.

typedef signed __int128 int128_t;
typedef unsigned __int128 uint128_t;

// Return pointer to the end
static char *uint128toa_helper(char *dest, uint128_t x) {
  if (x >= 10) {
    dest = uint128toa_helper(dest, x / 10);
  }
  *dest = (char) (x % 10 + '0');
  return ++dest;
}

char *int128toa(char *dest, int128_t x) {
  if (x < 0) {
    *dest = '-';
    *uint128toa_helper(dest + 1, (uint128_t) (-1 - x) + 1) = '\0';
  } else {
    *uint128toa_helper(dest, (uint128_t) x) = '\0';
  }
  return dest;
}

char *uint128toa(char *dest, uint128_t x) {
  *uint128toa_helper(dest, x) = '\0';
  return dest;
}

Тестовое задание. В худшем случае размер буфера: 41.

int main(void) {
  char buf[41];
  puts("1234567890123456789012345678901234567890");
  puts(uint128toa(buf, 0));
  puts(uint128toa(buf, 1));
  puts(uint128toa(buf, (uint128_t) -1));
  int128_t mx = ((uint128_t) -1) / 2;
  puts(int128toa(buf, -mx - 1));
  puts(int128toa(buf, -mx));
  puts(int128toa(buf, -1));
  puts(int128toa(buf, 0));
  puts(int128toa(buf, 1));
  puts(int128toa(buf, mx));
  return 0;
}

Выход

1234567890123456789012345678901234567890
0
1
340282366920938463463374607431768211455
-170141183460469231731687303715884105728
-170141183460469231731687303715884105727
-1
0
1
170141183460469231731687303715884105727

Ответ 11

Это для C++, но я оставлю это здесь, так как я не нашел C++ версию этого вопроса для 128-битных целых без знака.

Вот простой, читабельный способ преобразования uint128 в строку base-10 (которую вы можете затем распечатать или сделать как хотите):

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

При необходимости мы можем сделать это в несколько раз быстрее, получая цифры большими кусками вместо одного за раз. Но это требует, чтобы мы проверяли каждый блок на наличие лидирующих нулей, которые были потеряны, и добавляли их обратно:

std::string toString(__uint128_t num) {
    auto tenPow19 = 10000000000000000000;
    std::string str;
    do {
        uint64_t digits = num % tenPow19;
        auto digitsStr = std::to_string(digits);
        auto leading0s = (digits != num) ? std::string(19 - digitsStr.length(), '0') : "";
        str = leading0s + digitsStr + str;
        num = (num - digits) / tenPow19;
    } while (num != 0);
    return str;
}

Ответ 12

как # 3

unsigned __int128 g = ...........;

printf ("g = 0x%lx%lx\r\n", (uint64_t) (g >> 64), (uint64_t) g);