49

I want to do a function that will return the factorial of a number in bash

Here's the current code that doesn't work, can anyone tell me what's wrong and how to correct it? I just started learning bash and I don't know that much.

#!/bash/bin
factorial()
{
  let n=$1
  if (( "$n" <= "1" ))
  then return 1
  else
  factorial n-1
  return $n*$?
  fi
  return 0
}
factorial 5
echo "factorial 5 = $?"
FinalDestiny
  • 1,148
  • 3
  • 15
  • 26

10 Answers10

65

There are several syntax and a quite obvious logic one (return 0)

A working version is below:

#!/bin/bash

factorial()
{
    if (( $1 <= 1 )); then
        echo 1
    else
        last=$(factorial $(( $1 - 1 )))
        echo $(( $1 * last ))
    fi
}
factorial 5

You are missing:

  1. return is bad (should use echo)

  2. shbang line (is /bin/bash not /bash/bin)

  3. Can't do arithmetic outside of (( )) or $(( )) (or let, but (( )) is preferred)

Isaac Freeman
  • 308
  • 2
  • 16
Daniel Voina
  • 3,185
  • 1
  • 27
  • 32
  • 41
    Return codes are a single byte, so this breaks for n > 5. To fix this, you will need to change the function to echo a result, rather than set a return code. – tripleee Mar 13 '12 at 11:22
  • 10
    +1, and +9000 to @tripleee. Functions in bash should echo results, not return them. – sorpigal Mar 13 '12 at 11:50
  • 8
    The `$[...]` syntax has been deprecated for decades. Stop using it. – geirha Jun 26 '13 at 22:00
  • you shouldnt use $[ ], it only works on positive whole numbers and negative whole numbers. You should use an external utility like calc to do this kind of work unless you are positive you are not going to be using decimals, which is not supported. $[] is only really useful for storing numbers to work with arrays, return values, loops, and other internals, not for math and etc.. Esp. since no overflow checking is done (division by zero IS trapped). if you must use it dont use $[] use $(()) because $[] will be removed in future versions of bash altogether (probably will still work in posix mode) – osirisgothra Oct 18 '13 at 15:50
  • 1
    @osirisgothra: In mathematics, the factorial of a non-negative integer n, denoted by n!, is the product of all *positive* *integers* less than or equal to n. Your comments are valid though - it is hard to get rid of the idioms I've been accustomed to in the last 20 years... – Daniel Voina Oct 18 '13 at 19:45
  • 2
    @tripleee a byte is 8 bits or 2^8 numbers. Therefore bash `return` range is `0-255` (2^8 - 1). But I agree that return should still be avoided and replaced with echo. – Marko Gresak Nov 09 '14 at 04:05
  • 1
    @maremp Yes. 5! == 125 <= 255; 6! = 720 >> 255 – tripleee Nov 09 '14 at 07:52
  • 1
    Oh sorry @tripleee, I wasn't paying attention to a context of n, I thought you were talking about returning numbers > 5. I only found this thread because I was having problems with executing code after recursive function call. – Marko Gresak Nov 09 '14 at 13:31
  • @DanielVoina -- when you echo'd the returned value of ***last***, why did you not use ***$last***? Why did just ***last*** suffice? I thought you need ***$last*** to get the data from a variable? ***echo $(( $1 * last ))*** – Mo Fatty Oct 31 '21 at 10:09
  • Oh wait, because there is already a ***$*** before the parenthesis? – Mo Fatty Oct 31 '21 at 10:10
18
#!/bin/bash

function factorial() 
{ 
   if (( $1 < 2 ))
   then
     echo 1
   else
     echo $(( $1 * $(factorial $(( $1 - 1 ))) ))
   fi
}

This will work better.

(It works up to 25, anyway, which should be enough to prove the point about recursion.)

For higher numbers, bc would be the tool to use, making the ninth line above:

echo "$1 * $(factorial $(( $1 - 1 )))" | bc

but you have to be a bit careful with bc --

$ factorial 260
38301958608361692351174979856044918752795567523090969601913008174806\
51475135399533485285838275429773913773383359294010103333339344249624\
06009974551133984962615380298039823284896547262282019684886083204957\
95233137023276627601257325925519566220247124751398891221069403193240\
41688318583612166708334763727216738353107304842707002261430265483385\
20637683911007815690066342722080690052836580858013635214371395680329\
58941156051513954932674117091883540235576934400000000000000000000000\
00000000000000000000000000000000000000000

was quite a strain on my poor system!

user1070300
  • 836
  • 9
  • 15
  • 1
    I find your method will invoke as many subshells as the $1, and this is not necessary and very un-efficient. How to avoid the creation of subshells? – TorosFanny Nov 06 '15 at 13:14
  • 2
    @TorosFanny You are correct that this can be rewritten to avoid subshell creation -- there's a good answer here: https://stackoverflow.com/questions/33568055/how-to-stop-bash-from-creating-subshells-when-recursively-call-a-function Thanks for your comment, it helped me learn something new! – user1070300 Nov 06 '15 at 18:17
7

echo-ing a result may be the only way to get a result for n > 5, but capturing the echo'ed result requires a subshell, which means the recursion will get expensive fast. A cheaper solution is to use a variable:

factorial() {
    local -i val=${val:-($1)}
    if (( $1 <= 1 )); then
        echo $val
        return
    fi
    (( val *= $1 - 1 ))
    factorial $(( $1 - 1 ))
}

If you want to be extra sure that val is unset when you start, use a wrapping function:

factorial() {
    local -i val=$1
    _fact() {
        if (( $1 <= 1 )); then
            echo $val
            return
        fi
        (( val *= $1 - 1 ))
        _fact $(( $1 - 1 ))
    }
    _fact $1
}

For comparison:

# My Method
$ time for i in {0..100}; do factorial $(( RANDOM % 21 )); done > /dev/null 

real    0m0.028s
user    0m0.026s
sys     0m0.001s

# A capturing-expression solution
$ time for i in {0..100}; do factorial $(( RANDOM % 21 )); done > /dev/null 

real    0m0.652s
user    0m0.221s
sys     0m0.400s
kojiro
  • 74,557
  • 19
  • 143
  • 201
  • 2
    Sound advice. Still - your measurement would have been even more convincing if you hadn't used random input. For all we know, your method did 100 counts of factorial(1) while the other did 100 counts of factorial(20) :) – LOAS Feb 09 '17 at 13:00
  • I like the use of variable, great example of how and why. – wbg Nov 28 '19 at 06:30
  • if you really doubt that random gives convincing results, it's pretty easy to try the experiment yourself. – kojiro Aug 09 '20 at 11:17
  • +1 for adressing correctly the process and avoiding forks, but a *variable* is not necessary! See [my answer](https://stackoverflow.com/a/68524789/1765658) – F. Hauri - Give Up GitHub Jul 26 '21 at 05:18
2
clear cat

fact()

{

        i=$1
        if [ $i -eq 0 -o $i -eq 1 ]
        then
                echo 1
        else
                f=`expr $i \- 1`
                f=$(fact $f)
                f=`expr $i \* $f`
                echo $f
        fi
}

read -p "Enter the number : " n

if [ $n -lt 0 ]

then

        echo "ERROR"

else

        echo "THE FACTORIAL OF $n : $(fact $n) "
fi
Srini V
  • 11,045
  • 14
  • 66
  • 89
1

Another one implementation using echo instead of return

#!/bin/bash

factorial()
{
        if [ $1 -le 1 ]
        then
                echo 1
        else
                echo $[ $1 * `factorial $[$1-1]` ]
        fi
}
echo "factorial $1 = " `factorial $1`
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Andrii Kovalchuk
  • 4,351
  • 2
  • 36
  • 31
1

Compute factor recursively under

In addition to all answer, I would like to suggest:

Store computed factorials in order to avoid re-compute

Using an array to store computed factorials, could improve a lot your function, if you plan to run this function many times!!

For this, we need to reverse recursion way, in order to store each computed factorial:

declare -ia _factorials=(1 1)
factorial() {
    local -i _req=$1 _crt=${2:-${#_factorials[@]}} _next=_crt+1 \
        _factor=${3:-${_factorials[@]: -1}}
    if [ "${_factorials[_req]}" ]; then
        printf %u\\n ${_factorials[_req]}
    else
        printf -v _factorials[_crt] %u $((_factor*_crt))
        factorial $1 $_next ${_factorials[_next]}
    fi
}

Then

factorial 5
120

Ok, and:

declare -p _factorials
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120")

Then if I do set -x to trace operations and ask for higher value:

set -x
factorial 10
+ factorial 10
+ local -i _req=10 _crt=6 _next=_crt+1 _factor=120
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 720
+ factorial 10 7
+ local -i _req=10 _crt=7 _next=_crt+1 _factor=720
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 5040
+ factorial 10 8
+ local -i _req=10 _crt=8 _next=_crt+1 _factor=5040
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 40320
+ factorial 10 9
+ local -i _req=10 _crt=9 _next=_crt+1 _factor=40320
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 362880
+ factorial 10 10
+ local -i _req=10 _crt=10 _next=_crt+1 _factor=362880
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 3628800
+ factorial 10 11
+ local -i _req=10 _crt=11 _next=_crt+1 _factor=3628800
+ '[' 3628800 ']'
+ printf '%u\n' 3628800
3628800

Where recursions was done from 5 to 10, and factorial 5 was directly used.

declare -p _factorials
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120"
                  [6]="720" [7]="5040" [8]="40320" [9]="362880" [10]="3628800")

Avoid forks: replace echo by setting a variable:

declare -ia _factorials=(1 1)
factorialtovar() {
    local -i _req=$2 _crt=${3:-${#_factorials[@]}} _next=_crt+1 \
        _factor=${4:-${_factorials[@]: -1}}
    if [ "${_factorials[_req]}" ]; then
        printf -v $1 %u ${_factorials[_req]}
    else
        printf -v _factorials[_crt] %u $((_factor*_crt))
        factorialtovar $1 $2 $_next ${_factorials[_next]}
    fi
}

Then

factorialtovar myvar 7 ; echo $myvar
5040

declare -p myvar _factorials 
declare -- myvar="5040"
declare -ai _factorials=([0]="1" [1]="1" [2]="2" [3]="6" [4]="24" [5]="120" [6]="720" [7]="5040")

and

set -x
factorialtovar anothervar 10
+ factorialtovar anothervar 10
+ local -i _req=10 _crt=8 _next=_crt+1 _factor=5040
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 40320
+ factorialtovar anothervar 10 9
+ local -i _req=10 _crt=9 _next=_crt+1 _factor=40320
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 362880
+ factorialtovar anothervar 10 10
+ local -i _req=10 _crt=10 _next=_crt+1 _factor=362880
+ '[' '' ']'
+ printf -v '_factorials[_crt]' %u 3628800
+ factorialtovar anothervar 10 11
+ local -i _req=10 _crt=11 _next=_crt+1 _factor=3628800
+ '[' 3628800 ']'
+ printf -v anothervar %u 3628800
set +x
+ set +x

echo $anothervar
3628800
techno
  • 525
  • 3
  • 13
1

Avoiding forks!

The only answer here addressing correctly the process thread is kojiro's answer, but I dislike using a dedicated variable for this.

For this, we could use arguments to transmit steps or intermediary results:

factorial() { 
        if [ $1 -gt 1 ]; then
            factorial $(( $1 - 1 ))   $(( ${2:-1} * $1 ))
        else
            echo $2
        fi
}

There is no forks and result is passed as 2nd argument.

Avoiding fork (bis)

Then in order to avoid forks, I prefer storing result into a variable instead of using echo (or other printf):

setvarfactorial() { 
    if [ $2 -gt 1 ]; then
        setvarfactorial $1 $(( $2 - 1 )) $(( ${3:-1} * $2 ))
    else
        printf -v "$1" %u $3
    fi
}

Usage:

factorial 5
120

setvarfactorial result 5
echo $result
120

When goal is to store result into a variable, this second method is a lot more system friendly and quicker than using

result=$(factorial 5)
# or same
result=`factorial 5`

Comparison between with or without forks.

There is no variable set, just running 10x factorial 20:

Tested on my old raspberry-pi:

time for i in {1..10};do factorial 20;done|uniq -c
     10 2432902008176640000
real    0m0.376s
user    0m0.159s
sys     0m0.035s


time for i in {1..10};do kojirofactorial 20;done|uniq -c
     10 2432902008176640000
real    0m0.348s
user    0m0.150s
sys     0m0.023s


time for i in {1..10};do danielfactorial 20;done|uniq -c
     10 2432902008176640000
real    0m5.859s
user    0m1.015s
sys     0m1.732s

If kojiro's version seem a little quicker than mine, difference doesn't really matter, but other answer will take more than 10x more time!!

To see of forks are done:

set -x
danielfactorial 5
+ danielfactorial 5
+ ((  5 <= 1  ))
++ factorial 4
++ ((  4 <= 1  ))
+++ factorial 3
+++ ((  3 <= 1  ))
++++ factorial 2
++++ ((  2 <= 1  ))
+++++ factorial 1
+++++ ((  1 <= 1  ))
+++++ echo 1
++++ last=1
++++ echo 2
+++ last=2
+++ echo 6
++ last=6
++ echo 24
+ last=24
+ echo 120
120

vs:

factorial 5
+ factorial 5
+ '[' 5 -gt 1 ']'
+ factorial 4 5
+ '[' 4 -gt 1 ']'
+ factorial 3 20
+ '[' 3 -gt 1 ']'
+ factorial 2 60
+ '[' 2 -gt 1 ']'
+ factorial 1 120
+ '[' 1 -gt 1 ']'
+ echo 120
120
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
  • I've added *setting a variable* to [my answer](https://stackoverflow.com/a/68392176/1848245) – techno Jul 26 '21 at 07:01
1
#!/bin/bash
fact() {
   (( $1 == 0 )) && echo 1 || echo "$(( $1 * $(fact $(($1-1)) ) ))"
}
fact $1
  • There are **nine existing answers** to this question, including a top-voted, accepted answer with over **fifty votes**. Are you _certain_ your solution hasn't already been given? If not, why do you believe your approach improves upon the existing proposals, which have been validated by the community? Offering an explanation is _always_ useful on Stack Overflow, but it's _especially_ important where the question has been resolved to the satisfaction of both the OP and the community. Help readers out by explaining what your answer does different and when it might be preferred. – Jeremy Caney Jan 01 '22 at 00:37
0
#-----------------factorial ------------------------
# using eval to avoid process creation
fac=25
factorial()
{
    if [[ $1 -le 1 ]]
    then
        eval $2=1
    else

        factorial $[$1-1] $2
        typeset f2
        eval f2=\$$2
        ((f2=f2*$1))
        eval $2=$f2

    fi
}
time factorial $fac res 
echo "factorial =$res"
0

To complete @Marc Dechico solution, instead of eval, passing reference is preferable nowadays (bash >= 4.3) :

factorial_bruno() {
    local -i val="$1"
    local -n var="$2"                             # $2 reference
    _fact() {
        if (( $1 <= 1 )); then
            var="$val"
            return
        fi
        ((val*=$1-1))
        _fact $(($1-1))
    }
    _fact "$1"
}
declare -i res
factorial_bruno 20 res
printf "res=%d\n" "$res"

If we compare the time on a 1,000 runs of @kojiro's, @techno's (catching result for them, as after all we want the result), @Marc Dechici's, and my solution, we get :

declare -i res
TIMEFORMAT=$'\t%R elapsed, %U user, %S sys'

echo "Kojiro (not catching result) :"
time for i in {1..1000}; do factorial_kojiro $((i%21)); done >/dev/null
echo "Kojiro (catching result) :"
time for i in {1..1000}; do res=$(factorial_kojiro $((i%21))); done
echo "Techno (not catching result) :"
time for i in {1..1000}; do factorial_techno $((i%21)); done >/dev/null
echo "Techno (catching result, 100% data already cached) :"
time for i in {1..1000}; do res=$(factorial_techno $((i%21))); done
_factorials=(1 1)
echo "Techno (catching result, after cache reset) :"
time for i in {1..1000}; do res=$(factorial_techno $((i%21))); done
echo "Marc Dechico :"
time for i in {1..1000}; do factorial_marc $((i%21)) res; done
echo "This solution :"
time for i in {1..1000}; do factorial_bruno $((i%21)) res; done

Kojiro (not catching result) :
    0.182 elapsed, 0.182 user, 0.000 sys
Kojiro (catching result) :
    1.510 elapsed, 0.973 user, 0.635 sys
Techno (not catching result) :
    0.054 elapsed, 0.049 user, 0.004 sys
Techno (catching result, 100% data already cached) :
    0.838 elapsed, 0.573 user, 0.330 sys
Techno (catching result, after cache reset) :
    2.421 elapsed, 1.658 user, 0.870 sys
Marc Dechico :
    0.349 elapsed, 0.348 user, 0.000 sys

This solution :
    0.161 elapsed, 0.161 user, 0.000 sys

It is interesting to notice that doing an output (echo/printf) in a function and catching the result with res=$(func...) (subshell) is always very expensive, 80% of the time for Kojiro's solution, >95% for Techno one...

EDIT: By adding a cache with similar solution (@techno new solution - the difference is the use of printf -v instead of using variable reference), we can further improve response time if we need to calculate many times the factorial. With integer limit within bash, that would mean we need to calculate many times the same factorial (probably useful for benchmarks like this one :-)

Bruno
  • 580
  • 5
  • 14
  • You could add [my answer](https://stackoverflow.com/a/68392176/1848245) to your comparission! ;-) – techno Jul 26 '21 at 11:14
  • U could use `printf '%u'` instead of `%d` for ***unsigned integer*** – techno Jul 26 '21 at 11:33
  • @techno, I just did :-) But you could have done the job and edit my post, lol... – Bruno Jul 26 '21 at 18:42
  • @techno, I did not see your (better) second solution. Sorry, no time to add it now, feel free to run all tests again and edit my post. – Bruno Jul 26 '21 at 19:24