0

Writing a program in C that uses recursion to determine if a number is prime or not. It works until you try it with a prime number above 9431. Anything higher than that gets a stack overflow error. I was wondering if there was some way to fix this.

I haven't really tried anything other than see at which number it fails at, which varies each time.

//Remove scanf error
#define _CRT_SECURE_NO_WARNINGS

//Preprocessor directives
#include<stdio.h>
#include<stdlib.h>

//Recursion function
int PrimeCheck(int choice, int i)
{
    //Check if integer i is reduced to 1
    if (i == 1)
    {
        return 0;
    }
    else
    {
        //Check to see if number choice is divisible by value i
        if (choice % i == 0)
        {
            return 1;
        }

        //Call the function again but reduce the second variable by 1
        else
        {
            return PrimeCheck(choice, i - 1);
        }
    }
}//End PrimeCheck function

//Main function
main()
{
    //Assign needed variables
    int choice, num;

    //ask for user input
    printf("Please enter a number between 2 and %i:", INT_MAX);
    scanf("%i", &choice);

    //Check for numbers outside the range
    if (choice < 2 || choice > INT_MAX)
    {
        printf("Please try again and enter a valid number.\n");
        system("pause");
        return 0;
    }

    //Call the PrimeCheck "looping" function
    num = PrimeCheck(choice, choice / 2);

    //Display result for the user
    if (num == 0)
    {
        printf("%i is a prime number.\n", choice);
    }

    else
    {
        printf("%i is NOT a prime number.\n", choice);
    }

    system("pause");
}//End main

The output should be "____ is a prime number" or "____ is NOT a prime number" The actual output above 9431 is a stack overflow error.

  • 2
    (a) This program does not include ``, so we would expect `INT_MAX` not to be defined and compilation to fail. Is this the **exact** source code you are compiling? (b) Which compiler you are using, and what switches are you compiling with? – Eric Postpischil Aug 29 '19 at 22:54
  • 4
    Incidentally, it is impossible for `choice > INT_MAX` to evaluate to true, as `choice` is an `int`, and therefore its largest possible value is `INT_MAX`. – Eric Postpischil Aug 29 '19 at 22:54
  • Recursion is a poor choice for this task. However, for 9431, we should only see 4715 calls to the function, and it should not use much stack space per call, at most a few dozen bytes for return address, saved frame pointer, and some miscellaneous data. That should be well within the default stack size, so something is amiss. – Eric Postpischil Aug 29 '19 at 22:56
  • I know it's a poor choice for it. It's for a class assignment and we're not allowed to use ```for()```, ```while()```, or ```do while``` loops. This is the exact source code. It doesn't have a problem with the ```INT_MAX```. I am using Visual Studio 2019 I'm not sure what "Switches I am compiling with" means. I'm relatively new to programming. The ```choice > INT_MAX``` does actually work. if I put a number in that's above 2147483647 it runs the "Please try again" message. Thanks for the quick responses, by the way. Any help is greatly appreciated. – Jonathan Bailey Aug 29 '19 at 23:00
  • Well, I should say that with the number above the ```INT_MAX```, it works only if the number is not prime. I assume because it doesn't have to go back so many times to find a divisor that works. – Jonathan Bailey Aug 29 '19 at 23:03
  • @JonathanBailey it does not on my machine. That statement makes no sense and if it works for you then you copy-pasted something else or your `int` is somehow magically more than 4 bytes. There's not much more to say... – Marco Bonelli Aug 29 '19 at 23:06
  • 2
    Anyway, if you compile with `-O3` (or even `-O2`) GCC will happily optimize that tail call and compile your recursive function into a simple loop. No more stack overflows for recursion :P – Marco Bonelli Aug 29 '19 at 23:10
  • [link](https://imgur.com/a/l7ZX8jL) I don't know why it works for me. – Jonathan Bailey Aug 29 '19 at 23:12
  • 3
    When you enter a numeral for a number exceeding `INT_MAX`, the `scanf` silently fails; it does not put the value of the number in `choice`. It cannot, because `choice` cannot hold such a number. Per C 2018 7.21.6.2 10, when the result cannot be represented in the object, the behavior is undefined. – Eric Postpischil Aug 29 '19 at 23:12
  • @JonathanBailey that's because it overflows and `choice < 2` becomes true. `choice > INT_MAX` will **never** be true (again, if you don't play strange tricks and such). – Marco Bonelli Aug 29 '19 at 23:12
  • It actually works for me, so the code is right. Something with your environment is different – Daniel Nudelman Aug 29 '19 at 23:13
  • 1
    @DanielNudelman the code is very much wrong. The integer overflow just so happens to make it look right since `int` is a signed value, so `2147483648 == -1 < 2`. – Marco Bonelli Aug 29 '19 at 23:14
  • Oh, fair enough. I'm new to this. It just seemed to be working, so I was confused. haha – Jonathan Bailey Aug 29 '19 at 23:15
  • Yeah, @marcoBoneli you're right. If I ```printf(%i, choice)``` before the error statement it prints it as ```-1``` – Jonathan Bailey Aug 29 '19 at 23:19
  • But, is there some way to fix the stack overflow issue? – Jonathan Bailey Aug 29 '19 at 23:21
  • @JonathanBailey no, there is not. A recursive function eats away the stack very fast. As I said, the only thing you can do is compile with optimization (`-O3`) or try to increase the stack size ([see here](https://stackoverflow.com/questions/2279052/increase-stack-size-in-linux-with-setrlimit)). Increasing the stack size will never make it possible to reach 2147483647 though, not unless you have something like 24GiB of RAM. – Marco Bonelli Aug 29 '19 at 23:27
  • Oh. I see. Sorry, I didn't know what the (```-03```) meant. I appreciate the info! And, I removed the error message. I guess it's pointless anyway since you can't get anywhere close to ```INT_MAX``` anyway. – Jonathan Bailey Aug 29 '19 at 23:30
  • According to [this answer](https://stackoverflow.com/questions/2556938/how-to-change-stack-size-for-a-net-program), you can use `EDITBIN.EXE /STACK: file.exe` to change the stack size for your program. However, [Godbolt](https://godbolt.org/z/LbqKg2) shows your program using only 48 bytes of stack per call. Even with Microsoft’s small 1 MiB stack, that should allow numbers up to 80,000 before running out. – Eric Postpischil Aug 29 '19 at 23:31
  • “Switches” are the settings used to compile and link your program. (More generally, ”switches” is used to refer to options passed to programs on the command line.) In Microsoft Visual Studio, you have set various options for compiling your program—various debugging features are enabled or disabled, some optimization level is set, and so on. These options affect how your program is compiled. Without knowing the specific compiler version you used and the specific settings you used, we cannot reproduce the problem. – Eric Postpischil Aug 29 '19 at 23:33
  • 1
    @JonathanBailey word of advice, when dealing with C, "seemed to be working" is not a very good indicator of whether or not the code is actually correct due to a little thing called [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior). – PC Luddite Aug 29 '19 at 23:34
  • 1
    You can probably increase the stack size limit in your runtime environment. This is OS-specific. If you're using some flavor of `csh` then you can type `limit` to see your limits, and `unlimit stacksize` to remove the limit. Of course, you will eventually run into problems, but you will at least avoid any artificial limits on the stack size. – Tom Karzes Aug 30 '19 at 00:48
  • As the OP uses Visual Studio 2019 and tries to look at the output of his program by `system("pause");` he is most probably working on WIndows without a shell. So any advice concerning GCC or *nix does *not* help. If he gets a stack overflow on this simple program that apparently doesn't use so much stack space even compiled without optimization, its stack must be really small. – the busybee Aug 30 '19 at 06:06
  • So, Jonathan, please look into the preferences for your project if there is anything wrong with the stack. You should also be able do run your program in debug mode. Then the debugger should stop when the stack overflow occurs, and you should look into the "call history" or "call stack" (or however it is called, I'm not native English) so see if there is something fishy. – the busybee Aug 30 '19 at 06:08
  • Thank you all. This was all great information for a noob like me. I really, really appreciate it. :) – Jonathan Bailey Aug 31 '19 at 17:57

1 Answers1

0

One help, reduce tests.

PrimeCheck(choice, choice / 2); iterates about choice/2 times when only sqrt(choice) times needed.

Instead of starting at choice/2

PrimeCheck(choice, sqrt(choice));

Better code would avoid small rounding error and integer truncation with

PrimeCheck(choice, lround(sqrt(choice)));

or if you have access to an integer square root function:

PrimeCheck(choice, isqrt(choice));

For 9431, this will reduce stack depth by about a factor of 50 - and speeds the program.


Speed performance tip. Rather than iterate from choice / 2 or sqrt(choice) down to 1. Go up from 2 to sqrt(choice). Non-primes will be detected much faster.


Sample

#include <stdbool.h>
#include <stdio.h>

bool isprimeR_helper(unsigned test, unsigned x) {
  // test values too large, we are done
  if (test > x/test ) {
    return true;
  }
  if (x%test == 0) {
    return false; // composite
  }
  return isprimeR_helper(test + 2, x);
}

bool isprimeR(unsigned x) {
  // Handle small values
  if (x <= 3) {
    return x >= 2;
  }
  // Handle other even values
  if (x %2 == 0) {
    return false;
  }
  return isprimeR_helper(3, x);
}

int main(void) {
  for (unsigned i = 0; i < 50000; i++) {
    if (isprimeR(i)) {
      printf(" %u", i);
    }
  }
  printf("\n");
}

Output

2 3 5 7 11 13 17 19 ... 49991 49993 49999

Implementations notes

Do not use if (test*test > x). Use test > x/test

  • test*test may overflow. x/test will not.

  • Good compilers will see nearby x/test and x%test and compute both effectively as one operation. So if code has x%test, the cost of x/test if often negligible.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256