试图用C计算阿姆斯壮数:程序打印000 .....并挂起

时间:2016-08-20 17:31:53

标签: c

我编写了一个C程序,用于查找用户定义范围内的所有Armstrong数字。没有编译错误,也没有逻辑错误,但是在运行Turbo C编译器生成的程序时,它以某种方式打印000000 ....并挂起。下面是编写的代码。

#include <conio.h>
#include <stdio.h>

void main() {
    int n1, n2, sum, n_s;
    printf("Enter the lower limit: ");
    scanf("%d", &n1);
    fflush(stdin);
    printf("\nEnter the upper limit: ");
    scanf("%d", &n2);
    while (n1 <= n2) {
        n_s = n1;
        sum = 0;
        while (n1 != 0) {
            n1 = n1 % 10;
            sum += n1 * n1 * n1;
            n1 = n1 / 10;
        }
        if (sum == n_s) {
            printf("%d", n_s);
        }
        n1++;
    }
    getch();
}

我不知道哪里可能出错了。

3 个答案:

答案 0 :(得分:2)

您需要添加两个临时变量。

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int n1, n2, sum, tmp, digit;

  printf("Enter the lower limit: ");
  scanf("%d", &n1);
  printf("\nEnter the upper limit: ");
  scanf("%d", &n2);

  while (n1 <= n2) {
    sum = 0;
    tmp = n1;
    while (tmp != 0) {
      digit = tmp % 10;
      sum += digit * digit * digit;
      tmp = tmp / 10;
    }
    if (sum == n1) {
      printf("%d\n", n1);
    }
    n1++;
  }
  exit(EXIT_SUCCESS);
}

问题:不计算所有阿姆斯壮数(obligatory OEIS link)只有三位数。要计算所有n-narcissistic数字,您需要知道小数位数并相应地计算功率。作为简短的草图:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// ALL CHECKS OMMITTED!

int decimal_digits(int number)
{
  // actually wrong, but we don't use negative numbers here
  // and can safely set log(0) = 1
  if (number <= 0) {
    return 1;
  }
  return (int) (floor(log10(number)) + 1);
}

int main()
{
  int n1, n2, sum, tmp, digit, dec_digits;

  printf("Enter the lower limit: ");
  scanf("%d", &n1);
  printf("\nEnter the upper limit: ");
  scanf("%d", &n2);

  while (n1 <= n2) {
    sum = 0;
    dec_digits = decimal_digits(n1);
    tmp = n1;
    while (tmp != 0) {
      digit = tmp % 10;
      sum += (int) (floor(pow((double) digit, (double) dec_digits)));
      tmp = tmp / 10;
    }
    if (sum == n1) {
      printf("%d\n", n1);
    }
    n1++;
  }
  exit(EXIT_SUCCESS);
}

但这只是一个草图!很多丑陋的演员和更多关于环境的假设等等!

很快发现阿姆斯壮的数字高达1741725,但其余时间需要几分钟(5分钟后仍然是146511208)。有很多优化空间。

编辑在两次实验中找到了一些时间(obligatory XKCD)。

以上代码需要大约10.5分钟才能找到前34个阿姆斯特朗号码(下限= 0,上限= 912985154)。如果我们从数学库中删除函数并自行滚动,我们可以节省超过一半的运行时间。

#include <stdio.h>
#include <stdlib.h>

        // ALL CHECKS OMMITTED!

int decimal_digits(int number);
    /*
     * If you know for sure that e.g.: sizeof(unsigned long)*CHAR_BITS = 32
     * and sizeof(unsigned long long)*CHAR_BITS = 64 you can replace
     * the inclusion of stdint.h with the following type definitions
     * 
     * For 32 bit x86:
     * typedef unsigned int            uint32_t;
     * typedef unsigned long long int  uint64_t;
     * 
     * For 64 bit x86:
     * typedef unsigned int            uint32_t;
     * typedef unsigned long int       uint64_t;
     */
#include <stdint.h>
uint64_t own_pow(uint32_t base, uint32_t exponent);
uint32_t own_ilogb(uint32_t base, uint32_t n);

int decimal_digits(int number)
{
  // actually wrong, but we don't use negative numbers here
  // and can safely set log(0) = 1
  if (number < 10) {
    return 1;
  }
  return (int) (own_ilogb(10,(uint32_t) number) + 1);
}


uint64_t uipow(uint32_t base, uint32_t exponent)
{
  uint64_t power = 1;
  uint64_t big_base = (uint64_t) base;
  while (exponent) {
    if (exponent % 2 == 1) {
      power *= big_base;
    }
    exponent >>= 1;
    big_base *= big_base;
  }
  return power;
}

uint32_t own_ilogb(uint32_t base, uint32_t n)
{
  uint32_t low = 0, big_low = 1, high = 1, mid, big_mid;

  uint64_t big_high = base;

  // interval reduction (more useful for big-integers)
  while (big_high < n) {
    low = high;
    big_low = big_high;
    high <<= 1;
    big_high *= big_high;
  }
  // the actual bisection
  while ((high - low) > 1) {
    mid = (low + high) >> 1;
    big_mid = big_low * uipow(base, mid - low);
    if (n < big_mid) {
      high = mid;
      big_high = big_mid;
    }
    if (n > big_mid) {
      low = mid;
      big_low = big_mid;
    }
    if (n == big_mid)
      return mid;
  }
  if (big_high == n) {
    return high;
  }
  return low;
}


int main()
{
  int n1, n2, sum, tmp, digit, dec_digits;

  printf("Enter the lower limit: ");
  scanf("%d", &n1);
  printf("\nEnter the upper limit: ");
  scanf("%d", &n2);

  while (n1 <= n2) {
    sum = 0;
    dec_digits = decimal_digits(n1);
    tmp = n1;
    while (tmp != 0) {
      digit = tmp % 10;
      sum += (int) uipow((uint32_t) digit, (uint32_t) dec_digits);
      tmp = tmp / 10;
    }
    if (sum == n1) {
      printf("%d\n", n1);
    }
    n1++;
  }
  exit(EXIT_SUCCESS);
}

(对于上面列出的范围,在4分7秒内运行)

own_ilogb()的二进制搜索算法看起来很慢,我们可以用

交换它
   // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
   // vid.: http://stackoverflow.com/questions/7365562/de-bruijn-like-sequence-for-2n-1-how-is-it-constructed for an explanation
static const int tab32[32] = {
  0, 9, 1, 10, 13, 21, 2, 29,
  11, 14, 16, 18, 22, 25, 3, 30,
  8, 12, 20, 28, 15, 17, 24, 7,
  19, 27, 23, 6, 26, 5, 4, 31
};

static int ilog2(uint32_t value)
{
  value |= value >> 1;
  value |= value >> 2;
  value |= value >> 4;
  value |= value >> 8;
  value |= value >> 16;
  return tab32[(uint32_t) (value * 0x07C4ACDD) >> 27];
}


int decimal_digits(int number)
{
  double logten2two = 3.3219280948873623478703194294893901759;
  // actually wrong, but we don't use negative numbers here
  // and can safely set log(0) = 1
  if (number < 10) {
    return 1;
  }
  return (int) (ilog2((uint32_t) number) / logten2two + 1.0);
}

这不会节省很多时间 - 它在3分38秒内运行 - 但半分钟并非一无所获。

使用编译器(GCC 4.8.1)优化-O3:3分43秒 有点慢(大约相同,我刚刚使用time并且它不是唯一在这里运行的进程)

外部循环邀请尝试并行方法(这里使用OpenMP来保持简单)

int main()
{
  int n1, n2, sum, tmp, digit, dec_digits;
  int iter;

  printf("Enter the lower limit: ");
  scanf("%d", &n1);
  printf("\nEnter the upper limit: ");
  scanf("%d", &n2);

  #pragma omp parallel for
  for(iter = n1;iter <= n2;iter++) {
    sum = 0;
    dec_digits = decimal_digits(iter);
    tmp = iter;
    while (tmp != 0) {
      digit = tmp % 10;
      sum += (int) uipow((uint32_t) digit, (uint32_t) dec_digits);
      tmp = tmp / 10;
    }
    if (sum == iter) {
      printf("%d\n", iter);
    }
  }
  exit(EXIT_SUCCESS);
}

运行时间为2分15秒(用户:8m11.933s,因为有4个CPU并行工作,而#34;用户&#34;全部添加),但输出当然没有排序。

PS:很多C&amp; P都参与了这篇文章,可能会在这里和那里造成一个或另一个错误。请在下面的评论中告诉我,我可以修理它们。

答案 1 :(得分:1)

完全重写我以前的解决方案。它可以在0.02秒内解决912985153(带-O3优化)在我的系统上无需并行处理。它可以在9秒内达到64位限制(4929273885928088826)。

密钥优化仅测试最小数字集(即避免置换)。并避免复杂的数学。该算法以the code in the OEIS definition of this sequence.

为模型
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#define MORE_THAN_ENOUGH (128)

unsigned long long sort_digits(unsigned long long number) {
    unsigned long long sortedNumber = 0;

    for (size_t i = 0; i < 10; i++) {
        unsigned long long temporary = number;

        while (temporary > 0) {
            int digit = temporary % 10;

            if (digit == i) {
                sortedNumber *= 10;
                sortedNumber += digit;
            }
            temporary /= 10; 
        }
    }

    return sortedNumber;
}

int compare(const void *a, const void *b) {
    unsigned long long x = *(unsigned long long *) a;
    unsigned long long y = *(unsigned long long *) b;

    return (x < y) ? -1 : (y < x) ? : 0;
}

unsigned long long power(unsigned long long number, unsigned exponent) {
    unsigned long long total = 1;

    for (unsigned i = 0; i < exponent; i++) {
        total *= number;
    }

    return total;
}

size_t generate_narcissistic(size_t places, unsigned long long results[]) {
    char digits[places];
    unsigned long long minimum = power(10, places - 1) - 1;
    size_t results_count = 0;

    for (size_t i = 0; i < places; i++) {
        digits[i] = 0;
        }
    digits[places - 1] = 1;

    bool finished = false;

    while (!finished) {
        unsigned long long sum = 0, number = 0;

        for (size_t i = 0; i < places; i++) {
            number *= 10;
            number += digits[i];
            sum += power(digits[i], places);
        }

        if (sum > minimum) {
            unsigned long long sorted = sort_digits(sum);
            if (sorted == number) {
                results[results_count++] = sum;
            }
        }

        for (int i = places - 1; i >= 0; i--) {
            digits[i] += 1;

            if (digits[i] <= 9) {
                break;
            }

            if (i == 0) {
                finished = true;
                break;
            }

            for (int j = i - 1; j >= 0; j--) {
                if (digits[j] != 9) {
                    digits[i] = digits[j] + 1;
                    break;
                }
            }
        }
    }

    if (results_count != 0) {
        qsort(results, results_count, sizeof(unsigned long long), &compare);;
    }

    return results_count;
}

int main(int argc, char *argv[]) {
    unsigned long long n0, n1, n2, narcissistic[MORE_THAN_ENOUGH];

    if (argc > 1) {
        n1 = strtoull(argv[1], NULL, 0);
    } else {
        printf("Enter the lower limit: ");
        scanf("%llu", &n1);
    }

    if (argc > 2) {
        n2 = strtoull(argv[2], NULL, 0);
    } else {
        printf("Enter the upper limit: ");
        scanf("%llu", &n2);
    }

    char scratch[MORE_THAN_ENOUGH];

    size_t lower_limit = sprintf(scratch, "%llu", n1);
    size_t upper_limit = sprintf(scratch, "%llu", n2);

    for (size_t places = lower_limit; places <= upper_limit; places++) {
        size_t count = generate_narcissistic(places, narcissistic);

        for (size_t i = 0; i < count; i++) {
            n0 = narcissistic[i];

            if (n0 >= n1 && n0 <= n2) {
                printf("%llu\n", n0);
            }
        }
    }

    return 0;
}

我在代码中一直要小心,如果你有128位的unsigned long long,并且有足够的耐心,你应该能够达到最大的自恋数字。

128 BIT UPDATE

我接受了@ chqrlie的建议并制作了上面使用gcc / clang 128位整数的修改版本。这使得程序在运行三天半之后可以达到基数为10的第88个和最后一个自恋数。(模拟的128位整数比硬件64位整数慢。)具体更改:

定义了以下内容,并将所有unsigned long long声明替换为uint128_t

typedef unsigned __int128 uint128_t;

从此SO question about how to print uint128_t numbers抓取uint128_to_str()uint128_to_str_iter()。由于printf不会直接处理它们,因此这些例程会将uint128_t数字转换为您可以打印的字符串。最后,我简化了main()例程,因此我无需处理任何其他数字转换 - 只是简单地计算:

uint128_t n0, narcissistic[MORE_THAN_ENOUGH];

for (size_t places = 1; places < 40; places++) {
    size_t count = generate_narcissistic(places, narcissistic);

    for (size_t i = 0; i < count; i++) {
        n0 = narcissistic[i];

        printf("%s\n", uint128_to_str(n0));
    }
}

答案 2 :(得分:1)

以下是使用附加功能的cdlane代码的改进版本 优化。它可以在 1.2秒中解决最多912985153 (在我的笔记本电脑上进行clang -O3优化)没有并行 处理

额外的优化是:

  • 以递增方式更新字符串表示,而不是重复调用sprintf

  • 当部分金额对于当前金额太大或太小时碰撞候选人编号。

以下是代码:

#include <stdio.h>
#include <stdlib.h>

unsigned long long next_narcissistic(unsigned long long number, unsigned long long max) {
    static size_t power = 0;
    static unsigned long long powers[10];
    static unsigned long long maxleft[42];  /* enough for 128 bit unsigned long long */
    static unsigned long long scale10[42] = { 1 };

    char string[64];
    size_t length;

    while (number < max) {

        length = sprintf(string, "%llu", number);

        if (length > power) {
            for (size_t i = power; i < length; i++) {
                scale10[i + 1] = scale10[i] * 10;
            }
            for (size_t digit = 0; digit < 10; digit++) {
                unsigned long long total = 1;

                for (size_t j = 0; j < length; j++) {
                    total *= digit;
                }
                powers[digit] = total;
            }
            for (size_t i = 0; i <= length; i++) {
                maxleft[i] = (length - i) * powers[9];
            }
            power = length;
        }

        unsigned long long sum = 0;
        unsigned long long max0 = max < scale10[length] ? max : scale10[length] - 1;

        for (size_t i = 0;;) {
            sum += powers[string[i++] - '0'];

            if (i == length) {
                if (sum == number)
                    return number;

                /* bump to next number and update string */
                number += 1;
                if (number > max0)
                    break;

                for (;;) {  /* i is always > 0 because number <= max0 */
                    i--;
                    sum -= powers[string[i] - '0'];
                    if (++string[i] <= '9')
                        break;
                    string[i] = '0';
                }
                continue;
            }

            if (sum <= number) {
                if (sum + maxleft[i] >= number)
                    continue;
            } else {
                sum -= powers[string[--i] - '0'];
            }

            /* bump to next possible number */
            number += scale10[length - i] - number % scale10[length - i];
            if (number > max0)
                break;

            for (;;) {  /* i is always > 0 because number <= max0 */
                i--;
                sum -= powers[string[i] - '0'];
                if (++string[i] <= '9') {
                    break;
                }
            }
            for (size_t j = i + 1; j < length; j++) {
                string[j] = '0';
            }
        }
    }
    return 0;
}

int main(int argc, char *argv[]) {

    unsigned long long n1, n2;

    if (argc > 1) {
        n1 = strtoull(argv[1], NULL, 0);
    } else {
        printf("Enter the lower limit: ");
        scanf("%llu", &n1);
    }

    if (argc > 2) {
        n2 = strtoull(argv[2], NULL, 0);
    } else {
        printf("Enter the upper limit: ");
        scanf("%llu", &n2);
    }

    for (unsigned long long n = n1; n <= n2; n++) {
        n = next_narcissistic(n, n2 + 1);
        if (n == 0)
            break;
        printf("%llu\n", n);
    }

    return 0;
}

再运行1分50秒,这些额外的阿姆斯特朗数量达到10 11

4679307774
32164049650
32164049651
40028394225
42678290603
44708635679
49388550606
82693916578
94204591914

理论上, 1 可能达到基数10的最大Armstrong数,115132219018763992565095597973971522401具有128位整数,它仍然需要非常很长一段时间。

编辑其他优化提供了另外100倍的速度因子,并产生了超过10 18 的额外阿姆斯特朗数字:

28116440335967
4338281769391370
4338281769391371
21897142587612075
35641594208964132
35875699062250035

1 虽然最后一个解决方案适合128位,但上述算法实际上会失败,因为它会计算10 40 ,这几乎不会超过128位无符号整数的容量