我正在尝试编写一个程序,该程序以数字n
作为输入,并将2的结果输出为n
的幂。问题是n
可能很大(最多100,000个)。本质上,我正在尝试为非常大的数字计算pow(2, n);
。
我认为,这样做的方法是将数字存储在数组中,因为没有内置的数字类型可以容纳这么大的值。
数字采用十进制格式(以10为底)。
我使用的是C,而不是C ++,因此无法使用STL向量和其他C ++容器。我也不能使用外部库,例如GMP。我需要在纯C语言中手动实现该算法。
答案 0 :(得分:3)
问题不是要计算2的高幂,而是要将此数字转换为十进制表示形式:
这是一个简单但快速的实现:
#include <stdint.h>
#include <stdio.h>
void print_2_pow_n(int n) {
int i, j, blen = n / 32 + 1, dlen = n / 29 + 1;
uint32_t bin[blen], dec[dlen];
uint64_t num;
for (i = 0; i < blen; i++)
bin[i] = 0;
bin[n / 32] = (uint32_t)1 << (n % 32);
for (j = 0; blen > 0; ) {
for (num = 0, i = blen; i-- > 0;) {
num = (num << 32) | bin[i];
bin[i] = num / 1000000000;
num = num % 1000000000;
}
dec[j++] = (uint32_t)num;
while (blen > 0 && bin[blen - 1] == 0)
blen--;
}
printf("2^%d = %u", n, dec[--j]);
while (j-- > 0)
printf("%09u", dec[j]);
printf("\n");
}
int main() {
int i;
for (i = 0; i <= 100; i += 5)
print_2_pow_n(i);
print_2_pow_n(1000);
print_2_pow_n(10000);
print_2_pow_n(100000);
return 0;
}
输出:
2^0 = 1
2^5 = 32
2^10 = 1024
2^15 = 32768
2^20 = 1048576
2^25 = 33554432
2^30 = 1073741824
2^35 = 34359738368
2^40 = 1099511627776
2^45 = 35184372088832
2^50 = 1125899906842624
2^55 = 36028797018963968
2^60 = 1152921504606846976
2^65 = 36893488147419103232
2^70 = 1180591620717411303424
2^75 = 37778931862957161709568
2^80 = 1208925819614629174706176
2^85 = 38685626227668133590597632
2^90 = 1237940039285380274899124224
2^95 = 39614081257132168796771975168
2^100 = 1267650600228229401496703205376
2^1000 = 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376
2^10000 = 1995063116880758384883742<...>91511681774304792596709376
2^100000 = 9990020930143845079440327<...>97025155304734389883109376
2 10000 有30103个数字,正好是floor(100000 * log10(2))
。它可以在我的旧笔记本电脑上执行33毫秒。
答案 1 :(得分:2)
由于原始问题语句未指定输出基础,因此这是一个玩笑的实现:
#include <stdio.h>
void print_2_pow_n(int n) {
printf("2^%d = 0x%d%.*d\n", n, 1 << (n % 4), n / 4, 0);
}
int main() {
int i;
for (i = 0; i < 16; i++)
print_2_pow_n(i);
print_2_pow_n(100);
print_2_pow_n(100000);
return 0;
}
输出:
2^0 = 0x1
2^1 = 0x2
2^2 = 0x4
2^3 = 0x8
2^4 = 0x10
2^5 = 0x20
2^6 = 0x40
2^7 = 0x80
2^8 = 0x100
2^9 = 0x200
2^10 = 0x400
2^11 = 0x800
2^12 = 0x1000
2^13 = 0x2000
2^14 = 0x4000
2^15 = 0x8000
2^100 = 0x10000000000000000000000000
2^100000 = 0x10...<0 repeated 24998 times>...0
答案 2 :(得分:2)
只需制作一个位数组并设置第n位。然后除以10,就好像位数组是一个小端 编号,然后反向打印余数,以获得n的2的n次方的底数表示。
下面的这个快速程序可以做到,并且给我与bc
相同的结果,所以我认为它可以工作。
打印例程可能需要一些调整。
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
uint_least32_t div32(size_t N, uint_least32_t Z[/*N*/], uint_least32_t X[/*N*/], uint_least32_t Y)
{
uint_least64_t carry; size_t i;
for(carry=0, i = N-1; i!=-1; i--)
carry = (carry << 32) + X[i], Z[i] = carry/Y, carry %= Y;
return carry;
}
void pr10(uint_least32_t *X, size_t N)
{
/*very quick and dirty; based on recursion*/
uint_least32_t rem=0;
if(!X[N?N-1:0]) return;
rem = div32(N,X,X,10);
while(N && !X[N-1]) N--;
pr10(X,N);
putchar(rem+'0');
}
int main(int C, char **V)
{
uint_least32_t exp = atoi(V[1]);
size_t nrcells = exp/32+1;
uint_least32_t *pow = calloc(sizeof(uint_least32_t),nrcells);
if(!pow) return perror(0),1;
else pow[exp/32] = UINT32_C(1)<<(exp%32);
pr10(pow,nrcells);
}
示例运行:
$ ./a.out 100
1267650600228229401496703205376
答案 3 :(得分:1)
已经存在用于此的库。 GNU Multiple Precision Integer library是常用选项。 (但是根据您的编辑,这不是一个选择。您可能仍然会瞥一眼他们中的一些人,以了解他们的工作方式,但这不是必须的。)
如果您想自己滚动,我不建议存储小数位数。如果这样做,则每次要对组件进行算术运算时,都需要在二进制表示形式之间进行转换。最好有一个uint32_t
的链接列表,以及一个符号位。想要读写时,可以从十进制转换为十进制,但是可以用二进制形式进行数学运算。
我将在此处假设链表bignum的实现;您可以根据需要调整算法。
如果您只是计算2的幂,那很容易。它是1,后跟N 0,因此,如果每个块都存储M位,并且您想要表示2^N
,则只有floor(N/M)
个块都为0,然后将1 << (N % M)
存储在最高有效位中。
如果您想以有效的方式对任意基进行求幂,则应使用exponentiation by squaring。这背后的想法是,如果要计算3 ^ 20,则不要乘以3 * 3 * 3 * ... *3。相反,您可以计算3^2 = 3 * 3
。然后3^4 = 3^2 * 3^2. 3^8 = 3^4 * 3^4. 3^16 = 3^8 * 3^8
。您可以随时存储这些中间结果。然后,一旦达到再次平方的程度,将导致产生比您想要的数字更大的数字,您将停止平方并从所拥有的零件中组装最终结果。在这种情况下,3^20 = 3^16 * 3^4
。
此方法以5步而不是20步计算最终结果,并且由于时间是指数的对数,所以指数越大,速度增益越明显。即使计算3 ^ 100000也只需要21乘法。
据我所知,没有一种聪明的乘法方法。您可能可以按照在小学学习的基本长乘法算法的方式来做一些事情,但是在块的层次上:我们更早使用uint32_t
而不是uint64_t的原因是,我们可以将将操作数转换为较大的类型,然后将它们相乘而不会丢失进位溢出。
首先,找到比您的数字小10的最大倍数。
我将效率留给读者练习,但是您可以通过平方运算来求幂,以找到上限,然后减去各种存储的中间值以更快地降至实际值,从而进行管理。比您将其重复除以10的结果。
或者您可以通过重复乘以10来找到数字;不管第一部分如何处理,其余的都是线性的。
但是无论如何,您都有q
这样的q = k * 10, 10 * q > n, q <= n
,您一次只能循环一位小数:
for (; q; q /= 10) {
int digit = n / q; //truncated down to floor(n/q)
printf("%d", digit);
n -= digit * q;
}
在某处文献中可能有一种更有效的方法,但我不熟悉其中一种。但这不是什么大不了的事,只要我们在编写输出时只需要做效率低下的部分即可。无论算法如何,这都是很慢的。我的意思是,打印所有100,000位数字可能需要一到两毫秒。当我们显示供人类消费的数字时,这并不重要,但是如果我们必须在某个地方的循环中等待一毫秒作为计算的一部分,那么这将加起来并变得非常低效。这就是为什么我们从不以十进制表示形式存储数字:通过在内部将其表示为二进制,我们在输入和输出上都进行了效率低下的部分,但是两者之间的速度很快。
答案 4 :(得分:1)
这是一个非常幼稚且效率低下的解决方案。根据要求,数字以十进制数字数组表示。我们通过将数字2与自身重复相加来计算指数2 n :
从e := 2
开始,并重复e := e + e
n次。
要得出digits
数组长度的上限,我们使用以下方法:
void print(int digits[], int length) {
for (int i = length - 1; i >= 0; --i)
printf("%d", digits[i]);
printf("\n");
}
void times2(int digits[], int length) {
int carry = 0;
for (int i = 0; i < length; ++i) {
int d = 2 * digits[i] + carry;
digits[i] = d % 10;
carry = d / 10;
}
}
int lengthOfPow2(int exponent) {
return exponent / 3 + 1;
}
// works only for epxonents > 0
void pow2(int digits[], int length, int exponent) {
memset(digits, 0, sizeof(int) * length);
digits[0] = 2;
for (int i = 1; i < exponent; ++i)
times2(digits, length);
}
int main() {
int n = 100000;
int length = lengthOfPow2(n);
int digits[length];
pow2(digits, length, n);
print(digits, length);
return 0;
}
在类似Unix的系统上,您可以使用来检查固定n的正确性
diff \
<(compiledProgram | sed 's/^0*//' | tr -d '\n') \
<(bc <<< '2^100000' | tr -d '\n\\')
正如已经指出的那样,这种解决方案不是很有效。借助clang -O2
进行编译,在Intel i5-4570(3.2 GHz)上计算2 100'000 花费了8秒。
加快这一步的下一步是反复将您的数字求立方,而不是将其乘以2。即使使用朴素的立方步骤实现,它也应比本答案中提出的实现更快。
如果需要提高效率,则可以使用Karatsuba算法或什至快速傅立叶变换(FFT)之类的方法来实现多维数据集步骤。使用计算方法和FFT,您可以在O(n·log(n))附近计算2 n (由于FFT中的舍入问题,可能会有一个额外的log(log(n))因子) 。
答案 5 :(得分:1)
我无法找到对数复杂度(通过平方求幂)的解决方案,但是我确实设法编写了时间复杂度为O(noOfDigits * pow),noOfDigits为2的朴素实现。 n将为n * log10(2)+1;
我只检查了答案中https://www.mathsisfun.com/calculator-precision.html的前几位,这似乎是正确的。
#include <stdio.h>
#include <math.h>
//MAX is no of digits in 2^1000000
#define MAX 30103
int a[MAX];
int n;
void ipow(int base, int exp,int maxdigits)
{
a[0]=1;
for (;exp>0;exp--){
int b=0;
for(int i=0;i<maxdigits;i++){
a[i]*=base;
a[i]+=b;
b=a[i]/10;
a[i]%=10;
}
}
}
int main()
{
int base=2;
int pow=100000;
n=log10(2)*pow+1;
printf("Digits=%d\n",n);
ipow(base,pow,n);
for(int i=n-1;i>=0;i--){
printf("%d",a[i]);
}
return 0;
}
我还通过平方运算编写了幂运算的代码,但是具有未优化的乘法功能。这似乎比上面的实现要快。
#define MAX 30103
int a[MAX];
int b[MAX];
int z[MAX];
//stores product in x[]; mul of large arrays implemented in n^2 complexity
//n and m are no of digits in x[] and y[]
//returns no of digits in product
int mul(int x[],int y[],int n,int m){
for(int i=0;i<n+m;i++)
z[i]=0;
for(int j=0;j<m;j++){
int c=0;
for(int i=0;i<n+m;i++){
z[i+j]+=x[i]*y[j];
z[i+j]+=c;
c=z[i+j]/10;
z[i+j]%=10;
}
}
for(int i=0;i<n+m;i++){
x[i]=z[i];
}
if(x[n+m-1]==0)
return n+m-1;
return n+m;
}
//stores answer in x[]
int ipow(int base, int exp)
{
int n=1,m=0;
for(int i=0;base>0;i++){
b[i]=base%10;
base/=10;
m++;
}
a[0]=1;
for (;;)
{
if (exp & 1)
n=mul(a,b,n,m);
exp >>= 1;
if (!exp)
break;
m=mul(b,b,m,m);
}
}
int main()
{
int base=2;
int pow=100000;
n=log10(2)*pow+1;
printf("Digits=%d\n",n);
ipow(base,pow);
printf("\n");
for(int i=n-1;i>=0;i--){
printf("%d",a[i]);
}
return 0;
}