App-SocialCalc-Multiplayer
view release on metacpan or search on metacpan
socialcalc/formula1.js view on Meta::CPAN
// Negative value means Right Associative
SocialCalc.Formula.TokenPrecedence = {
"!": 1,
":": 2, ",": 2,
"M": -3, "P": -3,
"%": 4,
"^": 5,
"*": 6, "/": 6,
"+": 7, "-": 7,
"&": 8,
"<": 9, ">": 9, "G": 9, "L": 9, "N": 9
};
// Convert one-char token text to input text:
SocialCalc.Formula.TokenOpExpansion = {'G': '>=', 'L': '<=', 'M': '-', 'N': '<>', 'P': '+'};
//
// Information about the resulting value types when doing operations on values (used by LookupResultType)
//
// Each object entry is an object with specific types with result type info as follows:
//
// 'type1a': '|type2a:resulta|type2b:resultb|...
// Type of t* or n* matches any of those types not listed
// Results may be a type or the numbers 1 or 2 specifying to return type1 or type2
SocialCalc.Formula.TypeLookupTable = {
unaryminus: { 'n*': '|n*:1|', 'e*': '|e*:1|', 't*': '|t*:e#VALUE!|', 'b': '|b:n|'},
unaryplus: { 'n*': '|n*:1|', 'e*': '|e*:1|', 't*': '|t*:e#VALUE!|', 'b': '|b:n|'},
unarypercent: { 'n*': '|n:n%|n*:n|', 'e*': '|e*:1|', 't*': '|t*:e#VALUE!|', 'b': '|b:n|'},
plus: {
'n%': '|n%:n%|nd:n|nt:n|ndt:n|n$:n|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|',
'nd': '|n%:n|nd:nd|nt:ndt|ndt:ndt|n$:n|n:nd|n*:n|b:n|e*:2|t*:e#VALUE!|',
'nt': '|n%:n|nd:ndt|nt:nt|ndt:ndt|n$:n|n:nt|n*:n|b:n|e*:2|t*:e#VALUE!|',
'ndt': '|n%:n|nd:ndt|nt:ndt|ndt:ndt|n$:n|n:ndt|n*:n|b:n|e*:2|t*:e#VALUE!|',
'n$': '|n%:n|nd:n|nt:n|ndt:n|n$:n$|n:n$|n*:n|b:n|e*:2|t*:e#VALUE!|',
'nl': '|n%:n|nd:n|nt:n|ndt:n|n$:n|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|',
'n': '|n%:n|nd:nd|nt:nt|ndt:ndt|n$:n$|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|',
'b': '|n%:n%|nd:nd|nt:nt|ndt:ndt|n$:n$|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|',
't*': '|n*:e#VALUE!|t*:e#VALUE!|b:e#VALUE!|e*:2|',
'e*': '|e*:1|n*:1|t*:1|b:1|'
},
concat: {
't': '|t:t|th:th|tw:tw|tl:t|t*:2|e*:2|',
'th': '|t:th|th:th|tw:t|tl:th|t*:t|e*:2|',
'tw': '|t:tw|th:t|tw:tw|tl:tw|t*:t|e*:2|',
'tl': '|t:tl|th:th|tw:tw|tl:tl|t*:t|e*:2|',
'e*': '|e*:1|n*:1|t*:1|'
},
oneargnumeric: { 'n*': '|n*:n|', 'e*': '|e*:1|', 't*': '|t*:e#VALUE!|', 'b': '|b:n|'},
twoargnumeric: { 'n*': '|n*:n|t*:e#VALUE!|e*:2|', 'e*': '|e*:1|n*:1|t*:1|', 't*': '|t*:e#VALUE!|n*:e#VALUE!|e*:2|'},
propagateerror: { 'n*': '|n*:2|e*:2|', 'e*': '|e*:2|', 't*': '|t*:2|e*:2|', 'b': '|b:2|e*:2|'}
};
/* *******************
parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(line)
Parses a text string as if it was a spreadsheet formula
This uses a simple state machine run on each character in turn.
States remember whether a number is being gathered, etc.
The result is parseinfo which is an array with one entry for each token:
parseinfo[i] = {
text: "the characters making up the parsed token",
type: the type of the token (a number),
opcode: a single character version of an operator suitable for use in the
precedence table and distinguishing between unary and binary + and -.
************************* */
SocialCalc.Formula.ParseFormulaIntoTokens = function(line) {
var i, ch, chclass, haddecimal, last_token, last_token_type, last_token_text, t;
var scf = SocialCalc.Formula;
var scc = SocialCalc.Constants;
var parsestate = scf.ParseState;
var tokentype = scf.TokenType;
var charclass = scf.CharClass;
var charclasstable = scf.CharClassTable;
var uppercasetable = scf.UpperCaseTable; // much faster than toUpperCase function
var pushtoken = scf.ParsePushToken;
var coordregex = /^\$?[A-Z]{1,2}\$?[1-9]\d*$/i;
var parseinfo = [];
var str = "";
var state = 0;
var haddecimal = false;
for (i=0; i<=line.length; i++) {
if (i<line.length) {
ch = line.charAt(i);
cclass = charclasstable[ch];
}
else {
ch = "";
cclass = charclass.eof;
}
if (state == parsestate.num) {
if (cclass == charclass.num) {
str += ch;
}
else if (cclass == charclass.numstart && !haddecimal) {
haddecimal = true;
str += ch;
}
else if (ch == "E" || ch == "e") {
str += ch;
haddecimal = false;
state = parsestate.numexp1;
}
else { // end of number - save it
pushtoken(parseinfo, str, tokentype.num, 0);
haddecimal = false;
state = 0;
}
}
socialcalc/formula1.js view on Meta::CPAN
else if (str == '+') { // P is unary plus
str = "P";
ch = "P";
}
else if (str == ')' && last_token_text == '(') { // null arg list OK
;
}
else if (str != '(') { // binary-op open-paren OK, others no
t = tokentype.error;
str = scc.s_parseerrtwoops;
}
}
else if (str.length > 1) {
if (str == '>=') { // G is >=
str = "G";
ch = "G";
}
else if (str == '<=') { // L is <=
str = "L";
ch = "L";
}
else if (str == '<>') { // N is <>
str = "N";
ch = "N";
}
else {
t = tokentype.error;
str = scc.s_parseerrtwoops;
}
}
pushtoken(parseinfo, str, t, ch);
state = 0;
}
else if (cclass == charclass.quote) { // starting a string
str = "";
state = parsestate.string;
}
else if (cclass == charclass.space) { // store so can reconstruct spacing
pushtoken(parseinfo, " ", tokentype.space, 0);
}
else if (cclass == charclass.eof) { // ignore -- needed to have extra loop to close out other things
}
else { // unknown class - such as unknown char
pushtoken(parseinfo, scc.s_parseerrchar, tokentype.error, 0);
}
}
}
return parseinfo;
}
SocialCalc.Formula.ParsePushToken = function(parseinfo, ttext, ttype, topcode) {
parseinfo.push({text: ttext, type: ttype, opcode: topcode});
}
/* *******************
result = SocialCalc.Formula.evaluate_parsed_formula(parseinfo, sheet, allowrangereturn)
Does the calculation expressed in a parsed formula, returning a value, its type, and error info
returns: {value: value, type: valuetype, error: errortext}.
If allowrangereturn is present and true, can return a range (e.g., "A1:A10" - translated from "A1|A10|")
************************* */
SocialCalc.Formula.evaluate_parsed_formula = function(parseinfo, sheet, allowrangereturn) {
var result;
var scf = SocialCalc.Formula;
var tokentype = scf.TokenType;
var revpolish;
var parsestack = [];
var errortext = "";
revpolish = scf.ConvertInfixToPolish(parseinfo); // result is either an array or a string with error text
result = scf.EvaluatePolish(parseinfo, revpolish, sheet, allowrangereturn);
return result;
}
//
// revpolish = SocialCalc.Formula.ConvertInfixToPolish(parseinfo)
//
// Convert infix to reverse polish notation
//
// Returns revpolish array with a sequence of references to tokens by number if successful.
// Errors return a string with the error.
//
// Based upon the algorithm shown in Wikipedia "Reverse Polish notation" article
// and then enhanced for additional spreadsheet things
//
SocialCalc.Formula.ConvertInfixToPolish = function(parseinfo) {
var scf = SocialCalc.Formula;
var scc = SocialCalc.Constants;
var tokentype = scf.TokenType;
var token_precedence = scf.TokenPrecedence;
var revpolish = [];
var parsestack = [];
var errortext = "";
var function_start = -1;
var i, pii, ttype, ttext, tprecedence, tstackprecedence;
for (i=0; i<parseinfo.length; i++) {
pii = parseinfo[i];
ttype = pii.type;
ttext = pii.text;
if (ttype == tokentype.num || ttype == tokentype.coord || ttype == tokentype.string) {
revpolish.push(i);
}
else if (ttype == tokentype.name) {
parsestack.push(i);
revpolish.push(function_start);
}
else if (ttype == tokentype.space) { // ignore
continue;
}
else if (ttext == ',') {
while (parsestack.length && parseinfo[parsestack[parsestack.length-1]].text != "(") {
revpolish.push(parsestack.pop());
}
if (parsestack.length == 0) { // no ( -- error
errortext = scc.s_parseerrmissingopenparen;
break;
}
}
else if (ttext == '(') {
parsestack.push(i);
}
else if (ttext == ')') {
while (parsestack.length && parseinfo[parsestack[parsestack.length-1]].text != "(") {
revpolish.push(parsestack.pop());
}
if (parsestack.length == 0) { // no ( -- error
errortext = scc.s_parseerrcloseparennoopen;
break;
}
parsestack.pop();
if (parsestack.length && parseinfo[parsestack[parsestack.length-1]].type == tokentype.name) {
revpolish.push(parsestack.pop());
}
}
else if (ttype == tokentype.op) {
if (parsestack.length && parseinfo[parsestack[parsestack.length-1]].type == tokentype.name) {
revpolish.push(parsestack.pop());
}
while (parsestack.length && parseinfo[parsestack[parsestack.length-1]].type == tokentype.op
&& parseinfo[parsestack[parsestack.length-1]].text != '(') {
tprecedence = token_precedence[pii.opcode];
tstackprecedence = token_precedence[parseinfo[parsestack[parsestack.length-1]].opcode];
if (tprecedence >= 0 && tprecedence < tstackprecedence) {
break;
}
else if (tprecedence < 0) {
tprecedence = -tprecedence;
if (tstackprecedence < 0) tstackprecedence = -tstackprecedence;
if (tprecedence <= tstackprecedence) {
break;
}
}
revpolish.push(parsestack.pop());
}
parsestack.push(i);
}
else if (ttype == tokentype.error) {
errortext = ttext;
break;
}
else {
errortext = "Internal error while processing parsed formula. ";
break;
}
}
while (parsestack.length>0) {
if (parseinfo[parsestack[parsestack.length-1]].text == '(') {
errortext = scc.s_parseerrmissingcloseparen;
break;
}
revpolish.push(parsestack.pop());
}
if (errortext) {
return errortext;
}
return revpolish;
}
//
// result = SocialCalc.Formula.EvaluatePolish(parseinfo, revpolish, sheet, allowrangereturn)
//
// Execute reverse polish representation of formula
//
// Operand values are objects in the operand array with a "type" and an optional "value".
// Type can have these values (many are type and sub-type as two or more letters):
// "tw", "th", "t", "n", "nt", "coord", "range", "start", "eErrorType", "b" (blank)
// The value of a coord is in the form A57 or A57!sheetname
// The value of a range is coord|coord|number where number starts at 0 and is
// the offset of the next item to fetch if you are going through the range one by one
// The number starts as a null string ("A1|B3|")
//
SocialCalc.Formula.EvaluatePolish = function(parseinfo, revpolish, sheet, allowrangereturn) {
var scf = SocialCalc.Formula;
var scc = SocialCalc.Constants;
var tokentype = scf.TokenType;
var lookup_result_type = scf.LookupResultType;
var typelookup = scf.TypeLookupTable;
var operand_as_number = scf.OperandAsNumber;
var operand_as_text = scf.OperandAsText;
var operand_value_and_type = scf.OperandValueAndType;
var operands_as_coord_on_sheet = scf.OperandsAsCoordOnSheet;
var format_number_for_display = SocialCalc.format_number_for_display || function(v, t, f) {return v+"";};
var errortext = "";
var function_start = -1;
var missingOperandError = {value: "", type: "e#VALUE!", error: scc.s_parseerrmissingoperand};
var operand = [];
var PushOperand = function(t, v) {operand.push({type: t, value: v});};
var i, rii, prii, ttype, ttext, value1, value2, tostype, tostype2, resulttype, valuetype, cond, vmatch, smatch;
if (!parseinfo.length || (! (revpolish instanceof Array))) {
return ({value: "", type: "e#VALUE!", error: (typeof revpolish == "string" ? revpolish : "")});
}
for (i=0; i<revpolish.length; i++) {
rii = revpolish[i];
if (rii == function_start) { // Remember the start of a function argument list
PushOperand("start", 0);
continue;
}
prii = parseinfo[rii];
ttype = prii.type;
ttext = prii.text;
if (ttype == tokentype.num) {
PushOperand("n", ttext-0);
}
else if (ttype == tokentype.coord) {
PushOperand("coord", ttext);
}
else if (ttype == tokentype.string) {
PushOperand("t", ttext);
}
else if (ttype == tokentype.op) {
socialcalc/formula1.js view on Meta::CPAN
else if (ttext == '*') {
resulttype = lookup_result_type(value1.type, value2.type, typelookup.plus);
PushOperand(resulttype, value1.value * value2.value);
}
else if (ttext == '/') {
if (value2.value != 0) {
PushOperand("n", value1.value / value2.value); // gives plain numeric result type
}
else {
PushOperand("e#DIV/0!", 0);
}
}
else if (ttext == '^') {
value1.value = Math.pow(value1.value, value2.value);
value1.type = "n"; // gives plain numeric result type
if (isNaN(value1.value)) {
value1.value = 0;
value1.type = "e#NUM!";
}
PushOperand(value1.type, value1.value);
}
}
}
// function or name
else if (ttype == tokentype.name) {
errortext = scf.CalculateFunction(ttext, operand, sheet);
if (errortext) break;
}
else {
errortext = scc.s_InternalError+"Unknown token "+ttype+" ("+ttext+"). ";
break;
}
}
// look at final value and handle special cases
value = operand[0] ? operand[0].value : "";
tostype = operand[0] ? operand[0].type : "";
if (tostype == "name") { // name - expand it
value1 = SocialCalc.Formula.LookupName(sheet, value);
value = value1.value;
tostype = value1.type;
errortext = errortext || value1.error;
}
if (tostype == "coord") { // the value is a coord reference, get its value and type
value1 = operand_value_and_type(sheet, operand);
value = value1.value;
tostype = value1.type;
if (tostype == "b") {
tostype = "n";
value = 0;
}
}
if (operand.length > 1 && !errortext) { // something left - error
errortext += scc.s_parseerrerrorinformula;
}
// set return type
valuetype = tostype;
if (tostype.charAt(0) == "e") { // error value
errortext = errortext || tostype.substring(1) || scc.s_calcerrerrorvalueinformula;
}
else if (tostype == "range") {
vmatch = value.match(/^(.*)\|(.*)\|/);
smatch = vmatch[1].indexOf("!");
if (smatch>=0) { // swap sheetname
vmatch[1] = vmatch[1].substring(smatch+1) + "!" + vmatch[1].substring(0, smatch).toUpperCase();
}
else {
vmatch[1] = vmatch[1].toUpperCase();
}
value = vmatch[1] + ":" + vmatch[2].toUpperCase();
if (!allowrangereturn) {
errortext = scc.s_formularangeresult+" "+value;
}
}
if (errortext && valuetype.charAt(0) != "e") {
value = errortext;
valuetype = "e";
}
// look for overflow
if (valuetype.charAt(0) == "n" && (isNaN(value) || !isFinite(value))) {
value = 0;
valuetype = "e#NUM!";
errortext = isNaN(value) ? scc.s_calcerrnumericnan: scc.s_calcerrnumericoverflow;
}
return ({value: value, type: valuetype, error: errortext});
}
/*
#
# resulttype = SocialCalc.Formula.LookupResultType(type1, type2, typelookup);
#
# typelookup has values of the following form:
#
# typelookup{"typespec1"} = "|typespec2A:resultA|typespec2B:resultB|..."
#
# First type1 is looked up. If no match, then the first letter (major type) of type1 plus "*" is looked up
# resulttype is type1 if result is "1", type2 if result is "2", otherwise the value of result.
#
*/
SocialCalc.Formula.LookupResultType = function(type1, type2, typelookup) {
var pos1, pos2, result;
var table1 = typelookup[type1];
if (!table1) {
table1 = typelookup[type1.charAt(0)+'*'];
if (!table1) {
return "e#VALUE! (internal error, missing LookupResultType "+type1.charAt(0)+"*)"; // missing from table -- please add it
}
}
pos1 = table1.indexOf("|"+type2+":");
if (pos1 >= 0) {
pos2 = table1.indexOf("|", pos1+1);
if (pos2<0) return "e#VALUE! (internal error, incorrect LookupResultType "+table1+")";
result = table1.substring(pos1+type2.length+2, pos2);
if (result == "1") return type1;
if (result == "2") return type2;
return result;
}
pos1 = table1.indexOf("|"+type2.charAt(0)+"*:");
if (pos1 >= 0) {
pos2 = table1.indexOf("|", pos1+1);
if (pos2<0) return "e#VALUE! (internal error, incorrect LookupResultType "+table1+")";
result = table1.substring(pos1+4, pos2);
socialcalc/formula1.js view on Meta::CPAN
/*
#
# result = SocialCalc.Formula.OperandAsSheetName(sheet, operand)
#
# Gets top of stack and pops it.
# Returns sheetname value. All others are treated as an error.
# Accepts text, cell reference, and named value which is one of those two.
#
*/
SocialCalc.Formula.OperandAsSheetName = function(sheet, operand) {
var nvalue, cell;
var scf = SocialCalc.Formula;
var result = {type: "", value: ""};
var stacklen = operand.length;
result.value = operand[stacklen-1].value; // get top of stack
result.type = operand[stacklen-1].type;
operand.pop(); // we have data - pop stack
if (result.type == "name") {
nvalue = SocialCalc.Formula.LookupName(sheet, result.value);
if (!nvalue.value) { // not a known name - return bare name as the name value
return result;
}
result.value = nvalue.value;
result.type = nvalue.type;
}
if (result.type == "coord") { // value is a coord reference, follow it to find sheet name
cell = sheet.cells[SocialCalc.Formula.PlainCoord(result.value)];
if (cell) {
result.value = cell.datavalue;
result.type = cell.valuetype;
}
else {
result.value = "";
result.type = "b";
}
}
if (result.type.charAt(0) == "t") { // value is a string which could be a sheet name
return result;
}
else {
result.value = "";
result.error = SocialCalc.Constants.s_calcerrsheetnamemissing;
return result;
}
}
//
// value = SocialCalc.Formula.LookupName(sheet, name)
//
// Returns value and type of a named value
// Names are case insensitive
// Names may have a definition which is a coord (A1), a range (A1:B7), or a formula (=OFFSET(A1,0,0,5,1))
// Note: The range must not have sheet names ("!") in them.
//
SocialCalc.Formula.LookupName = function(sheet, name) {
var pos, specialc, parseinfo;
var names = sheet.names;
var value = {};
var startedwalk = false;
if (names[name.toUpperCase()]) { // is name defined?
value.value = names[name.toUpperCase()].definition; // yes
if (value.value.charAt(0) == "=") { // formula
if (!sheet.checknamecirc) { // are we possibly walking the name tree?
sheet.checknamecirc = {}; // not yet
startedwalk = true; // remember we are the reference that started it
}
else {
if (sheet.checknamecirc[name]) { // circular reference
value.type = "e#NAME?";
value.error = SocialCalc.Constants.s_circularnameref+' "' + name + '".';
return value;
}
}
sheet.checknamecirc[name] = true;
parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(value.value.substring(1));
value = SocialCalc.Formula.evaluate_parsed_formula(parseinfo, sheet, 1); // parse formula, allowing range return
delete sheet.checknamecirc[name]; // done with us
if (startedwalk) {
delete sheet.checknamecirc; // done with walk
}
if (value.type != "range") {
return value;
}
}
pos = value.value.indexOf(":");
if (pos != -1) { // range
value.type = "range";
value.value = value.value.substring(0, pos) + "|" + value.value.substring(pos+1)+"|";
value.value = value.value.toUpperCase();
}
else {
value.type = "coord";
value.value = value.value.toUpperCase();
}
return value;
}
else if (specialc=SocialCalc.Formula.SpecialConstants[name.toUpperCase()]) { // special constant, like #REF!
pos = specialc.indexOf(",");
value.value = specialc.substring(0,pos)-0;
value.type = specialc.substring(pos+1);
return value;
}
else {
value.value = "";
value.type = "e#NAME?";
value.error = SocialCalc.Constants.s_calcerrunknownname+' "'+name+'"';
return value;
}
}
/*
#
# coord = SocialCalc.Formula.StepThroughRangeDown(operand, rangevalue)
#
# Returns next coord in a range, keeping track on the operand stack
# Goes from upper left across and down to bottom right.
#
*/
SocialCalc.Formula.StepThroughRangeDown = function(operand, rangevalue) {
var value1, value2, sequence, pos1, pos2, sheet1, rp, c, r, count;
var scf = SocialCalc.Formula;
pos1 = rangevalue.indexOf("|");
pos2 = rangevalue.indexOf("|", pos1+1);
value1 = rangevalue.substring(0, pos1);
value2 = rangevalue.substring(pos1+1, pos2);
sequence = rangevalue.substring(pos2+1) - 0;
pos1 = value1.indexOf("!");
if (pos1 != -1) {
sheet1 = value1.substring(pos1);
socialcalc/formula1.js view on Meta::CPAN
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
( run in 2.153 seconds using v1.01-cache-2.11-cpan-0d23b851a93 )