如何解决为什么N!当 N>=34 时停止溢出 32 位输出变量?
大数的变量溢出有什么限制吗?一本关于阶乘的 C 书中有一个练习。
我的代码:
#include <stdio.h>
int main(void) {
unsigned int n,fn,counter;
//puts("enter nonnegative int");
//scanf("%u",&n);
n = 1;
while (n != 34) {
fn = n;
counter = n - 1;
while (counter > 0) {
fn *= counter;
counter--;
}
printf("%u\n",fn);
n++;
}
}
注释行用于调试。
此代码打印从 n(1) 到 34 ('while(n!=34)'
) 的数字的阶乘,但如果将其增加到 36 之类的值,它将在前 34 个输出后仅打印 0。我知道大部分输出都溢出了,并且对这些大数字的控制非常糟糕。
但是,我想知道导致这些零发生的限制是什么。
解决方法
你抱怨
N!
在 N>=34
意思是,在34!
之后,结果一直保持为0。
嗯,答案是它不会停止溢出。发生的情况是显示的值,即从 N=34 开始的除法 N! / 2^32
的余数变为 0,并且永远不会改变。
为了理解它是如何发生的,让我们从一个使用十进制数的例子开始。我们将显示 N 的结果!使用只有两位数的显示:
阶乘 | 实际结果 | 显示结果 | 注意事项 |
---|---|---|---|
1! | 1 | 01 | |
2! | 2 | 02 | |
3! | 6 | 06 | |
4! | 24 | 24 | |
5! | 120 | 20 | 溢出! |
6! | 720 | 20 | 溢出! |
7! | 5040 | 40 | 溢出! |
8! | 40320 | 20 | 溢出! |
9! | 362880 | 80 | 溢出! |
10! | 3628800 | 00 | 溢出,显示值为00! |
11! | 39916800 | 00 | 溢出,显示的值仍然是00! |
12! | 479001600 | 00 | 溢出,显示值仍然是00! |
.. | .. | 00 | 显示值永远为00 |
如您所见,显示在 5!
处溢出,我们还可以注意到结果是 5*2=10
的倍数。出于显而易见的原因,所有后续结果都将是 5*2=10
的倍数,因此它们的尾随为 0。
但是当我们达到 10!
时,会出现一个特殊情况:结果变成 (5^2)*(2^2)=10^2=100
的倍数,因此,无论我们之后执行的乘法是什么,显示的值始终为 {{ 1}}。
记住这个信息:当结果开始具有 B^N 的公因数时,我们达到了 all-0 条件,其中
-
00
是表示 (10) 的基础 -
B
是显示的位数 (2)
所以,在这种情况下,N
。
同样的推理可以使用二进制 (base-2) 表示来完成,受限于 10^2
的大小。当结果开始具有 unsigned int
的公因数时,我们将达到全 0 条件。
结果什么时候会变成B^N = 2^32
的倍数?让我们计算一下引入 2 的幂的乘法:
-
2^32
将添加因子 2(总共 2^1) -
2!
将添加一个因子 2^2(总共 2^3) -
4!
将添加因子 2(总共 2^4) -
6!
将添加一个因子 2^3(总共 2^7) -
8!
将添加因子 2(总共 2^8) -
10!
将添加一个因子 2^2(总共 2^10) -
12!
将添加因子 2(总共 2^11) -
14!
将添加一个因子 2^4(总共 2^15) -
16!
将添加因子 2(总共 2^16) -
18!
将添加一个因子 2^2(总共 2^18) -
20!
将添加因子 2(总共 2^19) -
22!
将添加一个因子 2^3(总共 2^22) -
24!
将添加因子 2(总共 2^23) -
26!
将添加一个因子 2^2(总共 2^25) -
28!
将添加因子 2(总共 2^26) -
30!
将添加一个因子 2^5(总共 2^31) -
32!
将添加因子 2(总共 2^32)
从现在开始,阶乘将始终是 34!
的倍数,因此显示的结果中,2^32
的余数将始终为 0。
没有限制,溢出永不停止。
只是当 n
到达 34 时,fn
溢出到数字 0。
然后下一个数字将是 35 * 0,下一个 36 * 35 * 0,依此类推。
没什么神秘的。
,unsigned int
、UINT_MAX = 4294967295
类型变量的最大值。
在这个过程中,找到 12!
后得到 overflowed
。为了处理溢出,C 环绕也称为 modulo wrapping
的值。
例如,
unsigned int x = 4294967295;
x += 1;
printf("%u",x)
输出:0
unsigned int x = 4294967295;
x += 2;
printf("%u",x)
输出:1
这意味着无论何时发生溢出,C
都会环绕值并且永远不会溢出!
但是在这种情况下,同时找到 35!在这个过程中的某个地方,模数变为零。因此,在 34!
之后总是 zero
。
因此,零不会出现溢出。
要查找 n!
所需的位,请使用公式 floor(log(n!))+1
。
然后使用所需的数据类型。
编辑,正如 Eric 所指出的,最大的 unsigned int 值可能最容易被显示为
printf("%u\n",UINT_MAX);
***** 上面编辑 - 下面的原始答案 *****
fn
是具有最大值的 unsigned int
类型。
要找出 unsigned int
尝试的最大值
printf("%d",sizeof(unsigned int));
此命令将告诉您分配给 unsigned int
的内存中的字节数 - 如果该数字为 n
,则最大的 unsigned int
值将是
2^{8n}-1 - 或 2 的 8n 次方然后减去 1。
如果您想要更大的数字,那么您可以测试其他类型,例如 long
。
如果您乐于获得近似值,那么您可以使用 double
类型,它比任何 int
类型都要高得多。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。