【问题标题】:How to convert a 128-bit integer to a decimal ascii string in C?如何在 C 中将 128 位整数转换为十进制 ascii 字符串?
【发布时间】:2011-12-22 19:56:59
【问题描述】:

我正在尝试将存储为 4 个无符号整数数组的 128 位无符号整数转换为 C 中的十进制字符串表示:

unsigned int src[] = { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 };
printf("%s", some_func(src)); // gives "53072739890371098123344"

(上面的输入和输出示例完全是虚构的;我不知道输入会产生什么。)

如果我要使用十六进制、二进制或八进制,这将是一个简单的掩码和移位来剥离最低有效字符的问题。但是,在我看来,我需要进行 10 进制除法。不幸的是,我不记得如何跨多个整数执行此操作,而且我使用的系统不支持大于 32 位的数据类型,因此无法使用 128 位类型。使用不同的语言也被淘汰了,我宁愿避免仅仅为了这个操作而使用大量的库。

【问题讨论】:

  • 如果你不想要一个 bignum 库,你将不得不自己实现长除法。它的工作原理类似于纸笔算法,只是因为它是二进制的,所以更容易,因此您不必进行太多猜测。你会发现你需要减法和移位。您确定不想使用 bignum 库吗?您将自己实现一个相当完整的。
  • 十进制适用于人类。他们往往会在第 7 位数字之后失去兴趣。这到底是什么意思?
  • 上面的应该如何打印出来?作为“0x1234567890abcdef ...”?作为十进制?
  • @ephemient "(上面的输入和输出示例完全是虚构的;我不知道那个输入会产生什么。)" 他不知道 xD
  • 您可以使用double dabble

标签: c string int ascii


【解决方案1】:

不需要除法:

#include <string.h>
#include <stdio.h>

typedef unsigned long uint32;

/* N[0] - contains least significant bits, N[3] - most significant */
char* Bin128ToDec(const uint32 N[4])
{
  // log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322
  static char s[128 / 3 + 1 + 1];
  uint32 n[4];
  char* p = s;
  int i;

  memset(s, '0', sizeof(s) - 1);
  s[sizeof(s) - 1] = '\0';

  memcpy(n, N, sizeof(n));

  for (i = 0; i < 128; i++)
  {
    int j, carry;

    carry = (n[3] >= 0x80000000);
    // Shift n[] left, doubling it
    n[3] = ((n[3] << 1) & 0xFFFFFFFF) + (n[2] >= 0x80000000);
    n[2] = ((n[2] << 1) & 0xFFFFFFFF) + (n[1] >= 0x80000000);
    n[1] = ((n[1] << 1) & 0xFFFFFFFF) + (n[0] >= 0x80000000);
    n[0] = ((n[0] << 1) & 0xFFFFFFFF);

    // Add s[] to itself in decimal, doubling it
    for (j = sizeof(s) - 2; j >= 0; j--)
    {
      s[j] += s[j] - '0' + carry;

      carry = (s[j] > '9');

      if (carry)
      {
        s[j] -= 10;
      }
    }
  }

  while ((p[0] == '0') && (p < &s[sizeof(s) - 2]))
  {
    p++;
  }

  return p;
}

int main(void)
{
  static const uint32 testData[][4] =
  {
    { 0, 0, 0, 0 },
    { 1048576, 0, 0, 0 },
    { 0xFFFFFFFF, 0, 0, 0 },
    { 0, 1, 0, 0 },
    { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 }
  };
  printf("%s\n", Bin128ToDec(testData[0]));
  printf("%s\n", Bin128ToDec(testData[1]));
  printf("%s\n", Bin128ToDec(testData[2]));
  printf("%s\n", Bin128ToDec(testData[3]));
  printf("%s\n", Bin128ToDec(testData[4]));
  return 0;
}

输出:

0
1048576
4294967295
4294967296
11248221411398543556294285637029484152

【讨论】:

  • unsigned long 在 64 位系统上是 8 字节。我看到你在移动时屏蔽了 n 的值并使用 sizeof,这很好,但通常如果你调用 uint32,它应该是 4 字节长。我建议将 typedef 更改为 unsigned int,或使用 long long 并将 typedef 更改为 uint64(或仅使用 stdint.h)。
  • 我喜欢这个 - 所有快速操作,我能看到的唯一缺点是它的位数为 O(n^2)。为什么是“& 0xFFFFFFFF”?想知道改变“s[j] += s[j] - '0' + carry;”到“s[j] = (s[j] 对字符串进行传递
  • @BobbyPowers: unsigned int 不保证至少为 32 位长。 unsigned long 是。系统是否为 64 位无关紧要,超出了 C 标准。
  • @Silverhalide:如果您考虑一下,除法(以及乘法)本身就是 O(n^2) 并且在循环中执行它不会让您获得更好的 O()。 “& 0xFFFFFFFF”是为了保证如果unsigned long超过32位,我们就忽略不使用的。你不能做“s[j] = (s[j]
  • 与其定义自己的uint32,不如使用来自stdint.h 的标准uint32_t
【解决方案2】:

简单的除法基数 2^32,以相反的顺序打印十进制数字,使用 64 位算术,复杂度 O(n) 其中n 是表示中的十进制位数:

#include <stdio.h>

unsigned int a [] = { 0x12345678, 0x12345678, 0x12345678, 0x12345678 };

/* 24197857161011715162171839636988778104 */

int
main ()
{
  unsigned long long d, r;

  do
    {
      r = a [0];

      d = r / 10;
      r = ((r - d * 10) << 32) + a [1];
      a [0] = d;

      d = r / 10;
      r = ((r - d * 10) << 32) + a [2];
      a [1] = d;

      d = r / 10;
      r = ((r - d * 10) << 32) + a [3];
      a [2] = d;

      d = r / 10;
      r = r - d * 10;
      a [3] = d;

      printf ("%d\n", (unsigned int) r);
    }
  while (a[0] || a[1] || a[2] || a[3]);

  return 0;
}

编辑:更正了循环,因此如果数组 a 仅包含零,它将显示 0。 此外,数组从左到右读取,a[0] 是最高有效位,a[3] 是最低有效位。

【讨论】:

  • 如果 a[0]=a[1]=a[2]=a[3]=0 则不会打印任何内容。
  • @Alex,是的,我知道。应该做 {} while();
  • 不幸的是,使用 64 位算法,这是不可用的。 ...虽然我想我可以将问题重铸为 8 个 16 位值,这将允许我在使用 64 的地方使用 32 位算术,但需要双倍的操作数。
  • @Silverhalide:您使用的是哪种古老的 C 编译器? (AFAIK 2003 年的最后一个 C++ 标准没有 long long,但 1999 年的当前 C 标准有它,是不是您将代码编译为 C++?)。
  • @Silverhalide,没问题,将其转换为仅使用 32 位甚至仅使用 16 位算术很简单,只是数组元素分别为 16 位或 8 位,并且该数组将包含 8 或 16 个元素。我将在一分钟内添加一个只有 32 位算术的版本。
【解决方案3】:

一种缓慢但简单的方法是使用减法仅打印从最高有效到最低有效的数字。基本上你需要一个函数来检查x &gt;= y 和另一个计算x -= y 的函数。 然后你可以开始计算你可以减去多少次 10^38(这将是最重要的数字),然后你可以减去多少次 10^37 ... 直到你可以减去多少次 1。

以下是该方法的完整实现:​​

#include <stdio.h>

typedef unsigned ui128[4];

int ge128(ui128 a, ui128 b)
{
    int i = 3;
    while (i >= 0 && a[i] == b[i])
        --i;
    return i < 0 ? 1 : a[i] >= b[i];
}

void sub128(ui128 a, ui128 b)
{
    int i = 0;
    int borrow = 0;
    while (i < 4)
    {
        int next_borrow = (borrow && a[i] <= b[i]) || (!borrow && a[i] < b[i]);
        a[i] -= b[i] + borrow;
        borrow = next_borrow;
        i += 1;
    }
}

ui128 deci128[] = {{1u,0u,0u,0u},
                   {10u,0u,0u,0u},
                   {100u,0u,0u,0u},
                   {1000u,0u,0u,0u},
                   {10000u,0u,0u,0u},
                   {100000u,0u,0u,0u},
                   {1000000u,0u,0u,0u},
                   {10000000u,0u,0u,0u},
                   {100000000u,0u,0u,0u},
                   {1000000000u,0u,0u,0u},
                   {1410065408u,2u,0u,0u},
                   {1215752192u,23u,0u,0u},
                   {3567587328u,232u,0u,0u},
                   {1316134912u,2328u,0u,0u},
                   {276447232u,23283u,0u,0u},
                   {2764472320u,232830u,0u,0u},
                   {1874919424u,2328306u,0u,0u},
                   {1569325056u,23283064u,0u,0u},
                   {2808348672u,232830643u,0u,0u},
                   {2313682944u,2328306436u,0u,0u},
                   {1661992960u,1808227885u,5u,0u},
                   {3735027712u,902409669u,54u,0u},
                   {2990538752u,434162106u,542u,0u},
                   {4135583744u,46653770u,5421u,0u},
                   {2701131776u,466537709u,54210u,0u},
                   {1241513984u,370409800u,542101u,0u},
                   {3825205248u,3704098002u,5421010u,0u},
                   {3892314112u,2681241660u,54210108u,0u},
                   {268435456u,1042612833u,542101086u,0u},
                   {2684354560u,1836193738u,1126043566u,1u},
                   {1073741824u,1182068202u,2670501072u,12u},
                   {2147483648u,3230747430u,935206946u,126u},
                   {0u,2242703233u,762134875u,1262u},
                   {0u,952195850u,3326381459u,12621u},
                   {0u,932023908u,3199043520u,126217u},
                   {0u,730304488u,1925664130u,1262177u},
                   {0u,3008077584u,2076772117u,12621774u},
                   {0u,16004768u,3587851993u,126217744u},
                   {0u,160047680u,1518781562u,1262177448u}};

void print128(ui128 x)
{
    int i = 38;
    int z = 0;
    while (i >= 0)
    {
        int c = 0;
        while (ge128(x, deci128[i]))
        {
            c++; sub128(x, deci128[i]);
        }
        if (i==0 || z || c > 0)
        {
            z = 1; putchar('0' + c);
        }
        --i;
    }
}

int main(int argc, const char *argv[])
{
    ui128 test = { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 };
    print128(test);
    return 0;
}

问题文本中的十进制数字变为

11248221411398543556294285637029484152

并且 Python 同意这是正确的值(这当然并不意味着代码是正确的!!!;-))

【讨论】:

  • unsigned 不保证包含 32 位,16 位是它所需的最小值。请改用unsigned long,每个标准至少包含 32 位。
  • 我认为问题是一个非常具体的问题,而不是一般问题。从文本中无符号是 32 位(OP 使用 4 个无符号整数来表示 128 位数字)。
  • 修改它以除以 1,000,000,000(这是 32 位值中可表示的 10 的最大幂)而不是 10 可能不会太难。这将减少操作9 次。
  • @Silverhalide:此方法未使用任何乘法或除法(例如,如果硬件没有这些指令,则很有用)并且除法是作为重复减法实现的(因此我不能使用10000 个基数,除非每个数字组最多循环 10000 个)。我为模方法添加了另一个答案......但是每次迭代的最大位数是 4 而不是 9,因为在计算除法/模运算时,您还必须考虑进位。使用 10000 作为基数允许四位数字并适合 16 位。
【解决方案4】:

同样的事情,但使用 32 位整数运算:

#include <stdio.h>

unsigned short a [] = { 
  0x0876, 0x5421,
  0xfedc, 0xba90,
  0x90ab, 0xcdef,
  0x1234, 0x5678
};

int
main ()
{
  unsigned int d, r;

  do
    {
      r = a [0];

      d = r / 10;
      r = ((r - d * 10) << 16) + a [1];
      a [0] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [2];
      a [1] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [3];
      a [2] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [4];
      a [3] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [5];
      a [4] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [6];
      a [5] = d;

      d = r / 10;
      r = ((r - d * 10) << 16) + a [7];
      a [6] = d;

      d = r / 10;
      r = r - d * 10;
      a [7] = d;

      printf ("%d\n", r);
    }
  while (a[0] || a[1] || a[2] || a[3] || a [4] || a [5] || a[6] || a[7]);


  return 0;
}

【讨论】:

    【解决方案5】:

    您实际上不需要实现长除法。您需要实现乘以 2 的幂和加法。你有四个uint_32。首先将它们中的每一个转换为字符串。将它们分别乘以(2^32)^3(2^32)^2(2^32)^1(2^32)^0,然后将它们相加。您不需要进行基本转换,只需将四个部分放在一起即可。您显然需要确保字符串可以处理最多为UINT_32_MAX*(2^32)^3 的数字。

    【讨论】:

    • +1 自定义以 10 为底的 bignum 库是个好主意,但在以 10 为底的情况下“乘以 2 的幂”并不特别容易。OP 必须进行一般乘法。跨度>
    • 无论如何,必须实现“字符串”的乘法。也就是说,将字符串视为十进制数字并进行乘法
    【解决方案6】:

    假设您有一个快速的 32 位乘法和除法,通过实现 bigint 除法/模 10000,然后使用 (s)printf 输出数字组,可以一次计算 4 位的结果。

    这种方法也很容易扩展到更高(甚至可变)的精度......

    #include <stdio.h>
    
    typedef unsigned long bigint[4];
    
    void print_bigint(bigint src)
    {
        unsigned long int x[8];   // expanded version (16 bit per element)
        int result[12];           // 4 digits per element
        int done = 0;             // did we finish?
        int i = 0;                // digit group counter
    
        /* expand to 16-bit per element */
        x[0] = src[0] & 65535;
        x[1] = src[0] >> 16;
        x[2] = src[1] & 65535;
        x[3] = src[1] >> 16;
        x[4] = src[2] & 65535;
        x[5] = src[2] >> 16;
        x[6] = src[3] & 65535;
        x[7] = src[3] >> 16;
    
        while (!done)
        {
            done = 1;
            {
                unsigned long carry = 0;
                int j;
                for (j=7; j>=0; j--)
                {
                    unsigned long d = (carry << 16) + x[j];
                    x[j] = d / 10000;
                    carry = d - x[j] * 10000;
                    if (x[j]) done = 0;
                }
                result[i++] = carry;
            }
        }
    
        printf ("%i", result[--i]);
        while (i > 0)
        {
            printf("%04i", result[--i]);
        }
    }
    
    int main(int argc, const char *argv[])
    {
        bigint tests[] = { { 0, 0, 0, 0 },
                           { 0xFFFFFFFFUL, 0, 0, 0 },
                           { 0, 1, 0, 0 },
                           { 0x12345678UL, 0x90abcdefUL, 0xfedcba90UL, 0x8765421UL } };
        {
            int i;
            for (i=0; i<4; i++)
            {
                print_bigint(tests[i]);
                printf("\n");
            }
        }
        return 0;
    }
    

    【讨论】:

      【解决方案7】:

      @Alexey Frunze 的方法很简单,但速度很慢。您应该使用上述@chill 的 32 位整数方法。另一个不需要任何乘法或除法的简单方法是double dabble。这可能比chill 的算法慢,但比Alexey 的算法快得多。运行后你会得到一个十进制数的压缩 BCD

      【讨论】:

        【解决方案8】:

        在 github 上有一个开源项目 (c++),它为数据类型 uint265_tuint128_t 提供了一个类。

        https://github.com/calccrypto/uint256_t

        不,我不隶属于那个项目,但我将它用于这样的目的,但我想它也可能对其他人有用。

        【讨论】:

          猜你喜欢
          • 2020-09-16
          • 1970-01-01
          • 2012-09-28
          • 1970-01-01
          • 2012-04-18
          • 2021-03-04
          • 1970-01-01
          • 2019-11-29
          相关资源
          最近更新 更多