【问题标题】:How do I perform "Millions of Calculations?"如何执行“数百万次计算”?
【发布时间】:2010-10-04 02:36:38
【问题描述】:

我的代码贴在下面。当我运行这个程序时,它一直在计算。我使用的是旧的 Turbo C++ 编译器。这样的程序需要多长时间才能执行?我等了大约 5 分钟,但没有任何输出。

/*The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.

Find the sum of all the primes below two million.
*/
#include<stdio.h>
#include<conio.h>
#define TWO_MILLION 2*1000*1000
int IsPrime(long unsigned int num);
int main()
{
    long unsigned int i,sum=0;
    clrscr();
    for(i=2;i<TWO_MILLION;i++)
    {
        if(IsPrime(i))
        sum+=i;
    }
    gotoxy(25,25);
    printf("%ld",sum);
    getch();
    return 0;
}
int IsPrime(long unsigned int num)
{
    int flag=1;
    long unsigned int i;
    for(i=2;i<num;i++)
    {
        if(num%i==0)
        {
            flag=0;
            break;
        }
    }
    return flag;
}

【问题讨论】:

  • "旧 Turbo C++ 编译器" 1. WHY OH WHY OH WHY OH WHY? 2. 那不是 C++ 问题,不是 C 问题吗?
  • 史诗:#define TWO_MILLION 2*1000*1000
  • 我的大学课程计划已经过时了。
  • 好吧,让它每 100 或 1000 或 10000 次操作输出一些东西。另外,我怀疑你需要一个BigInt 来保存结果而不是溢出你也可以写一个'。每 10 次找到一个素数,但请确保将其重定向到一个文件以保持速度不错。然后分别查看文件的大小/内容。您可能希望仅通过查找 sqrt(N) 来加快素数测试。
  • 我以前从未见过intunsignedlong都在同一个声明中使用过!

标签: c turbo-c


【解决方案1】:

您不是在进行数百万次计算,而是在进行数万亿次计算。

IsPrime 将在 O(n) 时间内运行,也就是说,它将执行 200 万条指令来确定单个数字。做这种事情需要两百万的时间太长了。

要做到这一点,你真的想使用类似的东西:http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes,它可以更有效地确定特定范围内的所有素数。

【讨论】:

  • 实际上,200 万平方只有 4 万亿,远不及千万亿。
  • 是的,Sieve 很酷,但是素数问题的总和已经被问了一百万次,而且那里有一些该死的快速答案。特别是 Sieve 的局限性在于您可以轻松查看前 2M 个数字,但如果您想要前 2M 个素数 - 那么它会变得有点棘手。一个快速的 python 素数生成器是我建议看的东西。扫描整个线程并准备学习新东西。 stackoverflow.com/questions/567222/…
  • @Hamish:我不认为用 Python 编写的生成器对于使用 C 的课程中​​的某个人来说是一个可以接受的答案。此外,这个问题被标记为 C。我们并不特别关心你的语言偏好在这里。
  • @Hamish:只要你能得到至少 200 万个素数,就可以计算出第 n 个素数的上限 - 所以你可以找到该值并将其与您的筛子一起使用。详情请见stackoverflow.com/questions/1042717/…
  • @Billy ONeal,这不是关于一种语言,而是关于素数的一个新概念。如果提问者不懒惰并四处寻找有关素数的其他问题,他会在 SO 上找到很好的答案。我希望对未来的读者有所帮助,而不是急于帮助完成家庭作业,尤其是在过去几十年里,这个问题可能确实存在于数百万计算机科学专业的学生身上。
【解决方案2】:

执行这样的程序需要多长时间?

这完全取决于您的平台。我怀疑你正在执行 ~(200 万)^2 次运算 (~4 万亿次) 计算,很长一段时间。

有更好的方法来执行您正在执行的操作 - 例如,要检查素数,您只需检查数字的平方根,而不是一直检查数字本身。更不用说可能有一个动态编程解决方案可以比这快得多。

【讨论】:

  • 疯狂的是,四万亿模运算(实际上要少得多,因为他至少实现了提前退出)在现代硬件上不一定是不可行的。将 1 GHz GPU 用于解决问题,每个周期执行 256 次操作(保守),您有 2500 亿次操作/秒,或 10-20 秒,给予或接受。
  • @Potatoswatter:x86 肯定不能执行 256 次操作/周期。如果幸运的话,你会在一个周期内获得一个操作(也许 SSE 有一些奇怪的东西,但在那里你最多可以得到 4-8 个操作/周期,而且它也只是浮点数)。也许您指的是一些 RISC 或 VLSI 架构?
  • @Potatoswatter:啊。这就说得通了。 CUDA 或 AMD 的同等产品是否提供整数运算?出于某种原因,我认为这都是单精度浮点...
  • @Billy:不,整数是一流的。没有它们,流控制和数组索引将是一个真正的痛苦,尽管我确信那里有一个架构可以做到这一点。不过,除法的缓慢可能会使我的计算在一个公平的因素上出现错误。 (当然,除法在这里很容易优化,但这里的重点是处理愚蠢的程序。)
  • @Potatoswatter:嗯.. 听起来很不错。要去看看....:P
【解决方案3】:

正如其他人所说,这将需要很长时间。另一种有趣的方法是埃拉托色尼筛法。您可以在以下位置阅读:

http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

基本上,您使用数字 2...2 百万初始化一个数组。尚未处理的最小数字是素数。然后,从数组中删除该数字的所有倍数并继续。它的运行速度会比你现有的快得多。

【讨论】:

  • 我怀疑这个答案没有大量支持的唯一原因是@Winston 的答案基本相同,但大约提前了 4 分钟。
  • 看起来像。在我查看链接并回答之前,我检查了没有人发布它,但我想当时我并不是唯一一个有相同想法的人!
【解决方案4】:

另类答案

gotoxy(25,25);

您是否以文本模式运行程序?如果文本屏幕只有80 x 25,并且如果第25行被其他东西遮挡,那么您可能不会在文本屏幕上看到任何变化。

【讨论】:

  • 很可能第 25 行是用gotoxy(25,24) 访问的,所以他实际上是在屏幕外显示!
  • 25,25 不会把你带到屏幕中间附近的地方吗?
  • @fahad:仅当您的终端长度为 50 行时。大多数终端总共只有 25 行(至少是“标准的”)。
【解决方案5】:

正如其他人所说:检查实施的限制

如果TurboC++ 具有,则那些实现限制在该标头中定义了相应的宏

#include <limits.h>
#include <stdio.h>
int main(void) {
    printf("int goes from %d to %d.\n", INT_MIN, INT_MAX);
    printf("long goes from %ld to %ld.\n", LONG_MIN, LONG_MAX);
    return 0;
}

如果失败,您需要自己“计算”限制。我正在切换到unsigned,因为它们没有溢出问题,我只需要“计算”上限(下限为0)

#include <stdio.h>
int main(void) {
    unsigned u;
    unsigned long lu;

    u = -1; lu = -1;
    printf("unsigned ints go all the way to %u\n", u);
    printf("unsigned longs go all the way to %lu\n", lu);
    return 0;
}

在我的系统上,第一个程序输出

int 从 -2147483648 到 2147483647。
long 从 -9223372036854775808 到 9223372036854775807。

第二个程序输出

无符号整数一直到 4294967295
unsigned longs 一直到 18446744073709551615

【讨论】:

  • 我假设您实际上是从 GCC 之类的东西中生成了该输出,因为 TurboC 和朋友生成 16 位应用程序,这些应用程序(在所有事情中)需要 NTVDM 才能实际执行。 (short 是 16 位,intlong 是 32 位)
  • 是的,我在 64 位 Linux 上构建并运行了它。但是我机器上的结果并不是重要的部分:重要的是 OP 机器上的结果(我以前安装的虚拟机是 DOS 6.2。也许我也会在这里重新安装它
  • 我的 int 比你的小
  • 我希望我的unsigned long longs 是 128 位:...一直到 340282366920938463463374607431768211455(39 位)
【解决方案6】:

除了“史诗”之外,仍然没有关于常量的评论/答案......

#define TWO_MILLION 2*1000*1000

这很糟糕。当您稍后更改该值时,您的名称内容不匹配:

#define TWO_MILLION 5*1000*1000

或者你把它重命名为

#define FIVE_MILLION 5*1000*1000

并且需要在您使用过它的任何地方进行更改。不要在内容之后命名你的常量,这只会把你的幻数变成幻名。以它们的含义命名它们,例如MAX_NUMBER UPPER_LIMIT RANGE_TO_TEST 或任何最适合的。

【讨论】:

  • 常量很少改变。
  • @fahad:那为什么要使用定义而不是在代码中写入数字本身呢?它被认为是糟糕的编程习惯。上面带有 20 多个赞成票的“Epic”评论指的是这样的代码很有可能出现在 thedailywtf.com 上。浏览那里的文章,你会发现足够多的例子。
  • 我认为人们不会反对:#define UPPER_LIMIT 2*1000*1000 // 200 万 然后您可以将 UPPER_LIMIT 更改为任何其他数字,而无需进行普遍的代码更改。跨度>
  • 感谢您解释如何命名常量
【解决方案7】:

您也可以使用筛子方法来执行此操作,这些方法并不比您使用的复杂得多。这个想法是选择前 n 个连续的素数并用它们来构造一个筛子。我在my answer 中对另一个问题进行了讨论(带有证明),Sheldon L. Cooper 在his 中提供了一个实现。我认为他这样做没有得到足够的支持(我已经得到了“很好的答案”,所以也许你可以帮助他。

所以在计算完筛子数后,您只需要测试与筛子对齐且小于n 的平方根的数字的可分性。

【讨论】:

    【解决方案8】:

    这可能需要非常很长时间才能运行。

    添加此以查看您的进度(尽管需要更长的时间):

    for(i=2;i<num;i++)
        {
            if(num%i==0)
            {
                flag=0;
                printf("%d,", num); /* <== show the prime */
                break;
            }
        }
    

    编辑

    正如其他人指出的那样,这是计算素数的最慢方法。也许您的任务的目的是让您查找(并实施)更快的任务?

    【讨论】:

    • 不过时间不会长——大部分时间不会花在实际寻找素数上。 +1
    • -1,别生气。不是答案,只是一个多小时前我的评论的视觉效果。
    • @vol7ron:嗯,什么? SO将它们都列为“11小时前”。即使你是对的,为重复项分发底片也没有意义。
    【解决方案9】:

    您的程序导致整数溢出,您可以使用 long long 来修复它。

    此外,您检查数字是否为素数的算法也不是很好。另一种同样简单的方法是将数字 2 测试为数字的平方根。您只需检查数字的平方根即可确定它是否为素数。

    【讨论】:

      【解决方案10】:

      一个简单的改变会告诉你你的程序运行得有多快,以及它需要做多少工作。每 100 次迭代就可以轻松打印出您的状态。 (或者您可以将其设置为 1000 或 10000 次迭代。)


      认识到您可以将IsPrime 中的循环速度加倍
      勾选2后,只需要查奇数,可以用i+=2代替i++前进。

      如果您关心速度,为什么要花这么多时间检查偶数? (请注意,一旦开始只做奇数,您还需要将输出测试更改为奇数)

      您也可以在main加倍循环速度,同时避免那里的偶数。 (再一次,你必须特例 2,然后使用 i+=2,从 3 开始得到 3、5、7、9....)

      通过使IsPrime 中的循环运行速度提高一倍,并使main 中的循环运行速度提高一倍,这将使您的程序运行速度4X。 (如果之前需要一个小时,现在需要 15 分钟。)


      您可以进行的另一大速度改进是仅将循环运行到sqrt(num),而不是num

      由于我讨厌引入浮点函数,例如 sqrt,因此我建议使用一个近似值,它会在通过 sqrt 边界的 100 次迭代内停止,并且还会定期显示状态更新。

      if (num%2 == 0)
      {
          flag=0;
          return flag;
      }
      
      /* Now that we checked 2, we only need to check odd numbers from now on. */
      for(i=3;i<num;i+=2)
      {
          if (i%101 == 0)
          {
              printf("i is %d out of %d\n", i, num);
              if (i*i > num)
              {
                  break;  /* If you pass the sqrt boundary, quit. */
              }
          }
      
          if(num%i==0)
          {
              flag=0;
              break;
          }
      }
      

      P.S. 我把这段代码放到了一个 C# 项目中(小移植)。 当然,它现在运行在 64 位操作系统上,具有更好的编译器和 2.8GHz CPU。
      不到 20 秒就跑完了。

      【讨论】:

      • 为什么你更喜欢计算指数的平方而不是 n/101 次而不是计算一次 n 的平方根?
      • 问题被标记为 Turbo-C。在那个编译器中,浮点库是独立的,链接它会大大增加可执行文件的大小,甚至限制你使用带有 FPU 的 CPU(是的,在过去,并不是所有的 CPU 都有 FPU)。对于一个 sqrt 操作肯定有一个很好的论据,但我怀疑每 100 条语句格式化的 printf 比简单的整数乘法更消耗 CPU。添加整数乘法更像是“我的风格”,并且相对于同一范围内的其他操作非常便宜。
      • 这个答案优化了事情,但这里的问题实际上是 OP 一开始就使用了错误的算法,而不是他当前的解决方案是该算法的必然缓慢实现。
      • @Billy:我同意,但我回答了所提出的问题。我更愿意说,“你做错了......从头开始重新设计你的算法”,但这对学生没有多大帮助。
      • 这对学生有何帮助?
      猜你喜欢
      • 2014-12-12
      • 2012-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多