如何使用gcc打印__uint128_t数字?

时间:2012-07-25 18:28:50

标签: c 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__uint128_t我自己的typedef。

15 个答案:

答案 0 :(得分:29)

GCC 4.7.1 manual说:

  

6.8 128位整数

     

作为扩展,具有整数的目标支持整数标量类型__int128   模式宽度足以容纳128位。只需为带符号的128位整数写__int128,或者   unsigned __int128表示无符号的128位整数。海湾合作委员会没有表示支持   类型为__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);
}

汇编:

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

(这是在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]

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

ab54a98cdc6770b1
5678900987654321

对于十进制打印,最好的办法是查看该值是否大于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);
}

该输出是:

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()一样。调整代码以将结果格式化为字符串缓冲区在编程中并不是一件非常简单的工作,但并不是非常困难。

答案 1 :(得分:14)

图书馆中没有支持打印这些类型。它们甚至不是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 :(得分: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;
}

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

答案 3 :(得分:3)

您可以使用这个简单的宏:

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

答案 4 :(得分:3)

我认为你的print_uint128功能非常复杂。

编写和运行这个不是更简单吗?

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

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

答案 5 :(得分:3)

根据塞巴斯蒂安的回答,这是针对g ++签名的int128,而非线程安全。

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

答案 6 :(得分:1)

根据上面的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;
}

这似乎按预期工作。

答案 7 :(得分:0)

这是莱弗勒答案的修改版本,支持从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's complement being 0xFFFFF...

答案 8 :(得分:0)

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

答案 9 :(得分:0)

  

如何使用gcc打印__uint128_t数字?
  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

答案 10 :(得分:0)

这是针对C ++的,但是我将其留在这里,因为我没有找到针对 unsigned 128位整数的此问题的C ++版本。

这是将uint128转换为以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;
}

答案 11 :(得分:0)

我想以十进制打印无符号的 64/128 位数字,不想重新发明轮子。所以“pu128()”有3种情况:<10^19,<10^38,否则。也许不是最快的,但应该是便携的。定义 UINT128_MAX 和 UINT128_C 宏。

$ gcc -Wall -Wextra -pedantic lu.c
$ ./a.out 
0
10000000000000000000
18446744073709551615
0
10000000000000000000
18446744073709551615
100000000000000000000000000000000000000
340282366920938463463374607431768211455
$ 
$ cat lu.c 
#include <stdio.h>
#include <inttypes.h>

#define UINT128_C(u)     ((__uint128_t)u)

void pu64(__uint64_t u)   { printf("%" PRIu64, u); }
void pu640(__uint64_t u)  { printf("%019" PRIu64, u); }

#define D19_ UINT64_C(10000000000000000000)
const __uint128_t d19_ = D19_;
const __uint128_t d38_ = UINT128_C(D19_)*D19_;

const __uint128_t UINT128_MAX = UINT128_C(UINT64_MAX)<<64 | UINT64_MAX;

void pu128(__uint128_t u)
{
       if (u < d19_) pu64(u);
  else if (u < d38_) { pu64(u/d19_); pu640(u%d19_); }
  else               { pu64(u/d38_); u%=d38_; pu640(u/d19_); pu640(u%d19_); }
}

int main()
{
  pu64(0); puts("");
  pu64(d19_); puts("");
  pu64(UINT64_MAX); puts("");

  pu128(0); puts("");
  pu128(d19_); puts("");
  pu128(UINT64_MAX); puts("");
  pu128(d38_); puts("");
  pu128(UINT128_MAX); puts("");
}
$ 

答案 12 :(得分:0)

在我之前的回答中,我展示了如何基于“printf()”打印 128 位数字。

我已经实现了一个 256 位无符号整数类型 uint256_t:

typedef __uint128_t uint256_t[2];

我已经实现了所需的操作,比如“sqr()”以 __uint128_t 作为参数并计算 uint256_t 作为结果。

我有 uint256_t 的十六进制打印,现在想要十进制打印。但目前我的 uint256_t 只有“mod_256()”,但没有“div()”,所以在许多答案中看到的“n/=10”是没有选择的。我找到了一个有效的(缓慢的)解决方案,并且由于我只在定时部分之外使用打印,这是可以接受的。代码可以在这个要点中找到(包括编译命令的详细信息):
https://gist.github.com/Hermann-SW/83c8ab9e10a0bb64d770af543ed08445

如果您使用参数运行 sqr.cpp,它只会输出 UINT256_MAX 并退出:

if (argc>1)  { pu256(UINT256_MAX); puts(""); return 0; }

$ ./sqr 1
115792089237316195423570985008687907853269984665640564039457584007913129639935
$

棘手的部分是递归调用以达到最大使用数字,然后减去第一个数字并输出。递归完成剩下的工作。函数 "pu256()" 使用了 10 "mul10()" 的快速乘法:

...
void mul10(uint256_t d, uint256_t x)
{
  uint256_t t = { x[0], x[1] };
  shl_256(t, 2);
  add_256(d, x, t);
  shl_256(d, 1);
}

const uint256_t UINT256_MAX_10th = UINT256( UINT128(0x1999999999999999, 0x9999999999999999), UINT128(0x9999999999999999, 0x999999999999999A) );

void pu256_(uint256_t v, uint256_t t, const uint256_t o)
{
  if (!lt_256(v, t) && le_256(o, UINT256_MAX_10th))
  {
    uint256_t nt, no = { t[0], t[1] };
    mul10(nt, t);
    pu256_(v, nt, no);
  }
  char d = '0';
  while (le_256(o, v))
  {
    sub_256(v, v, o);
    ++d;
  }
  putchar(d);
}

void pu256(const uint256_t u)
{
  if ((u[1]==0) && (u[0]==0))  putchar('0');
  else
  {
    uint256_t v = { u[0], u[1] }, t = UINT256( 0, 10 ), o = UINT256( 0, 1 );
    pu256_(v, t, o);
  }
}
...

如前所述,这种方法仅适用于整数类型缺少除法运算。

答案 13 :(得分:0)

您可以重新定义运算符 cin 和 cout 以使用 __int128_t。您应该只将 __int128_t 转换为字符串和 cin/cout 字符串

typedef __int128_t lint;

istream& operator >> (istream &in, lint &x) {
    string s;
    in >> s;
    for (lint i = s.size() - 1, p = 1; i >= 0; i--, p *= 10) x += p * (s[i] - '0');
    return in;
}

ostream& operator << (ostream &out, lint x) {
    string s;
    while (x > 0) {
        s.push_back(x % 10 + '0');
        x /= 10;
    }
    reverse(s.begin(), s.end());
    out << s;
    return out;
}

答案 14 :(得分:-2)

很像#3

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

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