如何使用gcc打印__uint128_t数字?

jfs*_*jfs 45 c gcc

PRIu128,其行为类似于PRIu64来自<inttypes.h>:

printf("%" PRIu64 "\n", some_uint64_value);
Run Code Online (Sandbox Code Playgroud)

或者手动逐位转换:

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);
}
Run Code Online (Sandbox Code Playgroud)

是唯一的选择吗?

请注意,这uint128_t是我自己的typedef __uint128_t.

Jon*_*ler 30

GCC 4.7.1手册说:

6.8 128位整数

作为扩展,整数标量类型__int128支持具有足够宽的整数模式以保持128位的目标.只需写入__int128带符号的128位整数,或 写入unsigned __int128无符号的128位整数.在GCC中不支持表示__int128具有long long小于[ sic ] 128位宽度的整数的目标的类型的整数常量.

有趣的是,虽然没有提及,但__uint128_t即使设置了严格的警告,该类型也被接受:

#include <stdio.h>

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

汇编:

$ 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]
$
Run Code Online (Sandbox Code Playgroud)

(这是在Mac OS X 10.7.4上使用家庭编译的GCC 4.7.1.)

将常量更改为0x12345678900987654321,编译器说:

xxx.c: In function ‘main’:
xxx.c:6:24: warning: integer constant is too large for its type [enabled by default]
Run Code Online (Sandbox Code Playgroud)

因此,操纵这些生物并不容易.十进制常量和十六进制常量的输出为:

ab54a98cdc6770b1
5678900987654321
Run Code Online (Sandbox Code Playgroud)

对于十进制打印,最好的办法是查看该值是否大于UINT64_MAX; 如果是,则除以小于UINT64_MAX的10的最大功率,打印该数字(并且您可能需要再次重复该过程),然后以小于10的最大功率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);
}
Run Code Online (Sandbox Code Playgroud)

那个输出是:

24197857200151252746022455506638221840
38 digits
321944928255972408260334335944939549199
39 digits
Run Code Online (Sandbox Code Playgroud)

我们可以验证使用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
$
Run Code Online (Sandbox Code Playgroud)

显然,对于hex,过程更简单; 只需两次操作即可移动,遮罩和打印.对于八进制,由于64不是3的倍数,因此必须通过类似的步骤进行十进制运算.

print_u128_u()接口并不理想,但它至少返回打印的字符数,就像printf()做.调整代码以将结果格式化为字符串缓冲区在编程中并不是一件非常简单的工作,但并不是非常困难.

  • 似乎`__uint128_t`和`__int128_t`只是遗留类型,它们现在分别是`unsde``到'unsigned __int128`和`__int128`.因此,海湾合作委员会就是不提它.http://gcc.gnu.org/ml/libstdc++/2011-09/msg00068.html (8认同)
  • `__uint128_t` 等价于 `unsigned __int128`。 (2认同)
  • @KennyTM:是的,我可以看到,并且知道这一点,但GCC文档中没有任何内容表明(我可以看到). (2认同)
  • @KennyTM:感谢您提供的信息。我已经更新了“工作代码”以使用首选的现代名称,而不是过时的和未记录的替代名称,并指出旧版本的 GCC 仅支持过时的表示法,而不支持新的首选记录表示法。 (2认同)

Jen*_*edt 14

没有在库中不支持打印这些类型.它们甚至不是C标准意义上的扩展整数类型.

你从后面开始打印的想法很好,但你可以使用更大的块.在P99的一些测试中,我有这样一个使用的功能

uint64_t const d19 = UINT64_C(10000000000000000000);
Run Code Online (Sandbox Code Playgroud)

作为最适合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);
Run Code Online (Sandbox Code Playgroud)

获得下半部分然后基本相同

  uint64_t high = (x >> 64);
Run Code Online (Sandbox Code Playgroud)

对于上半部分.


eph*_*ent 5

我没有内置解决方案,但划分/模数很昂贵.只需移位就可以将二进制转换为十进制.

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;
}
Run Code Online (Sandbox Code Playgroud)

(但显然128位除法/模数并不像我想象的那么昂贵.在带有GCC 4.7和Clang 3.1的Phenom 9600上-O2,这似乎比OP的方法慢了2倍-3倍.)

  • 这仍然需要模数(`j%10`),并且可能比简单的循环转换为十进制更昂贵,主要是因为它需要40*128 mod操作.你可以摆脱mod,但它可能仍然会慢,除非你也矢量化并行多个数字. (2认同)