Type and vetting
There are a few issues with the code, though minor they would make the function more usable.
Javascript is loosely typed and therefore you should expect that a function's arguments be so as well. If you are passed a string eg factorial("10")
you throw an error. This is not what I would expect from a javascript function.
Though debatable, Infinity
is not the correct result for an input value over 170. It is actually difficult to select an appropriate result for out of range values. I would opt for a RangeError
for both n > 170 and n < 0
The correct numeric return for an argument that can not be coerced into a number is NaN
You can only get the factorial of integers, I would thus expect that the function floor the argument for me.
Good code is efficient in both memory and speed. You can improve the function by removing the iterator variable i
and use the input argument n in its place and use a while loop.
Declare var's at the top of the function.
Rewrite
Thus I would rewrite your code to the following
function factorial (n) {
var accumulator = 1;
if (isNaN(n)) {
return NaN;
}
n |= 0; // this will convert a string to type Number
if (n > 170 || n < 0 ) {
throw new RangeError ("Argument out of range. Arguments range is 0 - 170 inclusive.");
}
while (n > 1) {
accumulator *= n--;
}
return accumulator;
}
Though i prefer a different more compact style.
function factorial (n) {
var accumulator = 1;
if (isNaN(n)) { return NaN }
n |= 0;
if (n > 170 || n < 0 ) { throw new RangeError ("Argument out of range.Arguments range is 0 - 170 inclusive.") }
while (n > 1) { accumulator *= n-- }
return accumulator;
}
Faster via lookup.
What I don't like about this function is its complexity grows in relationship to the value n. As the range of valid input values is small this function would best be implemented as a lookup table. For speed I would also drop the type checking.
Though you add a slight overhead just after parsing the code all subsequent calls will have the same execution time and be many time faster than calculating the value.
const factorial = (() => {
const f = new Float64Array(171);
(()=>{
var i, a = 1;
f[0] = a;
for (i = 1; i < 171; i++) { f[i] = a *= i }
})();
return (n) => n < 0 || n > 170 ? NaN : f[n | 0];
})();
Note on recursion.
Though this function is often used as an example of recursion you should avoid recursion in javascript if you are not in control of the calling code. Javascript has a limited call stack, and Tail call optimisation as yet has not been implemented by Chrome, Edge, Firefox. As you can not determine the depth of the call stack at run time any recursive code can indeterminately throw a RangeError
.