【问题标题】:how to print __uint128_t number using gcc?如何使用 gcc 打印 __uint128_t 数字?
【发布时间】:2012-07-24 06:27:17
【问题描述】:

是否有PRIu128 的行为类似于来自<inttypes.h>PRIu64

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。

【问题讨论】:

  • 我不会在函数中执行打印,而是返回一个字符串表示,所以我可以用它做一些事情而不是直接打印它。
  • @DanielFischer:char str[40] = {0}; 已经用零填充了整个数组。
  • @Wug:是的。通常我会。这只是一个示例,以避免使用传递缓冲区的样板。
  • @KennyTM 哦,呵呵!我怎么忽略了这一点?谢谢指正。
  • 小心 GCC 的 __uint128_t。它在许多平台上给我们带来了问题,比如 ARM64、ARMEL 和 S/390。我们不得不放弃使用它,因为它太麻烦了。例如,GCC 将 u = 93 - 0 - 0 - 0(使用 128 位类型)的结果计算为 18446744073709551615 在 ARM64 上。

标签: c gcc


【解决方案1】:

GCC 4.7.1 manual 说:

6.8 128 位整数

作为扩展,整数标量类型__int128 支持具有整数的目标 模式足够宽以容纳 128 位。只需将__int128 写成带符号的 128 位整数,或 unsigned __int128 用于无符号 128 位整数。 GCC 中不支持表达 __int128 类型的整数常量,用于具有小于 [sic] 的 long long 整数的目标 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 的最大幂为模打印残差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
$

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

print_u128_u() 接口并不理想,但它至少会返回打印的字符数,就像printf() 一样。调整代码以将结果格式化为字符串缓冲区在编程中并不是一项完全简单的练习,但也不是非常困难。

【讨论】:

  • __uint128_t 仅相当于 unsigned __int128
  • @KennyTM:是的,我可以看到,并且知道,但是 GCC 文档中没有任何内容(我可以看到)。
  • 看来__uint128_t__int128_t 只是现在的typedefed 到unsigned __int128__int128 的遗留类型。正因为如此,GCC 只是不提它。 gcc.gnu.org/ml/libstdc++/2011-09/msg00068.html
  • @KennyTM:感谢您提供的信息。我已经更新了“工作代码”以使用首选的现代名称而不是过时和未记录的替代名称,并指出旧版本的 GCC 仅支持过时的表示法,而不支持新的首选记录表示法。
【解决方案2】:

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

您从背面开始打印的想法不错,但您可以使用更大的块。在 P99 的一些测试中,我有一个使用

的函数
uint64_t const d19 = UINT64_C(10000000000000000000);

作为适合 uint64_t 的 10 的最大幂。

作为十进制,这些大数字很快就会变得不可读,因此另一个更简单的选择是以十六进制打印它们。然后你可以做类似的事情

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

上半部分。

【讨论】:

  • 为什么它们不是 C 意义上的扩展整数类型(我想是 N1256 6.2.5“类型”)?确实,sizeof(intmax_t) 给了我 8 而不是 16。为什么?
  • 啊,在 stackoverflow.com/questions/21265462/… 上要求 C++,因为那当然会允许 %ju
  • UINT64_MAX 是十进制的最长,而不是十六进制(当然会更短,16 个十六进制数字)。顺便说一句,十进制版本的一个聪明方法是使用预处理器通过“字符串化”UINT64_MAX 生成字符串。
  • 大块的问题是它们的前导零被切掉了,所以你必须检测到什么时候发生这种情况并将它们重新添加进去。但是it can be done,是的,它可以更快。
【解决方案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 位除法/模数并不像我想象的那么昂贵。在 -O2 带有 GCC 4.7 和 Clang 3.1 的 Phenom 9600 上,这似乎比 OP 的方法慢 2x-3x。)

【讨论】:

  • 这仍然需要模数 (j % 10),并且可能比简单的循环转换为十进制要昂贵得多,主要是因为它需要 40*128 的模数运算。您可以摆脱 mod,但它可能仍然会更慢,除非您也将它矢量化,并行执行多个数字。
  • @ChrisDodd 我优化了%,但是我机器上的基准测试表明你是对的——毕竟这更慢,至少在 128 位。但是,随着数字变大,它的损失会减少……也许这种技术更适合 bignums。
  • 或者我应该尝试使用硬件 BCD 支持?
【解决方案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】:

根据塞巴斯蒂安的回答,这是用于 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】:

    根据上面 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】:

      如何使用 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
      

      【讨论】:

      • 如果你需要一个缓冲区开头的结果,最有效的方法大概是从一个本地固定大小缓冲区的末尾开始(自动存储),然后memcpy将结果放入调用者的缓冲区。这比使用实际递归在返回堆栈之前不存储任何内容更有效。另一个优化是在您的数字适合时使用uint64_t,因此(至少在64位目标上)您可能会使用乘法逆而不是调用辅助函数来获得n%10n/=10 double -宽度分割。
      • @PeterCordes True - 关于递归与本地缓冲区。使用 compound literal 作为 How to use compound literals to fprintf() multiple formatted numbers with arbitrary bases? 的缓冲区空间怎么样?
      • 是的,您可以将其作为 memcpyfputs 的源 arg 或其他任何内容,以便将其全部放在一行中。
      【解决方案8】:

      我想以十进制打印无符号的 64/128 位数字,并且不想重新发明轮子。所以“pu128()”有3种情况:

      $ 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("");
      }
      $ 
      

      【讨论】:

      • 好主意。次要微优化:商u/d38_unsigned 范围内。 else { pu64(u/d38_); ... --> else { printf("%u", (unsigned) (u/d38_)); ....
      【解决方案9】:

      这是 Leffler 答案的修改版本,支持从 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...
      

      【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          在我之前的回答中,我展示了我是如何根据“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”是没有选择的。我找到了一个可行的(慢)解决方案,并且由于我仅在定时部分之外使用打印,因此这是可以接受的。代码可以在这个 gist 中找到(包括编译命令细节):
          https://gist.github.com/Hermann-SW/83c8ab9e10a0bb64d770af543ed08445

          如果您使用 arg 运行 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);
            }
          }
          ...
          

          如前所述,这种方法只对整数类型的缺失除法运算有意义。

          【讨论】:

          • 由于 C[++] 允许的最大常量大小是 64 位,宏 "UINT128(h,l)" 和 "UINT128(h, l)" 可以很容易地从左侧指定 256 位常量向右”,如这一行中的“const uint256_t UINT256_MAX_10th = UINT256(UINT128(0x1999999999999999, ...”
          【解决方案12】:

          您可以重新定义运算符 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;
          }
          
          

          【讨论】:

          • OP 使用 C,而不是 C++。
          【解决方案13】:

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

          这是一种将 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;
          }
          

          【讨论】:

          • @jfs,真的。我添加了一条注释。
          • 如果你喜欢流,你也可以使用 setw/setfill。
          • std::to_string 很慢
          • @PSPCODER 标准库中有更快的函数吗?
          • std::to_string 在使用 -O0 * 编译时很慢
          【解决方案14】:

          很像#3

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

          【讨论】:

          • 虽然这段代码 sn-p 可以解决问题,including an explanation 确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
          • 这与 Jens Gustedt 的回答没有什么不同,而且更糟。 %lx 不是打印uint64_t 的正确方法,PRIX64 是要使用的一种
          • 也和user2107435的回答差不多
          • (uint64_t) (g &gt;&gt; 64) 大于 0 时代码失败,(uint64_t) g 应该总是打印 16 个字符。它可能需要前导零。
          猜你喜欢
          • 2020-12-28
          • 1970-01-01
          • 2019-10-12
          • 1970-01-01
          • 1970-01-01
          • 2012-04-30
          • 1970-01-01
          相关资源
          最近更新 更多