3

i have written a small function which calculates factorial for a number in C as follows:

int factNnumbers(int n)
{
    if(n == 1)
        return 1;
    else
        return (n*factNnumbers(--n));
}

I call the function shown above as:

factNnumbers(takeInputN());

where the function to take the input (takeInputN) is defined as:

int takeInputN()
{   
    int n;
    printf("\n\nHow many numbers ?? \n ");
    scanf("%d", &n);
    return n;
}

If I change one line in my factorial code as shown below , my program works perfectly. Otherwise with the above code it prints the factorial of the number input -1 (example if number input is 5, it will print the factorial of 4). Why is this happening?.

int factNnumbers(int n)
{
    if(n != 1)
        return (n * factNnumbers(--n));
}
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
abhinavm93
  • 124
  • 17

6 Answers6

6

You are invoking undefined behaviour, that it works in one version is just an accident:

return (n*factNnumbers(--n));

Do you use n first and then decrement it or the other way around? I don't know and neither does the compiler, it's free to do either of them or format your hard drive. Just use n * f(n - 1).

Also, your "working" version does not return for the n==1 case, which is illegal.

kamikaze
  • 1,529
  • 8
  • 18
6

The problem is that you're both reading and modifying n in the same expression:

n * factNumbers(--n)

The evaluation of the n argument to * and of the --n subexpression are unsequenced, which gives your code Undefined Behaviour.

The easiest solution (and also, IMO, more expressive), is to say n - 1 where you mean it:

n * factNumbers(n - 1)

Your "improved" code in the bottom of the question is actually even more wrong. There, you have a control path which will return an unspecified value: a clear no-no.


Note: This answer was written while the question still had a C++ tag, and uses C++ terminology. The end effect in C is the same, but the terminology might be different.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • that's because order of evaluating sub expressions which have no sequence point between them is unspecified? – Giorgi Moniava Mar 25 '16 at 09:10
  • 1
    @GiorgiMoniava Yes, it's the general case of subexpressions evaluation order being unspecified. Only a few explicit exceptions have specified order (function call, comma operator, logic operators etc.) – Angew is no longer proud of SO Mar 25 '16 at 09:13
  • That makes sense. The 2nd version was something i saw randomly here and i tried it. It seems to be working though. Thanks for the quick response! – abhinavm93 Mar 25 '16 at 09:28
  • Another thing that was on my mind, and i hope i make sense with this question, When i debugged the code i noticed the function called itself recursively 5 times ( that means 5 stacks were built in memory if im correct) , so im guessing in the initial stack the value started with --n instead of n and computed factorial for --n? Just curious. – abhinavm93 Mar 25 '16 at 09:31
  • @abhinavm93 *Undefined behaviour is undefined.* You observed one particular execution in a debugger, but an execution which instead called `system("sudo rm -rf /")` would be just as valid, at least as far as the standard is concerned. Reasoning about UB can potentially be a nice exercise about the implementation details of a particular compiler and its optimiser, but that's about it. – Angew is no longer proud of SO Mar 25 '16 at 09:36
2

There are two causes of undefined behavior in your code:

  1. Whether n or --n in n * factNnumbers(--n) will be evaluated first is unspecified.
    See this. You want just n * factNnumbers(n - 1), why decrement? You're not using decremented n afterwards (at least you didn't want to).

  2. You're not returning a value on all control paths, what's going to be returned on n == 1? An indeterminate value that will mess up the whole result.

Community
  • 1
  • 1
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
  • No, it is not just unspecified. Accessing a value that is changed in the same expression where it is accessed is not allowed and leads to undefined behavior. – Jens Gustedt Mar 25 '16 at 09:09
  • @Angew, it may be correct, but it is incomplete. Accessing an object that is changed in the same expression is a severe programming error. – Jens Gustedt Mar 25 '16 at 09:12
  • @Angew What do you mean with using unspecified value is undefined behavior? – Giorgi Moniava Mar 25 '16 at 09:14
  • @GiorgiMoniava Actually, I was not quite correct, at least in C++14 terminology. It appears the evaluations are *unsequenced,* not in an unspecified order; and such unsequenced multiple-use is explicitly called out as undefined. – Angew is no longer proud of SO Mar 25 '16 at 09:22
  • @Angew Hm though the behavior OP sees clearly indicates --n was called first. PS. about using unspecified variable is UB you probably meant that its value is indeterminate, and that's why. PPS Maybe you can add that clarification to your answer. – Giorgi Moniava Mar 25 '16 at 09:23
  • @Angew The c++ tag's been removed. – LogicStuff Mar 25 '16 at 09:26
0

the rule of decrement: X = Y-- first passes the value of I to X and decrements after X = --I first pass and decrements the value decremented X

for your case, you decrement the value of parameter passed as argument to the function factNnumbers. Therefore remedy this error, I invite you to put (n-1) instead of (--n).

embedded
  • 11
  • 1
0

I think it’s better to use the tgamma function from math.h for double precision calculations involving factorials (such as Poisson distribution etc.): tgamma(n+1) will give you factorial(n).

Factorial function is increasing very fast, and this will work also for values too big to fit an integer type.

user1978556
  • 7,277
  • 1
  • 12
  • 3
-1

When n<=1 (or == 1 in your code), the factorial function has to return 1. Futhermore the your --n in you code is false and sould be n-1 as:

function factorial(n)
{
   if (n<=1)
      return 1;
   else
      return n * factorial(n-1);
}
KeyMaker00
  • 6,194
  • 2
  • 50
  • 49