numbers / numbers.js

Advanced Mathematics Library for Node.js and JavaScript
Apache License 2.0
1.76k stars 167 forks source link

Resolve decimal problem #11

Open sjkaliski opened 11 years ago

sjkaliski commented 11 years ago

So I started writing tests for calculus.

Here's an example:

var assert = require('assert');
var numeric = require('../index.js');
var calculus = numeric.calculus;

suite('numeric', function() {

  test('pointDiff should return the derivative at a point, provided function', function(done) {
    var func = function(x) {
      return 2 * x + 2;
    };

    assert.equal(2, calculus.pointDiff(func, 5));
    done();
  });

});

Now, the point derivative of 2x + 2 at any value is 2. The method returns the following: 1.9999999999242843

This is "basically" 2, but it won't pass tests. So I've considered two options:

  1. Handle this decimal stuff
  2. Set an error bound, which the user passes along when including numeric.

I like two more, although it certainly is going to be a hassle to set up. But this way we can have an "acceptable" error bound, which is sustainable and acceptable for the browser, and has minimal effect in performance or large scale operations.

sjkaliski commented 11 years ago

Look at simpsonRecursive() in /numeric/calculus.js. It takes an epsilon argument, which is part of ensuring the result is in a certain error bound.

ethanresnick commented 11 years ago

I think we're going to need to use error bounds, because all the calc functions with limits are going to be a little off. To reliably convert them to the right answer seems impossible without vastly complicating things.

sjkaliski commented 11 years ago

@ethanresnick did you see the implementation I started putting in place? It's actually really only relevant for testing...but it also could just be used globally for any functions requiring an error bound.

ethanresnick commented 11 years ago

I like it. Going to make a few changes w/ explanations in the commit logs; let me know what you think. We can always revert them.

ethanresnick commented 11 years ago

Don't think this is really closed.

Epsilon gets around the JS rounding errors, but it doesn't get around the other kind of error (i.e. that we're approximating "infinitely close" in derivatives and limits with fixed decimal distances).

For example, if I do pointDiff(function(x) { return 999999999*Math.pow(x,2); }, 10), the answer is .6 off, which is greater than the default EPSILON, and that's just due to the fact that the function's not being evaluated close enough to 10 for the derivative to stay super accurate.

To fix this kind of issue, we could just cheat and make the decimal used in pointDiff et al even smaller, and frankly, that might be fine. But to do it "right" I think we need some sort of recursion with conversion tracking, i.e. keep running pointDiff and evaluating the function closer to 10 each time until the change in the answer between iterations falls below EPSILON. (Though, with this use, maybe epsilon wouldn't be the best name for that threshold...not sure.) That's what I thought you were talking about with the do...whiles.

sjkaliski commented 11 years ago

Yeah you're right, I just got overly excited.

I am concerned that if you went with a recursive method, you might exceed max stack size.

Maybe in addition to epsilon, you provide a max recursion count that limits how far that goes. Because if you wanted to get close enough, you might notice performance problems. But if you're fine with some level of error in hopes of gaining efficiency, then you can reduce it.

The do...while was partially a joke because I think it's an insane construct, but it actually would be super useful in this case.

Thoughts?

ethanresnick commented 11 years ago

Hmm...thinking about this some more, maybe just replacing the decimal used in pointDiff with Number.MIN_VALUE is the right option. After all, we're saying that pointDiff's decimal is not small enough and proposing to making it iteratively smaller, so what if we just used the smallest possible value right off the bat? Carrying around all those decimal places might be too memory intensive, but it would definitely be simpler.

If we do decide to do something iterative/recursive, then I think (though correct me if I'm wrong) that using do...while gets around the max stack size worry because it doesn't introduce another function call/new vars.

Re having a max recursion count exposed in the API: I have to think about it some more; could definitely be useful, but it seems like it might be overkill too.