6
\$\begingroup\$

I'm trying to build a JavaScript library that does basic financial calculations, such as NPV, IRR, PV, FV, etc. So far I've added the NPV functionality only, and it's passed my test cases. But I want to know if the code overall can be written better.

(function () {
    /*
This library depends on underscore.js
*/
    'use strict';
    var root = this, previousFin = root.Fin, Fin = root.Fin = {}, _ = root._;

    Fin.VERSION = '0.0.1';
    Fin.PRECISION = 4; // floating point precision

    /*
Runs Fin.js in noConflict mode and
returns reference to this Fin object
*/
    Fin.noConflict = function () {
        root.Fin = previousFin;
        return this;
    };

    /*
NPV - Net Present Value
rate = the periodic discount rate
example: If the discount rate is 10% enter 0.1, not 10.
payments = an array or object (keys are the year numbers) of payments
example: [-100, 50, 60] means an initial cash outflow of 100 at time 0,
then cash inflows of 50 at the end of the period one, and 60 at
the end of the period two.
If you pass {0: -100, 2:50}, then the payment at the end of the
year one is assumed to be 0.
*/
    Fin.npv = function (rate, payments, precision) {
        if (isNaN(rate)) {
            /* rate needs to be a number */
            return null;
        }
        if (_.isArray(payments)) {
            /* all elements of the array need to be numbers */
            if (!_.all(payments, function (elem) { return !isNaN(elem); })) {
                return null;
            }
        } else if (_.isObject(payments)) {
            /* all key, value pairs of the object need to be numbers */
            if (!_.all(payments, function (key, value) { return !isNaN(key) && !isNaN(value); })) {
                return null;
            }
        } else {
            /* payment needs to be either an array or an object */
            return null;
        }
        if (typeof (precision) === 'undefined' || isNaN(precision)) {
            precision = this.PRECISION;
        }
        var i, npv = 0;
        for (i in payments) {
            if (payments.hasOwnProperty(i)) {
                npv += payments[i] / Math.pow((1 + rate), i);
            }
        }
        return npv.toFixed(precision);
    };
}).call(this);
\$\endgroup\$
10
  • 1
    \$\begingroup\$ That's not much of an introduction. What does it do? Are there specific things you want to improve? Please paste the code in your question (highlight it and hit ctrl+k to format). \$\endgroup\$
    – Dagg
    Commented Feb 9, 2012 at 7:57
  • 1
    \$\begingroup\$ As specified by our faq, you must include your code in the post. I would edit it in myself, but it is not obvious to me what it's trying to accomplish. Please add the code and a brief description of the problem you're solving, and I'll be very happy to reopen the post. \$\endgroup\$
    – Michael K
    Commented Feb 9, 2012 at 15:01
  • \$\begingroup\$ @MichaelK actually if you want to reopen it I'll fix it up, I think I see what he's after and have a pretty good idea for a review :) \$\endgroup\$
    – Dagg
    Commented Feb 9, 2012 at 23:37
  • 1
    \$\begingroup\$ wouldn't this produce an application wherby a bug in a client's JS interpreter could lead financial calculations gone wrong? \$\endgroup\$ Commented Feb 10, 2012 at 18:37
  • 1
    \$\begingroup\$ Do you really want to use the javascript built-in numerics for amounts of money? This is just asking for rounding errors. I strongly recommend coding up an implementation of decimal numbers. \$\endgroup\$ Commented Feb 11, 2012 at 7:03

1 Answer 1

5
\$\begingroup\$
  1. Avoid getting a reference to the global object ("window") using this; it won't work in strict mode. If this is only meant to run in the browser, just use window; otherwise you can do this:

    var root = (1,eval)('this');
    
  2. Try to declare variables at the top of the function.

  3. Regarding isArray: Favor feature detection. Does it need to have a length property? A push function?

  4. Regarding return null: Just return. This will return undefined, which should be close enough 99.9% of the time.

  5. typeof (precision) === 'undefined' can simply be !precision in this case.

  6. all those early isNaN checks seem redundant, why not just do the checks in the main loop? Or, better yet, leave out the checks entirely, let the calculation fail, and the function will return NaN by itself, which makes more sense than null or undefined.

Incidentally, these changes will remove the underscore.js dependency.


(function () {

    'use strict';

    var root = (1,eval)('this'),
        previousFin = root.Fin,
        Fin;

    root.Fin = Fin = {};

    Fin.VERSION = '0.0.1';
    Fin.PRECISION = 4; // floating point precision

    /*
    Runs Fin.js in noConflict mode and
    returns reference to this Fin object
    */
    Fin.noConflict = function () {
        root.Fin = previousFin;
        return this; 
    };

    /*
    NPV - Net Present Value
    rate = the periodic discount rate
    example: If the discount rate is 10% enter 0.1, not 10.
    payments = an array or object (keys are the year numbers) of payments
    example: [-100, 50, 60] means an initial cash outflow of 100 at time 0,
    then cash inflows of 50 at the end of the period one, and 60 at
    the end of the period two.
    If you pass {0: -100, 2:50}, then the payment at the end of the
    year one is assumed to be 0.
    */
    Fin.npv = function (rate, payments, precision) {
        var i, npv = 0;
        if (!precision || isNaN(precision)) {
            precision = this.PRECISION;
        }
        for (i in payments) {
            if (payments.hasOwnProperty(i)) {
                // do the NaN check here if you want.
                // if (isNaN(payments[i])) return;
                npv += payments[i] / Math.pow((1 + rate), i);
            }
        }
        return npv.toFixed(precision);
    };
}());
\$\endgroup\$
4
  • \$\begingroup\$ Thanks. That's very helpful. I'll upvote you when I have 15 reputation. \$\endgroup\$ Commented Feb 10, 2012 at 19:20
  • 1
    \$\begingroup\$ No problem. Just remember it's not Java, you don't have to check everything... you can generally get away with just letting stuff fail by itself :) Also this will be way simpler if you do away with the noConflict thing. I'd really think about whether your intended users need that. \$\endgroup\$
    – Dagg
    Commented Feb 10, 2012 at 19:30
  • \$\begingroup\$ another great piece of advice! i'll definitely consider it. \$\endgroup\$ Commented Feb 10, 2012 at 20:39
  • \$\begingroup\$ is there an irr part to this as well? \$\endgroup\$
    – HattrickNZ
    Commented Dec 10, 2015 at 23:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.