App-SocialCalc-Multiplayer
view release on metacpan or search on metacpan
socialcalc/formula1.js view on Meta::CPAN
if (lifetime.value < 1) {
scf.FunctionSpecificError(fname, operand, "e#NUM!", SocialCalc.Constants.s_sheetfuncslnlife);
return 0;
}
depreciation = (cost.value - salvage.value) / lifetime.value;
scf.PushOperand(operand, 'n$', depreciation);
return;
}
SocialCalc.Formula.FunctionList["SLN"] = [SocialCalc.Formula.SLNFunction, 3, "csl", "", "financial"];
/*
#
# SYD(cost,salvage,lifetime,period)
#
# Depreciation by Sum of Year's Digits method
#
*/
SocialCalc.Formula.SYDFunction = function(fname, operand, foperand, sheet) {
var depreciation, sumperiods;
var scf = SocialCalc.Formula;
var cost = scf.OperandAsNumber(sheet, foperand);
var salvage = scf.OperandAsNumber(sheet, foperand);
var lifetime = scf.OperandAsNumber(sheet, foperand);
var period = scf.OperandAsNumber(sheet, foperand);
if (scf.CheckForErrorValue(operand, cost)) return;
if (scf.CheckForErrorValue(operand, salvage)) return;
if (scf.CheckForErrorValue(operand, lifetime)) return;
if (scf.CheckForErrorValue(operand, period)) return;
if (lifetime.value < 1 || period.value <= 0) {
scf.PushOperand(operand, "e#NUM!", 0);
return 0;
}
sumperiods = ((lifetime.value + 1) * lifetime.value)/2; // add up 1 through lifetime
depreciation = (cost.value - salvage.value) * (lifetime.value - period.value + 1) / sumperiods; // calc depreciation
scf.PushOperand(operand, 'n$', depreciation);
return;
}
SocialCalc.Formula.FunctionList["SYD"] = [SocialCalc.Formula.SYDFunction, 4, "cslp", "", "financial"];
/*
#
# FV(rate, n, payment, [pv, [paytype]])
# NPER(rate, payment, pv, [fv, [paytype]])
# PMT(rate, n, pv, [fv, [paytype]])
# PV(rate, n, payment, [fv, [paytype]])
# RATE(n, payment, pv, [fv, [paytype, [guess]]])
#
# Following the Open Document Format formula specification:
#
# PV = - Fv - (Payment * Nper) [if rate equals 0]
# Pv*(1+Rate)^Nper + Payment * (1 + Rate*PaymentType) * ( (1+Rate)^nper -1)/Rate + Fv = 0
#
# For each function, the formulas are solved for the appropriate value (transformed using
# basic algebra).
#
*/
SocialCalc.Formula.InterestFunctions = function(fname, operand, foperand, sheet) {
var resulttype, result, dval, eval, fval;
var pv, fv, rate, n, payment, paytype, guess, part1, part2, part3, part4, part5;
var olddelta, maxloop, tries, deltaepsilon, rate, oldrate, m;
var scf = SocialCalc.Formula;
var aval = scf.OperandAsNumber(sheet, foperand);
var bval = scf.OperandAsNumber(sheet, foperand);
var cval = scf.OperandAsNumber(sheet, foperand);
resulttype = scf.LookupResultType(aval.type, bval.type, scf.TypeLookupTable.twoargnumeric);
resulttype = scf.LookupResultType(resulttype, cval.type, scf.TypeLookupTable.twoargnumeric);
if (foperand.length) { // optional arguments
dval = scf.OperandAsNumber(sheet, foperand);
resulttype = scf.LookupResultType(resulttype, dval.type, scf.TypeLookupTable.twoargnumeric);
if (foperand.length) { // optional arguments
eval = scf.OperandAsNumber(sheet, foperand);
resulttype = scf.LookupResultType(resulttype, eval.type, scf.TypeLookupTable.twoargnumeric);
if (foperand.length) { // optional arguments
if (fname != "RATE") { // only rate has 6 possible args
scf.FunctionArgsError(fname, operand);
return 0;
}
fval = scf.OperandAsNumber(sheet, foperand);
resulttype = scf.LookupResultType(resulttype, fval.type, scf.TypeLookupTable.twoargnumeric);
}
}
}
if (resulttype == "n") {
switch (fname) {
case "FV": // FV(rate, n, payment, [pv, [paytype]])
rate = aval.value;
n = bval.value;
payment = cval.value;
pv = dval!=null ? dval.value : 0; // get value if present, or use default
paytype = eval!=null ? (eval.value ? 1 : 0) : 0;
if (rate == 0) { // simple calculation if no interest
fv = -pv - (payment * n);
}
else {
fv = -(pv*Math.pow(1+rate,n) + payment * (1 + rate*paytype) * ( Math.pow(1+rate,n) -1)/rate);
}
result = fv;
resulttype = 'n$';
break;
case "NPER": // NPER(rate, payment, pv, [fv, [paytype]])
rate = aval.value;
payment = bval.value;
pv = cval.value;
fv = dval!=null ? dval.value : 0;
paytype = eval!=null ? (eval.value ? 1 : 0) : 0;
if (rate == 0) { // simple calculation if no interest
if (payment == 0) {
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
n = (pv + fv)/(-payment);
}
else {
part1 = payment * (1 + rate * paytype) / rate;
part2 = pv + part1;
if (part2 == 0 || rate <= -1) {
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
part3 = (part1 - fv) / part2;
if (part3 <= 0) {
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
part4 = Math.log(part3);
part5 = Math.log(1 + rate); // rate > -1
n = part4/part5;
}
result = n;
resulttype = 'n';
break;
case "PMT": // PMT(rate, n, pv, [fv, [paytype]])
rate = aval.value;
n = bval.value;
pv = cval.value;
fv = dval!=null ? dval.value : 0;
paytype = eval!=null ? (eval.value ? 1 : 0) : 0;
if (n == 0) {
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
else if (rate == 0) { // simple calculation if no interest
payment = (fv - pv)/n;
}
else {
payment = (0 - fv - pv*Math.pow(1+rate,n))/((1 + rate*paytype) * ( Math.pow(1+rate,n) -1)/rate);
}
result = payment;
resulttype = 'n$';
break;
case "PV": // PV(rate, n, payment, [fv, [paytype]])
rate = aval.value;
n = bval.value;
payment = cval.value;
fv = dval!=null ? dval.value : 0;
paytype = eval!=null ? (eval.value ? 1 : 0) : 0;
if (rate == -1) {
scf.PushOperand(operand, "e#DIV/0!", 0);
return;
}
else if (rate == 0) { // simple calculation if no interest
pv = -fv - (payment * n);
}
else {
pv = (-fv - payment * (1 + rate*paytype) * ( Math.pow(1+rate,n) -1)/rate)/(Math.pow(1+rate,n));
}
result = pv;
resulttype = 'n$';
break;
case "RATE": // RATE(n, payment, pv, [fv, [paytype, [guess]]])
n = aval.value;
payment = bval.value;
pv = cval.value;
fv = dval!=null ? dval.value : 0;
paytype = eval!=null ? (eval.value ? 1 : 0) : 0;
guess = fval!=null ? fval.value : 0.1;
// rate is calculated by repeated approximations
// The deltas are used to calculate new guesses
maxloop = 100;
tries = 0;
delta = 1;
epsilon = 0.0000001; // this is close enough
rate = guess || 0.00000001; // zero is not allowed
while ((delta >= 0 ? delta : -delta) > epsilon && (rate != oldrate)) {
delta = fv + pv*Math.pow(1+rate,n) + payment * (1 + rate*paytype) * ( Math.pow(1+rate,n) -1)/rate;
if (olddelta!=null) {
m = (delta - olddelta)/(rate - oldrate) || .001; // get slope (not zero)
oldrate = rate;
rate = rate - delta / m; // look for zero crossing
olddelta = delta;
}
else { // first time - no old values
oldrate = rate;
rate = 1.1 * rate;
olddelta = delta;
}
tries++;
if (tries >= maxloop) { // didn't converge yet
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
}
result = rate;
resulttype = 'n%';
break;
}
}
scf.PushOperand(operand, resulttype, result);
return;
}
SocialCalc.Formula.FunctionList["FV"] = [SocialCalc.Formula.InterestFunctions, -3, "fv", "", "financial"];
SocialCalc.Formula.FunctionList["NPER"] = [SocialCalc.Formula.InterestFunctions, -3, "nper", "", "financial"];
SocialCalc.Formula.FunctionList["PMT"] = [SocialCalc.Formula.InterestFunctions, -3, "pmt", "", "financial"];
SocialCalc.Formula.FunctionList["PV"] = [SocialCalc.Formula.InterestFunctions, -3, "pv", "", "financial"];
SocialCalc.Formula.FunctionList["RATE"] = [SocialCalc.Formula.InterestFunctions, -3, "rate", "", "financial"];
/*
#
# NPV(rate,v1,v2,c1:c2,...)
#
*/
SocialCalc.Formula.NPVFunction = function(fname, operand, foperand, sheet) {
var resulttypenpv, rate, sum, factor, value1;
var scf = SocialCalc.Formula;
var rate = scf.OperandAsNumber(sheet, foperand);
if (scf.CheckForErrorValue(operand, rate)) return;
sum = 0;
resulttypenpv = "n";
factor = 1;
while (foperand.length) {
value1 = scf.OperandValueAndType(sheet, foperand);
if (value1.type.charAt(0) == "n") {
factor *= (1 + rate.value);
if (factor == 0) {
scf.PushOperand(operand, "e#DIV/0!", 0);
return;
}
sum += value1.value / factor;
resulttypenpv = scf.LookupResultType(value1.type, resulttypenpv || value1.type, scf.TypeLookupTable.plus);
}
else if (value1.type.charAt(0) == "e" && resulttypenpv.charAt(0) != "e") {
resulttypenpv = value1.type;
break;
}
}
if (resulttypenpv.charAt(0) == "n") {
resulttypenpv = 'n$';
}
scf.PushOperand(operand, resulttypenpv, sum);
return;
}
SocialCalc.Formula.FunctionList["NPV"] = [SocialCalc.Formula.NPVFunction, -2, "npv", "", "financial"];
/*
#
# IRR(c1:c2,[guess])
#
*/
SocialCalc.Formula.IRRFunction = function(fname, operand, foperand, sheet) {
var value1, guess, oldsum, maxloop, tries, epsilon, rate, oldrate, m, sum, factor, i;
var rangeoperand = [];
var cashflows = [];
var scf = SocialCalc.Formula;
rangeoperand.push(foperand.pop()); // first operand is a range
while (rangeoperand.length) { // get values from range so we can do iterative approximations
value1 = scf.OperandValueAndType(sheet, rangeoperand);
if (value1.type.charAt(0) == "n") {
cashflows.push(value1.value);
}
else if (value1.type.charAt(0) == "e") {
scf.PushOperand(operand, "e#VALUE!", 0);
return;
}
}
if (!cashflows.length) {
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
guess = {value: 0};
if (foperand.length) { // guess is provided
guess = scf.OperandAsNumber(sheet, foperand);
if (guess.type.charAt(0) != "n" && guess.type.charAt(0) != "b") {
scf.PushOperand(operand, "e#VALUE!", 0);
return;
}
if (foperand.length) { // should be no more args
scf.FunctionArgsError(fname, operand);
return;
}
}
guess.value = guess.value || 0.1;
// rate is calculated by repeated approximations
// The deltas are used to calculate new guesses
maxloop = 20;
tries = 0;
epsilon = 0.0000001; // this is close enough
rate = guess.value;
sum = 1;
while ((sum >= 0 ? sum : -sum) > epsilon && (rate != oldrate)) {
sum = 0;
factor = 1;
for (i=0; i<cashflows.length; i++) {
factor *= (1 + rate);
if (factor == 0) {
scf.PushOperand(operand, "e#DIV/0!", 0);
return;
}
sum += cashflows[i] / factor;
}
if (oldsum!=null) {
m = (sum - oldsum)/(rate - oldrate); // get slope
oldrate = rate;
rate = rate - sum / m; // look for zero crossing
oldsum = sum;
}
else { // first time - no old values
oldrate = rate;
rate = 1.1 * rate;
oldsum = sum;
}
tries++;
if (tries >= maxloop) { // didn't converge yet
scf.PushOperand(operand, "e#NUM!", 0);
return;
}
}
scf.PushOperand(operand, 'n%', rate);
return;
}
SocialCalc.Formula.FunctionList["IRR"] = [SocialCalc.Formula.IRRFunction, -1, "irr", "", "financial"];
//
// SHEET CACHE
//
SocialCalc.Formula.SheetCache = {
// Sheet data: Attributes are each sheet in the cache with values of an object with:
//
// sheet: sheet-obj (or null, meaning not found)
// recalcstate: constants.asloaded = as loaded
// constants.recalcing = being recalced now
// constants.recalcdone = recalc done
// name: name of sheet (in case just have object and don't know name)
//
sheets: {},
// Waiting for loading:
// If sheet is not in cache, this is set to the sheetname being loaded
// so it can be tested in the recalc loop to start load and then wait until restarted.
( run in 0.823 second using v1.01-cache-2.11-cpan-98e64b0badf )