简体   繁体   中英

How to minimize Math calls? (Javascript)

I'm doing a Codewars challenge in which I have to create a function with the parameters a, b & c corresponding to the quadratic equation ax^2 + bx + c = 0 and solve for x. The goal is not only to solve for x, but to minimize the number of spendy Math.sqrt calls. (You also have to return an array with the unique solution(s)).

I came up with a solution:

function solveQuadratic(a, b, c) {
  if ((4*a*c > b*b) || ((a === 0) && (b === 0))) { return undefined;}
  else if (a === 0) {return [-c/b];}
  else {
   var xVals = [];
   var sqrt = Math.sqrt(b*b - 4*a*c);
   xVals.push((-b - sqrt)/2*a);
   xVals.push((-b + sqrt)/2*a);
   if (xVals[0] === xVals[1]) {xVals.pop();}
   return xVals;
  }
}

I got the error message:

You passed the tests using 6 Math.sqrt calls. You should be able to pass these tests with 4 Math.sqrt calls or less.

I thought storing the result of the square root part of the expression in a variable (sqrt) would prevent it from being called more than that one time to evaluate the expression and assign a value to the variable. But that's not the case.

So I have a couple of questions:

  • Is there a way to store a (static) value so that it doesn't need to be reevaluated any time it's used in your code?
  • Is there something obvious I'm missing from this solution besides the fact that it's making too many Math.sqrt calls?

Add a case for when c is zero:

....
var sqrt = c==0?Math.abs(b):Math.sqrt(b*b - 4*a*c);
....

[Edit]

Also, to pass all the tests, your solution needs parenthesis when dividing here:

xVals.push((-b - sqrt)/(2*a));
xVals.push((-b + sqrt)/(2*a));

An easy way is to use memoization . Use a closure to keep a static list of values used so you don't call Math.sqrt for values you've already calculated

var cachingSqrt = (function() {
    var inputs = {};
    return function(val) {
        if (inputs.hasOwnProperty(val)) {
            return inputs[val];
        } else {
            return inputs[val] = Math.sqrt(val);
        }
    }
})();

A generalization of this process would be

function createCachedResults(fn, scope) {
    var inputs = {};
    return function(val) {
        if (inputs.hasOwnProperty(val)) {
            return inputs[val];
        } else {
            return inputs[val] = fn.call(scope, val);
        }
    }
}

cachingSqrt  = createCachedResults(Math.sqrt, Math);

And you could use it like

var cachingSquareRoot = createCachedResults(Math.sqrt, Math);
function solveQuadratic(a, b, c) {
    if ((4*a*c > b*b) || ((a === 0) && (b === 0))) { 
        return undefined;
    }
    else if (a === 0) {
         return [-c/b];
    } else {
        var xVals = [];
        var sqrt = cachingSquareRoot(b*b - 4*a*c);
        xVals.push((-b - sqrt)/2*a);
        xVals.push((-b + sqrt)/2*a);
        if (xVals[0] === xVals[1]) { 
            xVals.pop();
        }
        return xVals;
    }
}

The key is to avoid Math.sqrt(x) when x === 0 and when x === b^2 since the answer is already known. These two situations occur when b^2 === 4ac and when 4ac === 0 , so the code needs to short circuit those two cases to avoid the extra Math.sqrt() calls.

So, all the special cases are:

  • When b^2 - 4ac < 0 or a === 0 && b === 0 which make the answer undefined .
  • When a === 0 (in which case the equation is linear, not quadratic) so the answer is -c / b .
  • When c === 0 which makes 4ac === 0 so it's just -b / a and 0 .
  • When b^2 - 4ac === 0 in which case the answer is just -b / (2 * a)

Using a combination of Ruud's suggestion and a fixed version of Joanvo's suggestion, it will pass with only 4 Math.sqrt() calls with this:

function solveQuadratic(a, b, c) {
    var delta = (b * b) - (4 * a * c), sqrt;
    if ((delta < 0) || ((a === 0) && (b === 0))) {
        return undefined;
    } else if (a === 0) {
        return [-c / b];
    } else if (c === 0) {
        return b === 0 ? [0] : [-b / a, 0];
    } else if (delta == 0) {
        return [-b / (2 * a)];
    } else {
        sqrt = Math.sqrt(delta);
        return [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)];
    }
}

Here's a version that builds on the above version and adds the cache from Juan's answer. In the initial standard test, this reports only one Math.sqrt() operation.

function solveQuadratic(a, b, c) {
    var delta = (b * b) - (4 * a * c), sqrt;
    if ((delta < 0) || ((a === 0) && (b === 0))) {
        return undefined;
    } else if (a === 0) {
        return [-c / b];
    } else if (c === 0) {
        return b === 0 ? [0] : [-b / a, 0];
    } else if (delta == 0) {
        return [-b / (2 * a)];
    } else {
        sqrt = sqrt2(delta);
        return [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)];
    }
}

var sqrt2 = (function() {
    var cache = {0:0, 1:1, 4:2, 9:3};
    return function(x) {
        if (cache.hasOwnProperty(x)) {
            return cache[x];
        } else {
            var result = Math.sqrt(x);
            cache[x] = result;
            return result;
        }
    }
})();

You should add a shortcut for cases where the discriminant is zero.

...
else if (b*b == 4*a*c) return [-b / (2*a)];
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM