在C语言中将大的256进制数组以十进制形式打印出来

8

我正在尝试以十进制打印C语言中的无符号字符数组,但我卡住了。为了更好地解释问题,我认为最好用代码来说明:

unsigned char n[3];
char[0] = 1;
char[1] = 2;
char[2] = 3;

我想打印197121。

使用小的256进制数组非常简单。可以简单地计算1 * 256 ^ 0 + 2 * 256 ^ 1 + 3 * 256 ^ 2。

但是,如果我的数组有100个字节,那么这很快就成为了一个问题。在C中没有100个字节大的整数类型,这就是为什么我一开始就将数字存储在无符号字符数组中的原因。

我应该如何高效地以10进制打印出这个数字呢?

我有点迷失。


5
我将担任魔鬼的代言人并问一下,为什么你需要以十进制格式打印出这些巨大的数字?如果是因为人类需要阅读它们,那么人类如何理解如此大的数字(比较、阅读)?如果不是为了人类,那么为什么仍要使用十进制呢? - Peter M
1
混合格式化文本和二进制输出通常不是一个好主意,因此,如果您需要将确切的数字存储在已用于文本的文件中,可能会成为问题。 - Greg Rogers
@Greg 我并不是建议将二进制对象写入文件,而是改变数字呈现为文本的方式。例如,人们可以愉快地阅读数字的十六进制编码。 - Peter M
我只是想在这里添加一条评论。虽然我真的很喜欢Adam Rosenfield的答案,并投了赞成票,但它仍然不是我要找的。这可能是做这件事情最好的方法,但我想自己做。我决定采用的解决方案虽然不理想,但只是简单地将大数除以10,保存余数并重复。我现在正在实现大整数的除法(还没有完成),虽然非常慢,但它会起作用。 - endeavormac
6个回答

8

如果只使用标准C库,没有简单的方法可以实现。你要么需要自己编写函数(不建议),要么使用外部库,如GMP

例如,使用GMP,您可以执行以下操作:

unsigned char n[100];  // number to print

mpz_t num;
mpz_import(num, 100, -1, 1, 0, 0, n);  // convert byte array into GMP format
mpz_out_str(stdout, 10, num);  // print num to stdout in base 10
mpz_clear(num);  // free memory for num

1
耶稣啊,我把StackOverflow搞崩了!重来一次:它可能不完美,但你可以使用双精度(或长双精度)一段时间。你可能会失去一些精度,但对于80%的情况,这真的不重要,因为在我的机器上DBL_MAX是179769313486231570814527423731704356798070567525844996598917476803 157260780028538760589558632766878171540458953514382464234321326889 464182768467546703537516986049910576551282076245490090389328944075 868508455133942304583236903222948165808559332123348274797826204144 723168738177180919299881250404026184124858368(添加空格以避免破坏SO)。 - Chris Lutz
@Nick - 我觉得完整的数字会更有影响力。 - Chris Lutz
在第三行中,为什么大小(第四个参数)会是0? - Mike Caron
@Mike Caron,我忘记了第四个参数,现在已经修复了。 - Adam Rosenfield
@Chris Lutz:在二进制机器值和难以阅读的十进制人类值之间做出妥协,可以使用“0x1.fffffffffffffp+1023”来表示DBL_MAX,以便在文本中既能被机器读取,也能被人类读取。 - jfs
显示剩余2条评论

8
当我看到这个问题时,我决定解决它,但那时我非常忙碌。 上个周末我终于有了一些空闲时间,所以考虑了一下我的待处理挑战。
首先,我建议您考虑上面的回答。我从未使用过GMP库,但我确信它比手工编写的代码更好。 此外,您可能有兴趣分析bc计算器的代码;它可以处理大数,我用它来测试自己的代码。
好的,如果您仍然有兴趣自己编写代码(只支持C语言和标准C库),我可以给您一些东西。
在所有之前,先讲一点理论。在基本的数字理论(模运算级别)中,有一个算法启发了我得出一个解决方案;乘法和幂算法用于求解a^N对m取模的值:
Result := 1;
for i := k until i = 0
    if n_i = 1 then Result := (Result * a) mod m;
    if i != 0 then Result := (Result * Result) mod m;
end for;

其中k是N的二进制表示中数字位数减一,n_i是第i个二进制数字。例如(N为指数):

N = 44 -> 1 0 1 1 0 0

k = 5
n_5 = 1
n_4 = 0
n_3 = 1
n_2 = 1
n_1 = 0
n_0 = 0

当我们进行模块操作时,例如整数除法,我们可能会丢失部分数字,因此我们只需要修改算法以避免丢失相关数据。
以下是我的代码(请注意,这是一个特定情况下的代码,强烈依赖于我的计算机体系结构。基本上我在使用C语言的数据长度,所以请小心,因为我的数据长度可能不同):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>


enum { SHF = 31, BMASK = 0x1 << SHF, MODULE = 1000000000UL, LIMIT = 1024 };


unsigned int scaleBigNum(const unsigned short scale, const unsigned int lim, unsigned int *num);   
unsigned int pow2BigNum(const unsigned int lim, unsigned int *nsrc, unsigned int *ndst);
unsigned int addBigNum(const unsigned int lim1, unsigned int *num1, const unsigned int lim2, unsigned int *num2);

unsigned int bigNum(const unsigned short int base, const unsigned int exp, unsigned int **num);


int main(void)
{
  unsigned int *num, lim;
  unsigned int *np, nplim;
  int i, j;


  for(i = 1; i < LIMIT; ++i)
  {
    lim = bigNum(i, i, &num);

    printf("%i^%i == ", i, i);
    for(j = lim - 1; j > -1; --j)
      printf("%09u", num[j]);
    printf("\n");

    free(num);
  } 

  return 0;
}


/*
  bigNum: Compute number base^exp and store it in num array
  @base: Base number
  @exp: Exponent number
  @num: Pointer to array where it stores big number

  Return: Array length of result number
*/
unsigned int bigNum(const unsigned short int base, const unsigned int exp, unsigned int **num)
{
  unsigned int m, lim, mem; 
  unsigned int *v, *w, *k;


  //Note: mem has the exactly amount memory to allocate (dinamic memory version) 
  mem = ( (unsigned int) (exp * log10( (float) base ) / 9 ) ) + 3;
  v = (unsigned int *) malloc( mem * sizeof(unsigned int) );
  w = (unsigned int *) malloc( mem * sizeof(unsigned int) );

  for(m = BMASK; ( (m & exp) == 0 ) && m;  m >>= 1 ) ;

  v[0] = (m) ? 1 : 0;
  for(lim = 1; m > 1; m >>= 1)
  { 
    if( exp & m )
      lim = scaleBigNum(base, lim, v);

    lim = pow2BigNum(lim, v, w);

    k = v;
    v = w;
    w = k;
  }

  if(exp & 0x1)
    lim = scaleBigNum(base, lim, v);

  free(w);

  *num = v;  
  return lim;
}

/*
  scaleBigNum: Make an (num[] <- scale*num[]) big number operation
  @scale: Scalar that multiply big number
  @lim: Length of source big number
  @num: Source big number (array of unsigned int). Update it with new big number value

  Return: Array length of operation result
  Warning: This method can write in an incorrect position if we don't previous reallocate num (if it's necessary). bigNum method do it for us
*/
unsigned int scaleBigNum(const unsigned short scale, const unsigned int lim, unsigned int *num)
{
  unsigned int i;
  unsigned long long int n, t;


  for(n = 0, t = 0, i = 0; i < lim; ++i)
  {
    t = (n / MODULE);
    n = ( (unsigned long long int) scale * num[i]  );

    num[i] =  (n % MODULE) + t;  // (n % MODULE) + t always will be smaller than MODULE  
  }

  num[i] = (n / MODULE);

  return ( (num[i]) ? lim + 1 : lim );
}


/*
  pow2BigNum: Make a (dst[] <- src[] * src[]) big number operation  
  @lim: Length of source big number
  @src: Source big number (array of unsigned int)
  @dst: Destination big number (array of unsigned int)

  Return: Array length of operation result
  Warning: This method can write in an incorrect position if we don't previous reallocate num (if it's necessary). bigNum method do it for us
*/
unsigned int pow2BigNum(const unsigned int lim, unsigned int *src, unsigned int *dst)
{
  unsigned int i, j;
  unsigned long long int n, t;
  unsigned int k, c;


  for(c = 0, dst[0] = 0, i = 0; i < lim; ++i)
  {
    for(j = i, n = 0; j < lim; ++j)
    {
      n = ( (unsigned long long int) src[i] * src[j] );
      k = i + j;

      if(i != j)
      {
        t = 2 * (n % MODULE);
        n = 2 * (n / MODULE);

        // (i + j)
        dst[k] = ( (k > c) ? ((c = k), 0) : dst[k] ) + (t % MODULE); 
        ++k; // (i + j + 1)
        dst[k] = ( (k > c) ? ((c = k), 0) : dst[k] ) + ( (t / MODULE) + (n % MODULE) ); 
        ++k; // (i + j + 2)
        dst[k] = ( (k > c) ? ((c = k), 0) : dst[k] ) + (n / MODULE);
      }
      else
      {
        dst[k] = ( (k > c) ? ((c = k), 0) : dst[k] ) + (n % MODULE);
        ++k; // (i + j)
        dst[k] = ( (k > c) ? ((c = k), 0) : dst[k] ) + (n / MODULE);
      }

      for(k = i + j; k < (lim + j); ++k)
      {
        dst[k + 1] += (dst[k] / MODULE);
        dst[k] %= MODULE;
      }

    }
  }

  i = lim << 1;
  return ((dst[i - 1]) ? i : i - 1);
}


/*
  addBigNum: Make a (num2[] <- num1[] + num2[]) big number operation
  @lim1: Length of source num1 big number
  @num1: First source operand big number (array of unsigned int). Should be smaller than second
  @lim2: Length of source num2 big number
  @num2: Second source operand big number (array of unsigned int). Should be equal or greater than first

  Return: Array length of operation result or 0 if num1[] > num2[] (dosen't do any op)
  Warning: This method can write in an incorrect position if we don't previous reallocate num2  
*/
unsigned int  addBigNum(const unsigned int lim1, unsigned int *num1, const unsigned int lim2, unsigned int *num2)
{
  unsigned long long int n;
  unsigned int i;

  if(lim1 > lim2)
    return 0;

  for(num2[lim2] = 0, n = 0, i = 0; i < lim1; ++i)
  {
    n = num2[i] + num1[i] + (n / MODULE); 
    num2[i] = n % MODULE;
  }

  for(n /= MODULE; n; ++i)
  {
    num2[i] += n;
    n = (num2[i] / MODULE);
  }

  return (lim2 > i) ? lim2 : i;
}

编译方法:

gcc -o bgn <name>.c -Wall -O3 -lm     //Math library if you wants to use log func

要检查结果,请将直接输出作为bc的输入。以下是一个简单的shell脚本:

#!/bin/bash


select S in ` awk -F '==' '{print $1 " == " $2 }' | bc`;
do
    0;
done;

echo "Test Finished!";

我们拥有一个无符号整型的数组(4个字节),在数组的每个整数中存储一个九位数字(%1000000000UL); 因此num [0]将具有前9位数字,num [1]将具有第10到18位数字,num [2] ... 我们使用传统内存进行工作,但可以通过动态内存进行改进。好的,但是这个数组应该有多长?(或者需要分配多少内存?)使用bc计算器(bc -l带mathlib)我们可以确定一个数字的位数:
l(a^N) / l(10)     // Natural logarith to Logarithm base 10

如果我们知道数字,就知道我们需要多少个整数:
( l(a^N) / (9 * l(10)) ) + 1     // Truncate result

如果您处理的数值为(2^k)^N这样的式子,您可以使用这个表达式进行对数运算:
如果用LaTeX表示为$\log_2{(2^k)^N}=kN$
( k*N*l(2)/(9*l(10)) ) + 1    // Truncate result  

确定整数数组的确切长度。例如:

256^800 = 2^(8*800) ---> l(2^(8*800))/(9*l(10)) + 1 = 8*800*l(2)/(9*l(10)) + 1

值为 1000000000UL(10^9)的常量非常重要。像10000000000UL(10^10)这样的常量不起作用,因为可能会产生未检测到的溢出(尝试使用数字16^16和10^10常量会发生什么),而更小的常量,如1000000000UL(10^8),是正确的,但我们需要保留更多的内存并执行更多步骤。 10^9是32位无符号整数和64位无符号长整数的关键常量。

代码有两部分,乘法(易)和2的幂次方(更难)。乘法只是乘法并扩展整数溢出。它采用数学中的结合律原理来做完全相反的原理,因此如果k(A + B + C),我们想要kA + kB + kC,其中数字将为k * A * 10 ^ 18 + k * B * 10 ^ 9 + kC。显然,kC操作可能会生成一个大于999999999的数字,但永远不会超过0xFFFFFFFFFFFFFFFF。在乘法中,大于64位的数字永远不会出现,因为C是32位无符号整数,而k是16位无符号短整数。在最坏的情况下,我们将拥有这个数字:

k = 0x FF FF;
C = 0x 3B 9A C9 FF;    // 999999999
n = k*C = 0x 3B 9A | 8E 64 36 01;

n % 1000000000 = 0x 3B 99 CA 01;
n / 1000000000 = 0x FF FE;

在Mul kB之后,我们需要从C的上一次乘法中添加0xFFFE(B = kB +(C / module)),以此类推(我们有18位算术偏移量,足以保证正确的值)。
Power更复杂,但本质上是相同的问题(乘法和加法),因此我提供了一些关于代码功率的技巧:
- 数据类型非常重要 - 如果尝试将无符号整数与无符号整数相乘,则会得到另一个无符号整数。使用显式转换来获取unsigned long long int,并且不会丢失数据。 - 总是使用unsigned修饰符,不要忘记它! - 2的幂可以直接修改当前索引的2个索引 - gdb是你的朋友
我开发了另一种将大数相加的方法。这些我没有进行太多测试,但我认为它运行良好。如果它有错误,请不要对我太过苛刻。
...就这样!
PD1:在

中开发。
Intel(R) Pentium(R) 4 CPU 1.70GHz

Data length: 
    unsigned short: 2 
    unsigned int: 4 
    unsigned long int: 4 
    unsigned long long int: 8 

数字如 256^1024 要花费:

real    0m0.059s
user    0m0.033s
sys    0m0.000s

一个循环计算i^i,其中i的范围为1到1024:

real    0m40.716s
user    0m14.952s
sys    0m0.067s

对于像65355^65355这样的数字,计算所需时间非常长。

PD2: 我的回复很晚但我希望我的代码能够有用。

PD3: 抱歉,用英语解释是我最大的缺陷之一!

最后更新:我刚刚想到了一个新的实现方式,使用相同的算法来改善响应并减少内存使用量(我们可以使用unsigned int的完整位数)。秘密:n^2 = n * n = n * (n - 1 + 1) = n * (n - 1) + n。 (我不会写这个新代码,但如果有人感兴趣,可能会在考试后...)


简而言之,但是回答非常好! - toto
3
你怎么知道这是一个不错的答案,如果它对你来说太长以至于你无法阅读? - xian

4

我不知道您是否还需要解决方案,但我写了一篇文章关于这个问题。它展示了一个非常简单的算法,可以用来将任意长的X进制数转换为对应的Y进制数。该算法使用Python编写,但实际上只有几行代码,不使用任何Python魔法。我也需要这样的算法来进行C语言实现,但出于两个原因,我决定使用Python来描述它。首先,Python非常易读,任何理解伪代码的人都能够看懂;其次,由于我是为公司编写此算法,所以不允许我发布C版本。只要看一眼,您就会发现这个问题在一般情况下可以很容易地解决。在C中的实现应该很直接...


+1 这是一篇非常有见地的文章,因为它解决了一般情况。 - mckamey

2

这里有一个可以实现你想要的功能的函数:

#include <math.h>
#include <stddef.h> // for size_t

double getval(unsigned char *arr, size_t len)
{
    double ret = 0;
    size_t cur;
    for(cur = 0; cur < len; cur++)
        ret += arr[cur] * pow(256, cur);
    return ret;
}

我认为这段代码非常易读。只需传递你想要转换的unsigned char *数组和大小即可。请注意,它不会完美地工作-对于任意精度,建议像之前提到的那样查看GNU MP BigNum库。

此外,我不喜欢你按照小端顺序存储数字,所以如果你想将基于256进制的数字存储在大端顺序中,以下是另一个版本:

#include <stddef.h> // for size_t

double getval_big_endian(unsigned char *arr, size_t len)
{
    double ret = 0;
    size_t cur;
    for(cur = 0; cur < len; cur++)
      {
        ret *= 256;
        ret += arr[cur];
      }
    return ret;
}

需要考虑的事情。


0

也许现在提出这个建议有点晚或者不太相关了,但是你能否将每个字节存储为两个十进制数字(或一个百位数)而不是一个256进制的数字呢?如果你还没有实现除法,那么这意味着你只有加法、减法和可能的乘法;这些转换起来应该不会太难。一旦你完成了这个步骤,打印它就很容易了。


0

由于我对其他提供的答案不满意,我决定自己编写另一种解决方案:

#include <stdlib.h>
#define BASE_256 256

char *largenum2str(unsigned char *num, unsigned int len_num)
{
    int temp;
    char *str, *b_256 = NULL, *cur_num = NULL, *prod = NULL, *prod_term = NULL;
    unsigned int i, j, carry = 0, len_str = 1, len_b_256, len_cur_num, len_prod, len_prod_term;

    //Get 256 as an array of base-10 chars we'll use later as our second operand of the product
    for ((len_b_256 = 0, temp = BASE_256); temp > 0; len_b_256++)
    {
        b_256 = realloc(b_256, sizeof(char) * (len_b_256 + 1));
        b_256[len_b_256] = temp % 10;
        temp = temp / 10;
    }

    //Our first operand (prod) is the last element of our num array, which we'll convert to a base-10 array
    for ((len_prod = 0, temp = num[len_num - 1]); temp > 0; len_prod++)
    {
        prod = realloc(prod, sizeof(*prod) * (len_prod + 1));
        prod[len_prod] = temp % 10;
        temp = temp / 10;
    }

    while (len_num > 1) //We'll stay in this loop as long as we still have elements in num to read
    {
        len_num--; //Decrease the length of num to keep track of the current element

        //Convert this element to a base-10 unsigned char array
        for ((len_cur_num = 0, temp = num[len_num - 1]); temp > 0; len_cur_num++)
        {
            cur_num = (char *)realloc(cur_num, sizeof(char) * (len_cur_num + 1));
            cur_num[len_cur_num] = temp % 10;
            temp = temp / 10;
        }

        //Multiply prod by 256 and save that as prod_term
        len_prod_term = 0;
        prod_term = NULL;

        for (i = 0; i < len_b_256; i++)
        {                                                                        //Repeat this loop 3 times, one for each element in {6,5,2} (256 as a reversed base-10 unsigned char array)
            carry = 0;                                                           //Set the carry to 0
            prod_term = realloc(prod_term, sizeof(*prod_term) * (len_prod + i)); //Allocate memory to save prod_term
            for (j = i; j < (len_prod_term); j++)                                //If we have digits from the last partial product of the multiplication, add it here
            {
                prod_term[j] = prod_term[j] + prod[j - i] * b_256[i] + carry;
                if (prod_term[j] > 9)
                {
                    carry = prod_term[j] / 10;
                    prod_term[j] = prod_term[j] % 10;
                }
                else
                {
                    carry = 0;
                }
            }

            while (j < (len_prod + i)) //No remaining elements of the former prod_term, so take only into account the results of multiplying mult * b_256
            {
                prod_term[j] = prod[j - i] * b_256[i] + carry;

                if (prod_term[j] > 9)
                {
                    carry = prod_term[j] / 10;
                    prod_term[j] = prod_term[j] % 10;
                }
                else
                {
                    carry = 0;
                }
                j++;
            }

            if (carry) //A carry may be present in the last term. If so, allocate memory to save it and increase the length of prod_term
            {
                len_prod_term = j + 1;
                prod_term = realloc(prod_term, sizeof(*prod_term) * (len_prod_term));
                prod_term[j] = carry;
            }
            else
            {
                len_prod_term = j;
            }
        }

        free(prod); //We don't need prod anymore, prod will now be prod_term
        prod = prod_term;
        len_prod = len_prod_term;

        //Add prod (formerly prod_term) to our current number of the num array, expressed in a b-10 array
        carry = 0;
        for (i = 0; i < len_cur_num; i++)
        {
            prod[i] = prod[i] + cur_num[i] + carry;
            if (prod[i] > 9)
            {
                carry = prod[i] / 10;
                prod[i] -= 10;
            }
            else
            {
                carry = 0;
            }
        }

        while (carry && (i < len_prod))
        {
            prod[i] = prod[i] + carry;
            if (prod[i] > 9)
            {
                carry = prod[i] / 10;
                prod[i] -= 10;
            }
            else
            {
                carry = 0;
            }
            i++;
        }

        if (carry)
        {
            len_prod++;
            prod = realloc(prod, sizeof(*prod) * len_prod);
            prod[len_prod - 1] = carry;
            carry = 0;
        }
    }

    str = malloc(sizeof(char) * (len_prod + 1)); //Allocate memory for the return string

    for (i = 0; i < len_prod; i++) //Convert the numeric result to its representation as characters
    {
        str[len_prod - 1 - i] = prod[i] + '0';
    }

    str[i] = '\0'; //Terminate our string

    free(b_256); //Free memory
    free(prod);
    free(cur_num);

    return str;
}

这一切的想法都源于简单的数学。对于任何一个256进制的数字,它的十进制表示可以计算为: num[i]*256^i + num[i-1]*256^(i-1) + (···) + num[2]*256^2 + num[1]*256^1 + num[0]*256^0

这可以扩展为: (((((num[i])*256 + num[i-1])*256 + (···))*256 + num[2])*256 + num[1])*256 + num[0]

因此,我们所要做的就是逐步将数字数组的每个元素乘以256,并加上下一个元素,以此类推...这样我们就可以得到十进制数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接