C语言 为什么当N>=34时,N!停止溢出32位输出变量?

fquxozlt  于 2023-05-22  发布在  其他
关注(0)|答案(4)|浏览(153)

对于大的数字是否有变量溢出的限制?在一本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。我知道大部分的输出都是溢出的,我对这些大数字的控制非常差。
但是,我想知道是什么限制导致这些零出现。

pu82cl6c

pu82cl6c1#

你抱怨说
N>=34出现以下情况时,N!停止溢出32位输出变量
这意味着在34!之后,结果保持为0。
答案是它不会停止溢出。所发生的是,所示的值,即除法N! / 2^32的余数,从N=34开始变为0,并且永远不会改变。
为了理解它是如何发生的,让我们从一个使用十进制数的例子开始。我们将显示N的结果!使用只有两个数字的显示器:
| 阶乘|实际结果|显示结果|注意事项|
| --------------|--------------|--------------|--------------|
| 一!|1| 01||
| 两个!|2| 02||
| 三!|六|06||
| 四个!|二十四|二十四||
| 五!|一百二十|二十|溢出!|
| 六个!|七二零|二十|溢出!|
| 七个!|五零四零|四十|溢出!|
| 八!|40320|二十|溢出!|
| 九!|362880|八十|溢出!|
| 十个!|3628800| 00|溢出,显示值为00!|
| 十一个!|39916800| 00|溢出,显示值仍为00!|
| 十二个!|479001600| 00|溢出,显示值仍为00!|
| ...|...| 00|显示的值将永远为00|
正如您所看到的,显示在5!处溢出,我们还可以注意到结果是5*2=10的倍数。由于显而易见的原因,所有后续结果都将是5*2=10的倍数,因此它们将有一个尾随0。
但是当我们到达10!时,出现了一个特殊的情况:结果变成了(5^2)*(2^2)=10^2=100的倍数所以,无论我们之后执行什么乘法,显示的值总是00
记住这个信息:当结果开始具有公因子B^N时,我们达到了 *all-0条件 *,其中

  • B是表示(10)的基
  • N是显示的位数(2)

所以,在这种情况下,10^2
同样的推理也可以使用二进制(base-2)表示来完成,但受限于unsigned int的大小。当结果的公因子为B^N = 2^32时,我们将达到全0条件。
什么时候结果会变成2^32的倍数?让我们计算引入2的幂的乘法:

  • 2!将添加因子2(总共2^1)
  • 4!将添加一个因子2^2(总共2^3)
  • 6!将添加因子2(总共2^4)
  • 8!将添加一个因子2^3(总共2^7)
  • 10!将添加因子2(总共2^8)
  • 12!将添加一个因子2^2(总共2^10)
  • 14!将添加因子2(总共2^11)
  • 16!将添加一个因子2^4(总共2^15)
  • 18!将添加因子2(总共2^16)
  • 20!将添加一个因子2^2(总共2^18)
  • 22!将添加因子2(总共2^19)
  • 24!将添加一个因子2^3(总共2^22)
  • 26!将添加因子2(总共2^23)
  • 28!将添加一个因子2^2(总共2^25)
  • 30!将添加因子2(总共2^26)
  • 32!将添加一个因子2^5(总共2^31)
  • 34!将增加一个因子2(总共2^32)

从现在开始,阶乘将始终是2^32的倍数,因此显示的结果,N! / 2^32的余数将始终为0。

odopli94

odopli942#

没有限制,溢出永远不会停止。
只是当n到达34时,fn溢出到数字0。
然后下一个数字将是35 * 0,下一个36 * 35 * 0,依此类推。
没什么神秘的

xoshrz7s

xoshrz7s3#

unsigned intUINT_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。然后使用所需的数据类型。

smtd7mpg

smtd7mpg4#

编辑,正如Eric所指出的,最大的unsigned int值可能最容易通过

printf("%u\n", UINT_MAX);

编辑上方-下方原始答案

fn的类型为unsigned int,将具有最大值。
要找出unsigned int,请尝试

printf("%d",sizeof(unsigned int));

这个命令将告诉你内存中分配给unsigned int的字节数-如果这个数字是n,那么最大的unsigned int值将是2^{8 n}-1 -或者2的8 n次方,然后减去1。
如果你想有一个更大的数字,那么你可以测试其他类型,如long
如果你喜欢得到近似值,那么你可以使用double类型,它将比任何int类型高得多。

相关问题