HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Basic financial calculation library

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
financialcalculationlibrarybasic

Problem

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;
}

Solution

-
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');


-
Try to declare variables at the top of the function.

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

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

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

-
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);
    };
}());

Code Snippets

var root = (1,eval)('this');
(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);
    };
}());

Context

StackExchange Code Review Q#8798, answer score: 5

Revisions (0)

No revisions yet.