【问题标题】:Why is there a difference in run time when datatype used is 'long long int' as compared to 'int'? [duplicate]为什么使用的数据类型为“long long int”与“int”相比,运行时间存在差异? [复制]
【发布时间】:2019-11-01 16:35:56
【问题描述】:

我正在编写一个程序,它涉及的输入范围高达 100 万,当我使用数据类型“int”来处理我的值时,运行时间非常长,程序从未完全执行过,所以我没有能够记下运行时间。 之前的代码;

#include<stdio.h>
int main()
{       
    int n,m,i,maxt=0,maxn;

    for(n=2;n<=1000000;n++){
        m=n;
        i=0;
        for(i=0;m!=1;i++){
            if(m%2==0)
                m=m/2;
            else
                m=(3*m+1);
        }

        if(i>maxt){
            maxt=i;
            maxn=n;
        }
    }

    printf("%d%d",maxn,maxt);

    return 0;
}

但是在处理代码时,我将数据类型从“int”更改为“long long int”,令人惊讶的是,运行时间急剧下降(毫秒),谁能解释这背后的原因是什么? 后面的代码;

#include<stdio.h>
int main()
{       
    long long int n,m,i,maxt=0,maxn;

    for(n=2;n<=1000000;n++){
        m=n;
        i=0;
        for(i=0;m!=1;i++){
            if(m%2==0)
                m=m/2;
            else
                m=(3*m+1);
        }

        if(i>maxt){
            maxt=i;
            maxn=n;
        }
    }

    printf("%lld%lld",maxn,maxt);

    return 0;
}

【问题讨论】:

  • 你能提供运行时间的确切数字吗?请包括您使用的编译器的版本,以及您可能使用的任何编译器标志。
  • @Apples 在第一种情况下运行时间非常长,即使等待超过 5 分钟,输出仍然没有计算出来。在第二种情况下,它需要几毫秒。我正在使用内置的 ubuntu 编译器。
  • 您能强调一下这两个版本的不同之处吗?它们看起来和我一模一样,在这两种情况下都使用long long int
  • 您是否使用相同的编译器标志编译两个代码示例?如此大的差异通常是由于错过了优化。您是否使用 -O2 之类的优化标志进行编译?
  • @Apples 根本没有

标签: c types


【解决方案1】:

您正在计算Collatz conjecture。对于某些数字n 作为输入,m 可以变得非常大。如果m 大于 231,使用正常的 int,你会得到一个负数。更明确地说:当 m >= 231m 32 一个有符号的 32 位值将被解释为一个负数:计算机仅使用 32 位时看不到这种差异。

m 的负数陷入无限循环,永远不会达到m == 1 结束条件。因此,需要一个 64 位的 int 类型。在维基百科页面上,显示了 3 个不同的负数之间的循环,例如 m=-1 变为 m=-2 再次变为 m=-1 在一个永无止境的循环中。

m 第一次大于 231 是针对n=113383,其中m 达到2482111348

进一步澄清:问题不在于n,而在于以下循环中的m

    m=n;
    for(i=0;m!=1;i++){
        if(m%2==0)
            m=m/2;
        else
            m=(3*m+1);
    }

对于每个n,此循环会执行多次。 m 从获取 n 的值开始,例如 113383。在这种情况下,经过 120 步后,m 达到 2482111348,它太大了,不再适合 32 位有符号整数。在大多数现代处理器上,2482111348 的表示形式与 -1812855948 相同。现在循环继续以负值继续。过了一会儿,它进入一个无限循环,总是重复相同的 18 个数字 -17、-50、-25、...、-34、-17。并且永远不会达到停止 for 循环所需的条件 m==1

【讨论】:

    【解决方案2】:

    这是对您的代码的小修改,适用于 gcc

    #include<stdio.h>
    #include<stdlib.h>
    
    void overflow()
    {
        fprintf(stderr, "Overflow\n");
        exit(1);
    }
    
    int main()
    {       
        int n,m,i,maxt=0,maxn;
    
        for(n=2;n<=1000000;n++){
            m=n;
            i=0;
            for(i=0;m!=1;i++){
                if(m%2==0)
                    m=m/2;
                else {
                    int m_prev = m;
    
                    // Replacing m = (3*m+1) with operations that checks for 
                    // overflow
                    if(__builtin_mul_overflow(m,3,&m)) {
                        printf("%d\n", m_prev);
                        printf("%d\n", INT_MAX);
                        overflow();
                    }
                    if(__builtin_add_overflow(m,1,&m))
                        overflow();
                }
            }
            if(i>maxt){
                maxt=i;
                maxn=n;
            }
        }
    
        printf("%lld%lld",maxn,maxt);
    
        return 0;
    }
    

    如果发生溢出,它将打印“溢出”并退出。这就是正在发生的事情。发生的情况是 3*m+1 的结果太大,int 无法容纳,导致溢出。

    您可以在此处阅读有关这些 gcc 函数的信息:https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html

    【讨论】:

    • 但是为什么它面临溢出导致 int(4byte) 可以保存那些最大值为 100 万的值...
    • @AbhishekGuru:问题不在于max,问题在于m - 您正试图将值存储到m 中,但该值太大了。有符号整数溢出的行为是未定义的,但结果是存储在m 中的值不再有用或不可预测,并且您的算法因此而脱轨。
    • @AbhishekGuru :当操作的结果不能由分配结果的数据类型表示时。例如,如果您有一个值为 127 的 int8_t 类型,然后加 1,则结果 128 不可表示,分配的值将为 -128。在您的情况下,溢出和错误结果意味着 m != 1 保持为真,直到错误发生为止。
    • @AbhishekGuru :当 JohanC 的编辑答案清楚地表明发生溢出的位置时,您为什么还要断言。
    • @AbhishekGuru:因为您计算m 的值与n 的值不同,这种差异意味着m 中的值比@987654334 中的值增长得更快@,并且在某些时候,该值超过了常规签名 int 可以表示的值。
    猜你喜欢
    • 1970-01-01
    • 2021-01-13
    • 2016-04-15
    • 2020-04-11
    • 2019-05-15
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    相关资源
    最近更新 更多