I was reading this post on tail recursion.
I'll copy the posted solution:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f( a - 1 ); // tail recursion
}
I was wondering, what about if the result depends on several recursive function calls? For example:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f(a -1) + f( a - 1 );
}
Would the code above be optimized by the compiler?
As it stands, tail recursion doesn't apply. But if you look at the end of the second answer on the question you linked to, you can see how to rewrite the function appropriately. Starting with
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f(a-1) + f(a-1);
}
rewrite it as follows:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return 2 * f(a-1);
}
Even now, tail recursion still isn't directly applicable. We need to make sure the return is strictly of the form return f(....)
. Rewrite the function again:
unsigned int f( unsigned int a, unsigned int multiplicative_accumulator = 1 ) {
if ( a == 0 ) {
return multiplicative_accumulator * a;
}
return f(a-1, multiplicative_accumulator * 2 );
}
Now, tail recursion is applicable. This uses a default value for the multiplicative_accumulator (thanks @Pubby) in order that the first call to f
can simply be f(x)
, otherwise you would have to write something f(x,1)
.
A couple of final notes thanks to @SteveJessop:
f(a+1)+f(a+1)
to 2*f(a+1)
because f has no side effects (printing, modifying the heap, that kind of thing). If f did have side effects, that rewrite wouldn't be valid.(2*(2*(2*a))
(or more precisely, (((a+a)+(a+a))+((a+a)+(a+a)))
) whereas the current version is more like (((2*2)*2)*a)
. This is fine, especially for ints, because multiplication is associative and distributive. But this wouldn't be exact equivalent for float
, where you would probably get small rounding discrepancies. With floating point arithmetic, sometimes a*b*c
can be slightly different from c*b*a
.The second function is not tail-recursive and can't be easily replaced with a loop, so most probably the compiler won't do so.
This is not a tail recursion (where the result of the function is the result of a recursive call): there is an operation to do after the recursion (the addition). There is a more complex transformation (which depends on the commutativity of addition) to get a tail recursion: add an auxilliary function with an accumulator:
unsigned int f_helper(unsigned int a, unsigned int acc)
{
if (a == 0) {
return acc;
}
return f_helper(a-1, f(a-1)+acc);
}
unsigned int f(unsigned int a) {
if (a == 0) {
return a;
}
return f_helper(a-1, f(a-1));
}
which you can transform into a loop
unsigned int f_helper(unsigned int a, unsigned int acc)
{
while (a != 0) {
acc += f(a-1);
a = a-1;
}
return acc;
}
unsigned int f(unsigned int a) {
if (a == 0) {
return a;
}
return f_helper(a-1, f(a-1));
}
then put it back into f
unsigned int f( unsigned int a ) {
if (a == 0) {
return a;
}
unsigned acc = f(a-1);
a = a-1;
while (a != 0) {
acc += f(a-1);
a = a-1;
}
return acc;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With