-3

I wrote a C program to find all Armstrong numbers in a user-defined range. There was no compilation error and no logical errors, but upon running program produced by the Turbo C compiler, it somehow prints 000000.... and hangs. Below is the code that was written.

#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();
}

I don't know where I could have possibly gone wrong.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
shanky
  • 1
  • 2
  • "There was no compilation error " - yes there is. This code won't compile, much less run. There is no statement termination after `n1++`. Even if it did compile it is rife with *undefined behavior*. Neither `n1` nor `n2` are ever being set to any determinate values. – WhozCraig Aug 20 '16 at 17:33
  • Always and ever initialise variables before using them in C. This is not VB. – alk Aug 20 '16 at 17:36
  • i am sorry , i actually scanned them with scanf and took user inputs for n1 and n2 but still the problem persists. sorry for not being able to write it in the code snippet – shanky Aug 20 '16 at 17:36
  • 2
    "*Always and ever*" show your *real* code on SO ... ;-) – alk Aug 20 '16 at 17:37
  • 1
    Ignoring the remaining undefined behavior (such as [`fflush(stdin)`](https://stackoverflow.com/questions/2979209/using-fflushstdin)) and lack of error checking, consider what value is in `n1` after `n1=n1%10;`. Now consider the *only* possible value that can result in the followup `n1=n1/10;`. The first guarantees `n1` will be in 0..9. The second will summarily set `n1` to zero; *always* (because `n1` is already only 0..9). In short, you need a temporary for the modulo usage. – WhozCraig Aug 20 '16 at 17:46
  • When you take the remainder of division by 10 (`n1=n1%10;`) and then divide that by 10 (`n1=n1/10;`) that leaves `0`. – Weather Vane Aug 20 '16 at 17:47
  • In your inner `` while`` loop ``n1`` always decrease to 0.. So when ``n_s == sum`` , the printed value ``n1`` is ``0`` . so your output always 0.. There was logical error in your code. – Krishna Aug 20 '16 at 17:51
  • Again a perfect opportunity to use a debugger. Do it. It's fun! :-) – alk Aug 20 '16 at 17:52
  • There is C and then there is Turbo C. – Antti Haapala -- Слава Україні Aug 20 '16 at 17:53
  • thank you all those who observed the printf("%d",n1) in the if clause right after inner while loop. I don't know how it went unchecked. Thanks! – shanky Aug 20 '16 at 18:23
  • Problem. Even after changing printf("%d",n1) to printf("%d",n_s) the output is 11111111111111111......... and hangs, not even ^c works. – shanky Aug 20 '16 at 18:34
  • Turbo C?? Leave that old stuff, get MSVC 2015 community edition (free) or Eclipse CDT + MinGW. – rustyx Aug 20 '16 at 18:57
  • When you say the "compiler somehow returns 000000.... and hangs", do you mean that's that your program does? Compile-time behavior and run-time behavior are two very different things. – Keith Thompson Aug 20 '16 at 21:17
  • @alk disagree, IMO it is better style to not initialize a variable until it is ready for first use (preferably don't declare it until that point either) – M.M Aug 20 '16 at 22:32
  • The question should include what input you provided to produce the unexpected output – M.M Aug 20 '16 at 22:32
  • @M.M: "*... don't declare it until that point either*" I silently implied exactly this. – alk Aug 21 '16 at 06:46

3 Answers3

2

You need to add two temporary variables.

#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);
}

Problem: does not calculate all Armstrong numbers (obligatory OEIS link) only the three digit ones. To compute all n-narcissistic numbers you need to know the number of decimal digits and compute the power accordingly. As a short sketch:

#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);
}

But that is really only a sketch! A lot of ugly casting and even more assumptions about the environment etc.!

Finds the Armstrong numbers up to 1741725 quite quickly but needs a couple of minutes for the rest (still at 146511208 after 5 minutes). There is a lot of room for optimization.

EDIT found some time (obligatory XKCD) for two experiments.

The code above needs about 10.5 minutes to find the first 34 Armstrong numbers (low limit = 0, high limit = 912985154). If we get rid of the functions from the math-library and roll our own we can save more than half of that run-time.

#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);
}

(run in 4 minutes and 7 seconds for the range listed above)

The binary search algorithm for own_ilogb() looks slow, we could exchange that with

   // 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);
}

This does not save a lot of time--it runs in 3 minutes 38 seconds--but half a minute is not nothing.

Using compiler (GCC 4.8.1) optimization -O3 : 3 minutes 43 seconds A bit slower (about the same, I just used time and it was not the only process running here)

The outer loop invites to try a parallel approach (here with OpenMP to keep things simple)

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);
}

That runs in 2 minutes 15 seconds (user: 8m11.933s because 4 CPUs were working at it in parallel and "user" adds all up), although the output is unsorted, of course.

PS: A lot of C&P involved in this post which might have caused one or the other error here and there. Please inform me about the, in the comments below such that I can repair them.

deamentiaemundi
  • 5,502
  • 2
  • 12
  • 20
  • UV for an amazing effort... I wonder what must have been so important to procrastinate and keep slacking off doing this. – chqrlie Aug 20 '16 at 21:17
  • @chqrlie still 30 minutes until the bar opens ;-) – deamentiaemundi Aug 20 '16 at 21:27
  • I couldn't help but put some effort in optimizing this further, and got it down to 1.2 seconds on a single thread. Computing all Armstrong numbers this way is still out of reach. – chqrlie Aug 22 '16 at 11:14
1

A complete rewrite of my previous solution. It can solve to 912985153 in 0.02 seconds (with -O3 optimization) on my system without parallel processing. It can reach the 64 bit limit (4929273885928088826) in just over 9 seconds.

A key optimization is only testing the minimum set of numbers (i.e. shun permutations). And avoid complicated math. The algorithm is modeled on 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;
}

I've tried to be careful in the code such that if you have a unsigned long long of 128 bits, and sufficient patience, you should be able to reach the maximum narcissistic number.

128 BIT UPDATE

I took @chqrlie's advice and made a modified version of the above that uses the gcc/clang 128-bit integers. This allowed the program, after running three and a half days, to reach the 88th and final narcissistic number in base 10. (The emulated 128-bit integers are slower than the hardware 64-bit integers.) Specific changes:

Defined the following and replaced all my unsigned long long declarations with uint128_t:

typedef unsigned __int128 uint128_t;

Grabbed uint128_to_str() and uint128_to_str_iter() from this SO question about how to print uint128_t numbers. Since printf won't handle them directly, these routines convert a uint128_t number to a string that you can print instead. Finally, I simplified the main() routine so I didn't have to deal with any other number conversions -- just simply count up:

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));
    }
}
Community
  • 1
  • 1
cdlane
  • 40,441
  • 5
  • 32
  • 81
  • I couldn't help but put some effort in optimizing this further, and got it down to 1.2 seconds on a single thread. Computing all Armstrong numbers this way is still out of reach. – chqrlie Aug 22 '16 at 11:14
  • @chqrlie, the largest Armstrong may be in reach if someone can run my revised solution with 128 bit integers. – cdlane Aug 23 '16 at 08:02
  • Both `gcc` and `clang` support 128-bit integers on Intel 64-bit platforms. I'm using type `unsigned __int128` and wrote 128 bit specific string conversion functions to pass the 64 bit limit and verify the largest number, but reaching that is still beyond my current implementation in reasonable time. – chqrlie Aug 23 '16 at 12:59
1

Here is an improved version of cdlane's code that uses additional optimizations. It can solve up to 912985153 in 1.2 seconds (with clang -O3 optimization) on my laptop without parallel processing.

The extra optimizations are:

  • updating the string representation incrementally instead of calling sprintf repeatedly

  • bumping the candidate number when the partial sum becomes too large or too small for the current one.

Here is the code:

#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;
}

Running an additional 1m50s produces these extra Armstrong numbers upto 1011:

4679307774
32164049650
32164049651
40028394225
42678290603
44708635679
49388550606
82693916578
94204591914

Also it would be theoretically1 possible to reach the largest Armstrong number for base 10, 115132219018763992565095597973971522401 with 128-bit integers, it would still take a very long time.

EDIT Additional optimisations provide for another 100x speed factor and produce these extra Armstrong numbers upto 1018:

28116440335967
4338281769391370
4338281769391371
21897142587612075
35641594208964132
35875699062250035

1Although the last solution fits in 128 bits, the above algorithm would actually fail because it would compute 1040 which barely exceeds the capacity of 128-bit unsigned integers.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • I've written a new solution that bests this by ~ 60x, reaching our benchmark 912985153 in 0.02 seconds and the 64 bit limit in just over 9 seconds. It did not escape my notice that you up voted the code you didn't use as a starting point but didn't up vote my code which you did reuse. I had no choice but to defend my honor. ;-) – cdlane Aug 23 '16 at 07:59
  • @cdlane: oops, what an oversight on my part! corrected immediately. In the mean time, I did find some other improvements and also get through the first billion in 14ms. Does your method let you compute all Amstrong numbers for base 10? Mine is still too slow too catch them all. – chqrlie Aug 23 '16 at 13:01
  • A much deserved UV for both your advice about unsigned __int128 (I added an update to my answer about how that worked out) and pushing on both of our solutions to go faster. Reminding me that it's a good algorithm, not faster hardware, that really makes the difference. – cdlane Aug 25 '16 at 06:42