diff --git a/spreadsheet/formula/fndatetime.go b/spreadsheet/formula/fndatetime.go index 479ee103..8e82e7e5 100644 --- a/spreadsheet/formula/fndatetime.go +++ b/spreadsheet/formula/fndatetime.go @@ -31,7 +31,7 @@ func init() { RegisterFunction("TIMEVALUE", TimeValue) RegisterFunction("TODAY", Today) RegisterFunctionComplex("YEAR", Year) - RegisterFunctionComplex("YEARFRAC", YearFrac) + RegisterFunction("YEARFRAC", YearFrac) } var date1900 int64 = makeDateS(1900, time.January, 1) @@ -107,6 +107,8 @@ func initRegexpTime() { } } +var empty Result = MakeEmptyResult() + // Day is an implementation of the Excel DAY() function. func Day(args []Result) Result { if len(args) != 1 { @@ -406,7 +408,7 @@ func dateValue(dateString string) (int, int, int, bool, Result) { if !validateDate(year, month, day) { return 0, 0, 0, false, MakeErrorResultType(ErrorTypeValue, dvErrMsg) } - return year, month, day, timeIsEmpty, MakeEmptyResult() + return year, month, day, timeIsEmpty, empty } func validateDate(year, month, day int) bool { @@ -743,7 +745,7 @@ func timeValue(timeString string) (int, int, float64, bool, bool, Result) { } else if hours >= 24 || seconds >= 10000 { return 0, 0, 0, false, false, MakeErrorResultType(ErrorTypeValue, tvErrMsg) } - return hours, minutes, seconds, pm, dateIsEmpty, MakeEmptyResult() + return hours, minutes, seconds, pm, dateIsEmpty, empty } // Year is an implementation of the Excel YEAR() function. @@ -760,73 +762,104 @@ func Year(ctx Context, ev Evaluator, args []Result) Result { } // YearFrac is an implementation of the Excel YEARFRAC() function. -func YearFrac(ctx Context, ev Evaluator, args []Result) Result { +func YearFrac(args []Result) Result { argsNum := len(args) if (argsNum != 2 && argsNum != 3) || args[0].Type != ResultTypeNumber || args[1].Type != ResultTypeNumber { return MakeErrorResult("YEARFRAC requires two or three number arguments") } basis := 0 - if argsNum == 3 { + if argsNum == 3 && args[2].Type != ResultTypeEmpty { if args[2].Type != ResultTypeNumber { return MakeErrorResult("YEARFRAC requires two or three number arguments") } basis = int(args[2].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for YEARFRAC") + } } - epoch := ctx.GetEpoch() - startDate, err := getValueAsTime(args[0].Value(), epoch) - if err != nil { - return MakeErrorResult("incorrect start date") + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("YEARFRAC requires start date to be number argument") } - endDate, err := getValueAsTime(args[1].Value(), epoch) - if err != nil { - return MakeErrorResult("incorrect end date") + startDate := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("YEARFRAC requires end date to be number argument") } - return yearFracFromTime(startDate, endDate, basis) + endDate := args[1].ValueNumber + yf, errResult := yearFrac(startDate, endDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + return MakeNumberResult(yf) } -func yearFrac(startDate, endDate float64, basis int) Result { - return yearFracFromTime(dateFromDays(startDate), dateFromDays(endDate), basis) -} - -func yearFracFromTime(startDate, endDate time.Time, basis int) Result { +// yearFrac returns float64 fraction of the year and Result value which can be of ResultTypeError type if an error occurs or ResultTypeEmpty if doesn't. +func yearFrac(startDateF, endDateF float64, basis int) (float64, Result) { + startDate, endDate := dateFromDays(startDateF), dateFromDays(endDateF) startDateS := startDate.Unix() endDateS := endDate.Unix() - sy, sm, sd := startDate.Date() - ey, em, ed := endDate.Date() - + if startDateS == endDateS { + return 0, empty + } + sy, smM, sd := startDate.Date() + ey, emM, ed := endDate.Date() + sm, em := int(smM), int(emM) + var dayDiff, daysInYear float64 switch basis { case 0: - if sd == 31 && ed == 31 { - sd = 30 - ed = 30 - } else if sd == 31 { - sd = 30 - } else if sd == 30 && ed == 31 { - ed = 30 + if sd == 31 { + sd-- } - return MakeNumberResult(float64(((ed + int(em) * 30 + ey * 360) - (sd + int(sm) * 30 + sy * 360))) / 360) - case 1: - var ylength = 365.0 - if (sy == ey || ((sy + 1) == ey) && ((sm > em) || ((sm == em) && (sd >= ed)))) { - if ((sy == ey && isLeapYear(sy)) || feb29Between(startDate, endDate) || (em == 1 && ed == 29)) { - ylength = 366.0 + if sd == 30 && ed == 31 { + ed-- + } else if leap := isLeapYear(sy); sm == 2 && ((leap && sd == 29) || (!leap && sd == 28)) { + sd = 30 + if leap := isLeapYear(ey); em == 2 && ((leap && ed == 29) || (!leap && ed == 28)) { + ed = 30 + } + } + dayDiff = float64((ey - sy) * 360 + (em - sm) * 30 + (ed - sd)) + daysInYear = 360 + case 1: + dayDiff = endDateF - startDateF + isYearDifferent := sy != ey + if isYearDifferent && (ey != sy + 1 || sm < em || (sm == em && sd < ed)) { + dayCount := 0 + for y := sy; y <= ey; y++ { + dayCount += getDaysInYear(y, 1) + } + daysInYear = float64(dayCount) / float64(ey - sy + 1) + } else { + if !isYearDifferent && isLeapYear(sy) { + daysInYear = 366 + } else { + if isYearDifferent && ((isLeapYear(sy) && (sm < 2 || (sm == 2 && sd <= 29))) || (isLeapYear(ey) && (em > 2 || (em == 2 && ed == 29)))) { + daysInYear = 366 + } else { + daysInYear = 365 + } } - return MakeNumberResult(daysBetween(startDateS, endDateS) / ylength) } - var years = float64((ey - sy) + 1) - var days = float64((makeDateS(ey + 1, time.January, 1) - makeDateS(sy, time.January, 1)) / 86400) - var average = days / years - return MakeNumberResult(daysBetween(startDateS, endDateS) / average) case 2: - return MakeNumberResult(daysBetween(startDateS, endDateS) / 360.0) + dayDiff = endDateF - startDateF + daysInYear = 360 case 3: - return MakeNumberResult(daysBetween(startDateS, endDateS) / 365.0) - case 4: - return MakeNumberResult(float64(((ed + int(em) * 30 + ey * 360) - (sd + int(sm) * 30 + sy * 360))) / 360.0) + dayDiff = endDateF - startDateF + daysInYear = 365 + case 4: + if sd == 31 { + sd-- + } + if ed == 31 { + ed-- + } + dayDiff = float64((ey - sy) * 360 + (em - sm) * 30 + (ed - sd)) + daysInYear = 360 + default: + return 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis for YearFrac") } - return MakeErrorResultType(ErrorTypeValue, "") + return dayDiff / daysInYear, empty } func getDaysInYear(year, basis int) int { @@ -979,3 +1012,23 @@ func getDaysInYearRange(from, to, basis int) int { func basis30(basis int) bool { return basis == 0 || basis == 4 } + +func parseDate(arg Result, dateName, funcName string) (float64, Result) { + var date float64 + switch arg.Type { + case ResultTypeNumber: + date = float64(int(arg.ValueNumber)) + case ResultTypeString: + dateFromString := DateValue([]Result{arg}) + if dateFromString.Type == ResultTypeError { + return 0, MakeErrorResult("Incorrect " + dateName + " date for " + funcName) + } + date = dateFromString.ValueNumber + default: + return 0, MakeErrorResult("Incorrect argument for " + funcName) + } + if date < 0 { + return 0, MakeErrorResultType(ErrorTypeNum, dateName + " should be non negative") + } + return date, empty +} diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index 4aa8d671..7b5d7317 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -44,74 +44,41 @@ func init() { RegisterFunction("NOMINAL", Nominal) RegisterFunction("NPER", Nper) RegisterFunction("NPV", Npv) + RegisterFunction("ODDLPRICE", Oddlprice) + RegisterFunction("ODDLYIELD", Oddlyield) RegisterFunction("PDURATION", Pduration) RegisterFunction("_xlfn.PDURATION", Pduration) + RegisterFunction("PMT", Pmt) + RegisterFunction("PPMT", Ppmt) + RegisterFunction("PRICE", Price) + RegisterFunction("PRICEDISC", Pricedisc) + RegisterFunction("PRICEMAT", Pricemat) + RegisterFunction("PV", Pv) + RegisterFunction("RATE", Rate) + RegisterFunction("RECEIVED", Received) + RegisterFunction("RRI", Rri) + RegisterFunction("_xlfn.RRI", Rri) + RegisterFunction("SLN", Sln) + RegisterFunction("SYD", Syd) + RegisterFunction("TBILLEQ", Tbilleq) + RegisterFunction("TBILLPRICE", Tbillprice) + RegisterFunction("TBILLYIELD", Tbillyield) + RegisterFunction("VDB", Vdb) } -// Duration implements the Excel DURATION function. -func Duration(args []Result) Result { - parsedArgs, err := parseDurationData(args, "DURATION") - if err.Type == ResultTypeError { - return err +func getSettlementMaturity(settlementResult, maturityResult Result, funcName string) (float64, float64, Result) { + settlementDate, errResult := parseDate(settlementResult, "settlement date", funcName) + if errResult.Type == ResultTypeError { + return 0, 0, errResult } - settlementDate := parsedArgs.settlementDate - maturityDate := parsedArgs.maturityDate - coupon := parsedArgs.coupon - yield := parsedArgs.yield - freq := parsedArgs.freq - basis := parsedArgs.basis - - return getDuration(settlementDate, maturityDate, coupon, yield, freq, basis) -} - -// Mduration implements the Excel MDURATION function. -func Mduration(args []Result) Result { - parsedArgs, err := parseDurationData(args, "MDURATION") - if err.Type == ResultTypeError { - return err + maturityDate, errResult := parseDate(maturityResult, "maturity date", funcName) + if errResult.Type == ResultTypeError { + return 0, 0, errResult } - settlementDate := parsedArgs.settlementDate - maturityDate := parsedArgs.maturityDate - coupon := parsedArgs.coupon - yield := parsedArgs.yield - freq := parsedArgs.freq - basis := parsedArgs.basis - - duration := getDuration(settlementDate, maturityDate, coupon, yield, freq, basis) - if duration.Type == ResultTypeError { - return duration + if settlementDate >= maturityDate { + return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName + " requires maturity date to be later than settlement date") } - mDuration := duration.ValueNumber / (1.0 + yield / freq) - return MakeNumberResult(mDuration) -} - -// Pduration implements the Excel PDURATION function. -func Pduration(args []Result) Result { - if len(args) != 3 { - return MakeErrorResult("PDURATION requires three number arguments") - } - if args[0].Type != ResultTypeNumber { - return MakeErrorResult("PDURATION requires rate to be number argument") - } - rate := args[0].ValueNumber - if rate <= 0 { - return MakeErrorResultType(ErrorTypeNum, "PDURATION requires rate to be positive") - } - if args[1].Type != ResultTypeNumber { - return MakeErrorResult("PDURATION requires current value to be number argument") - } - currentValue := args[1].ValueNumber - if currentValue <= 0 { - return MakeErrorResultType(ErrorTypeNum, "PDURATION requires current value to be positive") - } - if args[2].Type != ResultTypeNumber { - return MakeErrorResult("PDURATION requires specified value to be number argument") - } - specifiedValue := args[2].ValueNumber - if specifiedValue <= 0 { - return MakeErrorResultType(ErrorTypeNum, "PDURATION requires specified value to be positive") - } - return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate)) + return settlementDate, maturityDate, empty } type couponArgs struct { @@ -127,12 +94,14 @@ func Coupdaybs(args []Result) Result { if err.Type == ResultTypeError { return err } - settlementDate := dateFromDays(parsedArgs.settlementDate) - maturityDate := dateFromDays(parsedArgs.maturityDate) - freq := parsedArgs.freq - basis := parsedArgs.basis + return MakeNumberResult(coupdaybs(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) +} + +func coupdaybs(settlementDateF, maturityDateF float64, freq, basis int) float64 { + settlementDate := dateFromDays(settlementDateF) + maturityDate := dateFromDays(maturityDateF) pcd := couppcd(settlementDate, maturityDate, freq, basis) - return MakeNumberResult(getDiff(pcd, settlementDate, basis)) + return getDiff(pcd, settlementDate, basis) } // Coupdays implements the Excel COUPDAYS function. @@ -141,16 +110,18 @@ func Coupdays(args []Result) Result { if err.Type == ResultTypeError { return err } - settlementDate := dateFromDays(parsedArgs.settlementDate) - maturityDate := dateFromDays(parsedArgs.maturityDate) - freq := parsedArgs.freq - basis := parsedArgs.basis + return MakeNumberResult(coupdays(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) +} + +func coupdays(settlementDateF, maturityDateF float64, freq, basis int) float64 { + settlementDate := dateFromDays(settlementDateF) + maturityDate := dateFromDays(maturityDateF) if basis == 1 { pcd := couppcd(settlementDate, maturityDate, freq, 1) next := pcd.AddDate(0, 12 / freq, 0) - return MakeNumberResult(getDiff(pcd, next, basis)) + return getDiff(pcd, next, basis) } - return MakeNumberResult(float64(getDaysInYear(0, basis)) / float64(freq)) + return float64(getDaysInYear(0, basis)) / float64(freq) } // Coupdaysnc implements the Excel COUPDAYSNC function. @@ -159,12 +130,14 @@ func Coupdaysnc(args []Result) Result { if err.Type == ResultTypeError { return err } - settlementDate := dateFromDays(parsedArgs.settlementDate) - maturityDate := dateFromDays(parsedArgs.maturityDate) - freq := parsedArgs.freq - basis := parsedArgs.basis + return MakeNumberResult(coupdaysnc(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) +} + +func coupdaysnc(settlementDateF, maturityDateF float64, freq, basis int) float64 { + settlementDate := dateFromDays(settlementDateF) + maturityDate := dateFromDays(maturityDateF) ncd := coupncd(settlementDate, maturityDate, freq) - return MakeNumberResult(getDiff(settlementDate, ncd, basis)) + return getDiff(settlementDate, ncd, basis) } // Couppcd implements the Excel COUPPCD function. @@ -188,11 +161,9 @@ func Coupnum(args []Result) Result { if err.Type == ResultTypeError { return err } - settlementDate := dateFromDays(parsedArgs.settlementDate) - maturityDate := dateFromDays(parsedArgs.maturityDate) freq := parsedArgs.freq basis := parsedArgs.basis - cn, err := coupnum(settlementDate, maturityDate, freq, basis) + cn, err := coupnum(parsedArgs.settlementDate, parsedArgs.maturityDate, freq, basis) if err.Type == ResultTypeError { return err } @@ -227,21 +198,11 @@ func coupncd(settlementDate, maturityDate time.Time, freq int) time.Time { func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) { argsNum := len(args) if argsNum != 3 && argsNum != 4 { - return nil, MakeErrorResult(funcName + " requires four arguments") + return nil, MakeErrorResult(funcName + " requires three or four arguments") } - if args[0].Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires settlement date to be number argument") - } - settlementDate := args[0].ValueNumber - if settlementDate < 0 { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires settlement date to be non negative") - } - if args[1].Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires maturity date to be number argument") - } - maturityDate := args[1].ValueNumber - if maturityDate <= settlementDate { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires maturity date to be later than settlement date") + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], funcName) + if errResult.Type == ResultTypeError { + return nil, errResult } if args[2].Type != ResultTypeNumber { return nil, MakeErrorResult(funcName + " requires frequency to be number argument") @@ -251,7 +212,7 @@ func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) { return nil, MakeErrorResult("Incorrect frequency for " + funcName) } basis := 0 - if argsNum == 4 { + if argsNum == 4 && args[3].Type != ResultTypeEmpty { if args[3].Type != ResultTypeNumber { return nil, MakeErrorResult(funcName + " requires basis to be number argument") } @@ -265,7 +226,7 @@ func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) { maturityDate, int(freq), basis, - }, MakeEmptyResult() + }, empty } // couppcd finds last coupon date before settlement (can be equal to settlement). @@ -284,23 +245,23 @@ func couppcd(settlementDate, maturityDate time.Time, freq, basis int) time.Time } // coupnum gets count of coupon dates. -func coupnum(settlementDate, maturityDate time.Time, freq, basis int) (float64, Result) { +func coupnum(settlementDateF, maturityDateF float64, freq, basis int) (float64, Result) { + settlementDate, maturityDate := dateFromDays(settlementDateF), dateFromDays(maturityDateF) if maturityDate.After(settlementDate) { aDate := couppcd(settlementDate, maturityDate, freq, basis) months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month()) - return float64(months * freq) / 12.0, MakeEmptyResult() + return float64(months * freq) / 12.0, empty } return 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date") } // getDuration returns the Macauley duration for an assumed par value of $100. It is defined as the weighted average of the present value of cash flows, and is used as a measure of a bond price's response to changes in yield. func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis int) Result { - fracResult := yearFrac(settlementDate, maturityDate, basis) - if fracResult.Type == ResultTypeError { - return fracResult + frac, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult } - frac := fracResult.ValueNumber - coups, err := coupnum(dateFromDays(settlementDate), dateFromDays(maturityDate), int(freq), basis) + coups, err := coupnum(settlementDate, maturityDate, int(freq), basis) if err.Type == ResultTypeError { return err } @@ -339,42 +300,17 @@ type durationArgs struct { // validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments func parseDurationData(args []Result, funcName string) (*durationArgs, Result) { - if len(args) != 5 && len(args) != 6 { + argsNum := len(args) + if argsNum != 5 && argsNum != 6 { return nil, MakeErrorResult(funcName + " requires five or six arguments") } - var settlementDate, maturityDate float64 - settlementResult := args[0] - switch settlementResult.Type { - case ResultTypeNumber: - settlementDate = float64(int(settlementResult.ValueNumber)) - case ResultTypeString: - settlementFromString := DateValue([]Result{settlementResult}) - if settlementFromString.Type == ResultTypeError { - return nil, MakeErrorResult("Incorrect settltment date for " + funcName) - } - settlementDate = settlementFromString.ValueNumber - default: - return nil, MakeErrorResult("Incorrect argument for " + funcName) - } - maturityResult := args[1] - switch maturityResult.Type { - case ResultTypeNumber: - maturityDate = float64(int(maturityResult.ValueNumber)) - case ResultTypeString: - maturityFromString := DateValue([]Result{maturityResult}) - if maturityFromString.Type == ResultTypeError { - return nil, MakeErrorResult("Incorrect settltment date for " + funcName) - } - maturityDate = maturityFromString.ValueNumber - default: - return nil, MakeErrorResult("Incorrect argument for " + funcName) - } - if settlementDate >= maturityDate { - return nil, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date") + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], funcName) + if errResult.Type == ResultTypeError { + return nil, errResult } couponResult := args[2] if couponResult.Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires third argument of type number") + return nil, MakeErrorResult(funcName + " requires coupon rate of type number") } coupon := couponResult.ValueNumber if coupon < 0 { @@ -382,7 +318,7 @@ func parseDurationData(args []Result, funcName string) (*durationArgs, Result) { } yieldResult := args[3] if yieldResult.Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires fourth argument of type number") + return nil, MakeErrorResult(funcName + " requires yield rate of type number") } yield := yieldResult.ValueNumber if yield < 0 { @@ -390,21 +326,21 @@ func parseDurationData(args []Result, funcName string) (*durationArgs, Result) { } freqResult := args[4] if freqResult.Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires fifth argument of type number") + return nil, MakeErrorResult(funcName + " requires frequency of type number") } freq := float64(int(freqResult.ValueNumber)) if !checkFreq(freq) { return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") } basis := 0 - if len(args) == 6 { + if argsNum == 6 && args[5].Type != ResultTypeEmpty { basisResult := args[5] if basisResult.Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires sixth argument of type number") + return nil, MakeErrorResult(funcName + " requires basis of type number") } basis = int(basisResult.ValueNumber) if !checkBasis(basis) { - return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value") + return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for " + funcName) } } return &durationArgs{ @@ -414,7 +350,7 @@ func parseDurationData(args []Result, funcName string) (*durationArgs, Result) { yield, freq, basis, - }, MakeEmptyResult() + }, empty } func checkFreq(freq float64) bool { @@ -431,16 +367,13 @@ func Accrintm(args []Result) Result { if argsNum != 4 && argsNum != 5 { return MakeErrorResult("ACCRINTM requires four or five arguments") } - if args[0].Type != ResultTypeNumber { - return MakeErrorResult("ACCRINTM requires issue date to be number argument") + issueDate, errResult := parseDate(args[0], "issue date", "ACCRINTM") + if errResult.Type == ResultTypeError { + return errResult } - issue := args[0].ValueNumber - if args[1].Type != ResultTypeNumber { - return MakeErrorResult("ACCRINTM requires settlement date to be number argument") - } - settlement := args[1].ValueNumber - if issue >= settlement { - return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires settlement date to be later than issue date") + settlementDate, errResult := parseDate(args[1], "settlement date", "ACCRINTM") + if errResult.Type == ResultTypeError { + return errResult } if args[2].Type != ResultTypeNumber { return MakeErrorResult("ACCRINTM requires rate to be number argument") @@ -457,7 +390,7 @@ func Accrintm(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires par to be positive number argument") } basis := 0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("ACCRINTM requires basis to be number argument") } @@ -466,11 +399,11 @@ func Accrintm(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for ACCRINTM") } } - fracResult := yearFrac(issue, settlement, basis) - if fracResult.Type == ResultTypeError { - return fracResult + frac, errResult := yearFrac(issueDate, settlementDate, basis) + if errResult.Type == ResultTypeError { + return errResult } - return MakeNumberResult(par * rate * fracResult.ValueNumber) + return MakeNumberResult(par * rate * frac) } // Amordegrc implements the Excel AMORDEGRC function. @@ -501,11 +434,11 @@ func Amordegrc(args []Result) Result { } rate *= amorCoeff - yfResult := yearFrac(datePurchased, firstPeriod, basis) - if yfResult.Type == ResultTypeError { + yf, errResult := yearFrac(datePurchased, firstPeriod, basis) + if errResult.Type == ResultTypeError { return MakeErrorResult("incorrect dates for AMORDEGRC") } - nRate := mathRound(yfResult.ValueNumber * rate * cost) + nRate := mathRound(yf * rate * cost) cost -= nRate rest := cost - salvage @@ -541,11 +474,11 @@ func Amorlinc(args []Result) Result { rate := parsedArgs.rate basis := parsedArgs.basis - yfResult := yearFrac(datePurchased, firstPeriod, basis) - if yfResult.Type == ResultTypeError { + yf, errResult := yearFrac(datePurchased, firstPeriod, basis) + if errResult.Type == ResultTypeError { return MakeErrorResult("incorrect dates for AMORLINC") } - r0 := yfResult.ValueNumber * rate * cost + r0 := yf * rate * cost if period == 0 { return MakeNumberResult(r0) } @@ -583,19 +516,16 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) { } cost := args[0].ValueNumber if cost < 0 { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires cost to be positive") + return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires cost to be non negative") } - if args[1].Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires date purchased to be number argument") + datePurchased, errResult := parseDate(args[1], "date purchased", funcName) + if errResult.Type == ResultTypeError { + return nil, errResult } - datePurchased := args[1].ValueNumber - if datePurchased < 0 { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires date purchased to be positive") + firstPeriod, errResult := parseDate(args[2], "first period", funcName) + if errResult.Type == ResultTypeError { + return nil, errResult } - if args[2].Type != ResultTypeNumber { - return nil, MakeErrorResult(funcName + " requires first period to be number argument") - } - firstPeriod := args[2].ValueNumber if firstPeriod < datePurchased { return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires first period to be later than date purchased") } @@ -618,10 +548,10 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) { } rate := args[5].ValueNumber if rate < 0 { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be more than 0 and less than 0.5") + return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires depreciation rate to be more than 0 and less than 0.5") } basis := 0 - if argsNum == 7 { + if argsNum == 7 && args[6].Type != ResultTypeEmpty { if args[6].Type != ResultTypeNumber { return nil, MakeErrorResult(funcName + " requires basis to be number argument") } @@ -638,7 +568,7 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) { period, rate, basis, - }, MakeEmptyResult() + }, empty } func mathRound(x float64) float64 { @@ -648,7 +578,7 @@ func mathRound(x float64) float64 { type cumulArgs struct { rate float64 nPer float64 - pv float64 + presentValue float64 startPeriod float64 endPeriod float64 t int @@ -662,24 +592,24 @@ func Cumipmt(args []Result) Result { } rate := parsedArgs.rate nPer := parsedArgs.nPer - pv := parsedArgs.pv + presentValue := parsedArgs.presentValue startPeriod := parsedArgs.startPeriod endPeriod := parsedArgs.endPeriod t := parsedArgs.t - payment := pmt(rate, nPer, pv, 0, t) + payment := pmt(rate, nPer, presentValue, 0, t) interest := 0.0 if startPeriod == 1 { if t == 0 { - interest = -pv + interest = -presentValue startPeriod++ } } for i := startPeriod; i <= endPeriod; i++ { if t == 1 { - interest += fv(rate, i - 2, payment, pv, 1) - payment + interest += fv(rate, i - 2, payment, presentValue, 1) - payment } else { - interest += fv(rate, i - 1, payment, pv, 0) + interest += fv(rate, i - 1, payment, presentValue, 0) } } interest *= rate @@ -694,16 +624,16 @@ func Cumprinc(args []Result) Result { } rate := parsedArgs.rate nPer := parsedArgs.nPer - pv := parsedArgs.pv + presentValue := parsedArgs.presentValue startPeriod := parsedArgs.startPeriod endPeriod := parsedArgs.endPeriod t := parsedArgs.t - payment := pmt(rate, nPer, pv, 0, t) + payment := pmt(rate, nPer, presentValue, 0, t) principal := 0.0 if startPeriod == 1 { if t == 0 { - principal = payment + pv * rate + principal = payment + presentValue * rate } else { principal = payment } @@ -711,9 +641,9 @@ func Cumprinc(args []Result) Result { } for i := startPeriod; i <= endPeriod; i++ { if t == 1 { - principal += payment - (fv(rate, i - 2, payment, pv, 1) - payment) * rate + principal += payment - (fv(rate, i - 2, payment, presentValue, 1) - payment) * rate } else { - principal += payment - fv(rate, i - 1, payment, pv, 0) * rate + principal += payment - fv(rate, i - 1, payment, presentValue, 0) * rate } } return MakeNumberResult(principal) @@ -740,8 +670,8 @@ func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) { if args[2].Type != ResultTypeNumber { return nil, MakeErrorResult(funcName + " requires present value to be number argument") } - pv := args[2].ValueNumber - if pv <= 0 { + presentValue := args[2].ValueNumber + if presentValue <= 0 { return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires present value to be positive number argument") } if args[3].Type != ResultTypeNumber { @@ -762,7 +692,7 @@ func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) { return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be later or equal to start period") } if endPeriod > nPer { - return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires periods to be in number of periods range") + return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires periods to be in periods range") } t := int(args[5].ValueNumber) if t != 0 && t != 1 { @@ -771,11 +701,11 @@ func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) { return &cumulArgs{ rate, nPer, - pv, + presentValue, startPeriod, endPeriod, t, - }, MakeEmptyResult() + }, empty } func pmt(rate, periods, present, future float64, t int ) float64 { @@ -846,7 +776,7 @@ func Db(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect period for DB") } month := 12.0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("DB requires month to be number argument") } @@ -915,13 +845,13 @@ func Ddb(args []Result) Result { } period := args[3].ValueNumber if period < 1 { - return MakeErrorResultType(ErrorTypeNum, "DDB requires period to be positive") + return MakeErrorResultType(ErrorTypeNum, "DDB requires period to be not less than one") } if period > life { return MakeErrorResultType(ErrorTypeNum, "Incorrect period for DDB") } factor := 2.0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("DDB requires factor to be number argument") } @@ -931,29 +861,7 @@ func Ddb(args []Result) Result { } } - oldValue := 0.0 - rate := factor / life - if rate >= 1 { - rate = 1 - if period == 1 { - oldValue = cost - } - } else { - oldValue = cost * math.Pow(1 - rate, period - 1) - } - newValue := cost * math.Pow(1 - rate, period) - - var ddb float64 - - if newValue < salvage { - ddb = oldValue - salvage - } else { - ddb = oldValue - newValue - } - if ddb < 0 { - ddb = 0 - } - return MakeNumberResult(ddb) + return MakeNumberResult(getDDB(cost, salvage, life, period, factor)) } // Disc implements the Excel DISC function. @@ -962,16 +870,9 @@ func Disc(args []Result) Result { if argsNum != 4 && argsNum != 5 { return MakeErrorResult("DISC requires four or five arguments") } - if args[0].Type != ResultTypeNumber { - return MakeErrorResult("DISC requires settlement date to be number argument") - } - settlement := args[0].ValueNumber - if args[1].Type != ResultTypeNumber { - return MakeErrorResult("DISC requires maturity date to be number argument") - } - maturity := args[1].ValueNumber - if settlement >= maturity { - return MakeErrorResultType(ErrorTypeNum, "DISC requires maturity date to be later than settlement date") + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "DISC") + if errResult.Type == ResultTypeError { + return errResult } if args[2].Type != ResultTypeNumber { return MakeErrorResult("DISC requires pr to be number argument") @@ -988,7 +889,7 @@ func Disc(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "DISC requires redemption to be positive number argument") } basis := 0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("DISC requires basis to be number argument") } @@ -997,11 +898,11 @@ func Disc(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for DISC") } } - fracResult := yearFrac(settlement, maturity, basis) - if fracResult.Type == ResultTypeError { - return fracResult + frac, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult } - return MakeNumberResult((redemption - pr) / redemption / fracResult.ValueNumber) + return MakeNumberResult((redemption - pr) / redemption / frac) } // Dollarde implements the Excel DOLLARDE function. @@ -1083,9 +984,25 @@ func parseDollarArgs(args []Result, funcName string) (float64, float64, Result) } fraction := float64(int(args[1].ValueNumber)) if fraction < 0 { - return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName + " requires fraction to be positive number") + return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName + " requires fraction to be non negative number") } - return dollar, fraction, MakeEmptyResult() + return dollar, fraction, empty +} + +// Duration implements the Excel DURATION function. +func Duration(args []Result) Result { + parsedArgs, err := parseDurationData(args, "DURATION") + if err.Type == ResultTypeError { + return err + } + settlementDate := parsedArgs.settlementDate + maturityDate := parsedArgs.maturityDate + coupon := parsedArgs.coupon + yield := parsedArgs.yield + freq := parsedArgs.freq + basis := parsedArgs.basis + + return getDuration(settlementDate, maturityDate, coupon, yield, freq, basis) } // Effect implements the Excel EFFECT function. @@ -1131,18 +1048,15 @@ func Fv(args []Result) Result { return MakeErrorResult("FV requires payment to be number argument") } pmt := args[2].ValueNumber - if args[3].Type != ResultTypeNumber { - return MakeErrorResult("FV requires payment to be number argument") - } - pv := 0.0 - if argsNum >= 4 { + presentValue := 0.0 + if argsNum >= 4 && args[3].Type != ResultTypeEmpty { if args[3].Type != ResultTypeNumber { return MakeErrorResult("FV requires present value to be number argument") } - pv = args[3].ValueNumber + presentValue = args[3].ValueNumber } t := 0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("FV requires type to be number argument") } @@ -1151,7 +1065,7 @@ func Fv(args []Result) Result { t = 1 } } - return MakeNumberResult(fv(rate, nPer, pmt, pv, t)) + return MakeNumberResult(fv(rate, nPer, pmt, presentValue, t)) } // Fvschedule implements the Excel FVSCHEDULE function. @@ -1178,7 +1092,7 @@ func Fvschedule(args []Result) Result { } return MakeNumberResult(principal) default: - return MakeErrorResult("FVSCHEDULE requires schedule to be of array type") + return MakeErrorResult("FVSCHEDULE requires schedule to be of number or array type") } } @@ -1188,16 +1102,9 @@ func Intrate(args []Result) Result { if argsNum != 4 && argsNum != 5 { return MakeErrorResult("INTRATE requires four or five arguments") } - if args[0].Type != ResultTypeNumber { - return MakeErrorResult("INTRATE requires settlement date to be number argument") - } - settlement := args[0].ValueNumber - if args[1].Type != ResultTypeNumber { - return MakeErrorResult("INTRATE requires maturity date to be number argument") - } - maturity := args[1].ValueNumber - if settlement >= maturity { - return MakeErrorResultType(ErrorTypeNum, "INTRATE requires maturity date to be later than settlement date") + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "INTRATE") + if errResult.Type == ResultTypeError { + return errResult } if args[2].Type != ResultTypeNumber { return MakeErrorResult("INTRATE requires investment to be number argument") @@ -1214,7 +1121,7 @@ func Intrate(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "INTRATE requires redemption to be positive number argument") } basis := 0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("INTRATE requires basis to be number argument") } @@ -1223,18 +1130,18 @@ func Intrate(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for INTRATE") } } - fracResult := yearFrac(settlement, maturity, basis) - if fracResult.Type == ResultTypeError { - return fracResult + frac, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult } - return MakeNumberResult((redemption - investment) / investment / fracResult.ValueNumber) + return MakeNumberResult((redemption - investment) / investment / frac) } // Ipmt implements the Excel IPMT function. func Ipmt(args []Result) Result { argsNum := len(args) if argsNum < 4 || argsNum > 6 { - return MakeErrorResult("IPMT requires six arguments") + return MakeErrorResult("IPMT requires number of arguments in range between four and six") } if args[0].Type != ResultTypeNumber { return MakeErrorResult("IPMT requires rate to be number argument") @@ -1259,22 +1166,27 @@ func Ipmt(args []Result) Result { } presentValue := args[3].ValueNumber futureValue := 0.0 - if argsNum > 4 { + if argsNum > 4 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("IPMT requires future value to be number argument") } futureValue = args[4].ValueNumber } t := 0 - if argsNum == 6 { + if argsNum == 6 && args[5].Type != ResultTypeEmpty { if args[5].Type != ResultTypeNumber { - return MakeErrorResult("IPMT requires start period to be number argument") + return MakeErrorResult("IPMT requires type to be number argument") } t = int(args[5].ValueNumber) - if t != 0 { + if t != 0 { t = 1 } } + + return MakeNumberResult(ipmt(rate, period, nPer, presentValue, futureValue, t)) +} + +func ipmt(rate, period, nPer, presentValue, futureValue float64, t int) float64 { payment := pmt(rate, nPer, presentValue, futureValue, t) var interest float64 if period == 1 { @@ -1290,8 +1202,7 @@ func Ipmt(args []Result) Result { interest = fv(rate, period - 1, payment, presentValue, 0) } } - - return MakeNumberResult(interest * rate) + return interest * rate } // Irr implements the Excel IRR function. @@ -1301,7 +1212,7 @@ func Irr(args []Result) Result { return MakeErrorResult("IRR requires one or two arguments") } if args[0].Type != ResultTypeList && args[0].Type != ResultTypeArray { - return MakeErrorResult("IRR requires values to be range argument") + return MakeErrorResult("IRR requires values to be of array type") } valuesR := arrayFromRange(args[0]) values := []float64{} @@ -1317,7 +1228,7 @@ func Irr(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "") } guess := 0.1 - if argsNum == 2 { + if argsNum == 2 && args[1].Type != ResultTypeEmpty { if args[1].Type != ResultTypeNumber { return MakeErrorResult("IRR requires guess to be number argument") } @@ -1401,7 +1312,7 @@ func irrResultDeriv(values, dates []float64, rate float64) float64 { // Ispmt implements the Excel ISPMT function. func Ispmt(args []Result) Result { if len(args) != 4 { - return MakeErrorResult("ISPMT requires six arguments") + return MakeErrorResult("ISPMT requires four arguments") } if args[0].Type != ResultTypeNumber { return MakeErrorResult("ISPMT requires rate to be number argument") @@ -1421,9 +1332,30 @@ func Ispmt(args []Result) Result { if args[3].Type != ResultTypeNumber { return MakeErrorResult("ISPMT requires present value to be number argument") } - pv := args[3].ValueNumber + presentValue := args[3].ValueNumber - return MakeNumberResult(pv * rate * (period / nPer - 1)) + return MakeNumberResult(presentValue * rate * (period / nPer - 1)) +} + +// Mduration implements the Excel MDURATION function. +func Mduration(args []Result) Result { + parsedArgs, err := parseDurationData(args, "MDURATION") + if err.Type == ResultTypeError { + return err + } + settlementDate := parsedArgs.settlementDate + maturityDate := parsedArgs.maturityDate + coupon := parsedArgs.coupon + yield := parsedArgs.yield + freq := parsedArgs.freq + basis := parsedArgs.basis + + duration := getDuration(settlementDate, maturityDate, coupon, yield, freq, basis) + if duration.Type == ResultTypeError { + return duration + } + mDuration := duration.ValueNumber / (1.0 + yield / freq) + return MakeNumberResult(mDuration) } // Mirr implements the Excel MIRR function. @@ -1432,7 +1364,7 @@ func Mirr(args []Result) Result { return MakeErrorResult("MIRR requires three arguments") } if args[0].Type != ResultTypeList && args[0].Type != ResultTypeArray { - return MakeErrorResult("MIRR requires values to be range argument") + return MakeErrorResult("MIRR requires values to be of array type") } if args[1].Type != ResultTypeNumber { return MakeErrorResult("MIRR requires finance rate to be number argument") @@ -1492,7 +1424,7 @@ func Nominal(args []Result) Result { } effect := args[0].ValueNumber if effect <= 0 { - return MakeErrorResultType(ErrorTypeNum, "NOMINAL requires effect interest rate to be positive number argument") + return MakeErrorResultType(ErrorTypeNum, "NOMINAL requires effect interest rate to be positive") } if args[1].Type != ResultTypeNumber { return MakeErrorResult("NOMINAL requires number of compounding periods to be number argument") @@ -1521,16 +1453,16 @@ func Nper(args []Result) Result { if args[2].Type != ResultTypeNumber { return MakeErrorResult("NPER requires present value to be number argument") } - pv := args[2].ValueNumber - fv := 0.0 - if argsNum >= 4 { + presentValue := args[2].ValueNumber + futureValue := 0.0 + if argsNum >= 4 && args[3].Type != ResultTypeEmpty { if args[3].Type != ResultTypeNumber { return MakeErrorResult("NPER requires future value to be number argument") } - fv = args[3].ValueNumber + futureValue = args[3].ValueNumber } t := 0.0 - if argsNum == 5 { + if argsNum == 5 && args[4].Type != ResultTypeEmpty { if args[4].Type != ResultTypeNumber { return MakeErrorResult("NPER requires type to be number argument") } @@ -1539,8 +1471,8 @@ func Nper(args []Result) Result { t = 1 } } - num := pmt * (1 + rate * t) - fv * rate - den := (pv * rate + pmt * (1 + rate * t)) + num := pmt * (1 + rate * t) - futureValue * rate + den := (presentValue * rate + pmt * (1 + rate * t)) return MakeNumberResult(math.Log(num / den) / math.Log(1 + rate)) } @@ -1579,3 +1511,949 @@ func Npv(args []Result) Result { } return MakeNumberResult(npv) } + +// Oddlprice implements the Excel ODDLPRICE function. +func Oddlprice(args []Result) Result { + if len(args) != 8 && len(args) != 9 { + return MakeErrorResult("ODDLPRICE requires eight or nine arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "ODDLPRICE") + if errResult.Type == ResultTypeError { + return errResult + } + lastInterestDate, errResult := parseDate(args[2], "issue date", "ODDLPRICE") + if errResult.Type == ResultTypeError { + return errResult + } + if lastInterestDate >= settlementDate { + return MakeErrorResultType(ErrorTypeNum, "Last interest date should be before settlement date") + } + rateResult := args[3] + if rateResult.Type != ResultTypeNumber { + return MakeErrorResult("ODDLPRICE requires rate of type number") + } + rate := rateResult.ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "Rate should be non negative") + } + yieldResult := args[4] + if yieldResult.Type != ResultTypeNumber { + return MakeErrorResult("ODDLPRICE requires yield of type number") + } + yield := yieldResult.ValueNumber + if yield < 0 { + return MakeErrorResultType(ErrorTypeNum, "Yield should be non negative") + } + redemptionResult := args[5] + if redemptionResult.Type != ResultTypeNumber { + return MakeErrorResult("ODDLPRICE requires redemption of type number") + } + redemption := redemptionResult.ValueNumber + if redemption < 0 { + return MakeErrorResultType(ErrorTypeNum, "Yield should be non negative") + } + freqResult := args[6] + if freqResult.Type != ResultTypeNumber { + return MakeErrorResult("ODDLPRICE requires frequency of type number") + } + freq := float64(int(freqResult.ValueNumber)) + if !checkFreq(freq) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") + } + basis := 0 + if len(args) == 8 { + basisResult := args[7] + if basisResult.Type != ResultTypeNumber { + return MakeErrorResult("ODDLPRICE requires basis of type number") + } + basis = int(basisResult.ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for ODDLPRICE") + } + } + + dc, errResult := yearFrac(lastInterestDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dc *= freq + dsc, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dsc *= freq + a, errResult := yearFrac(lastInterestDate, settlementDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + a *= freq + + p := redemption + dc * 100 * rate / freq + p /= dsc * yield / freq + 1 + p -= a * 100 * rate / freq + + return MakeNumberResult(p) +} + +// Oddlyield implements the Excel ODDLYIELD function. +func Oddlyield(args []Result) Result { + if len(args) != 7 && len(args) != 8 { + return MakeErrorResult("ODDLYIELD requires seven or eight arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "ODDLYIELD") + if errResult.Type == ResultTypeError { + return errResult + } + lastInterestDate, errResult := parseDate(args[2], "issue date", "ODDLPRICE") + if errResult.Type == ResultTypeError { + return errResult + } + if lastInterestDate >= settlementDate { + return MakeErrorResultType(ErrorTypeNum, "Last interest date should be before settlement date") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("ODDLYIELD requires rate of type number") + } + rate := args[3].ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "Rate should be non negative") + } + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("ODDLYIELD requires present value of type number") + } + pr := args[4].ValueNumber + if pr <= 0 { + return MakeErrorResultType(ErrorTypeNum, "Present value should be positive") + } + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("ODDLYIELD requires redemption of type number") + } + redemption := args[5].ValueNumber + if redemption < 0 { + return MakeErrorResultType(ErrorTypeNum, "Yield should be non negative") + } + if args[6].Type != ResultTypeNumber { + return MakeErrorResult("ODDLYIELD requires frequency of type number") + } + freq := float64(int(args[6].ValueNumber)) + if !checkFreq(freq) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") + } + basis := 0 + if len(args) == 8 { + if args[7].Type != ResultTypeNumber { + return MakeErrorResult("ODDLYIELD requires basis of type number") + } + basis = int(args[7].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for ODDLYIELD") + } + } + + dc, errResult := yearFrac(lastInterestDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dc *= freq + dsc, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dsc *= freq + a, errResult := yearFrac(lastInterestDate, settlementDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + a *= freq + + yield := redemption + dc * 100 * rate / freq + yield /= pr + a * 100 * rate / freq + yield-- + yield *= freq / dsc + + return MakeNumberResult(yield) +} + +// Pduration implements the Excel PDURATION function. +func Pduration(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("PDURATION requires three arguments") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("PDURATION requires rate to be number argument") + } + rate := args[0].ValueNumber + if rate <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PDURATION requires rate to be positive") + } + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("PDURATION requires current value to be number argument") + } + currentValue := args[1].ValueNumber + if currentValue <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PDURATION requires current value to be positive") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PDURATION requires specified value to be number argument") + } + specifiedValue := args[2].ValueNumber + if specifiedValue <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PDURATION requires specified value to be positive") + } + return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate)) +} + +// Pmt implements the Excel PMT function. +func Pmt(args []Result) Result { + argsNum := len(args) + if argsNum < 3 || argsNum > 5 { + return MakeErrorResult("PMT requires number of arguments in range of 3 and 5") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("PMT requires rate to be number argument") + } + rate := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("PMT requires number of periods to be number argument") + } + nPer := args[1].ValueNumber + if nPer == 0 { + return MakeErrorResultType(ErrorTypeNum, "PMT requires number of periods to be not equal to 0") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PMT requires present value to be number argument") + } + presentValue := args[2].ValueNumber + futureValue := 0.0 + if argsNum >= 4 && args[3].Type != ResultTypeEmpty { + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PMT requires future value to be number argument") + } + futureValue = args[3].ValueNumber + } + t := 0.0 + if argsNum == 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PMT requires type to be number argument") + } + t = args[4].ValueNumber + if t != 0 { + t = 1 + } + } + var result float64 + if rate == 0 { + result = (presentValue + futureValue) / nPer + } else { + term := math.Pow(1 + rate, nPer) + if t == 1 { + result = (futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term)) / (1 + rate) + } else { + result = futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term) + } + } + return MakeNumberResult(-result) +} + +// Ppmt implements the Excel PPPMT function. +func Ppmt(args []Result) Result { + argsNum := len(args) + if argsNum < 4 || argsNum > 6 { + return MakeErrorResult("PPMT requires number of arguments in range of four and six") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires rate to be number argument") + } + rate := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires period to be number argument") + } + period := args[1].ValueNumber + if period <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PPMT requires period to be positive") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires number of periods to be number argument") + } + nPer := args[2].ValueNumber + if nPer < period { + return MakeErrorResultType(ErrorTypeNum, "PPMT requires number of periods to be not less than period") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires present value to be number argument") + } + presentValue := args[3].ValueNumber + futureValue := 0.0 + if argsNum >= 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires future value to be number argument") + } + futureValue = args[4].ValueNumber + } + t := 0 + if argsNum == 6 && args[5].Type != ResultTypeEmpty { + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("PPMT requires type to be number argument") + } + t = int(args[5].ValueNumber) + if t != 0 { + t = 1 + } + } + return MakeNumberResult(pmt(rate, nPer, presentValue, futureValue, t) - ipmt(rate, period, nPer, presentValue, futureValue, t)) +} + +// Price implements the Excel PRICE function. +func Price(args []Result) Result { + argsNum := len(args) + if argsNum != 6 && argsNum != 7 { + return MakeErrorResult("PRICE requires six or seven arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "PRICE") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PRICE requires rate of type number") + } + rate := args[2].ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICE requires rate to not be negative") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PRICE requires yield of type number") + } + yield := args[3].ValueNumber + if yield < 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICE requires yield to not be negative") + } + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PRICE requires redemption to be number argument") + } + redemption := args[4].ValueNumber + if redemption <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICE requires redemption to be positive number argument") + } + freqResult := args[5] + if freqResult.Type != ResultTypeNumber { + return MakeErrorResult("PRICE requires frequency of type number") + } + freqF := freqResult.ValueNumber + if !checkFreq(freqF) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") + } + freq := int(freqF) + basis := 0 + if argsNum == 7 { + if args[6].Type != ResultTypeNumber { + return MakeErrorResult("PRICE requires basis to be number argument") + } + basis = int(args[6].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for PRICE") + } + } + e := coupdays(settlementDate, maturityDate, freq, basis) + dsc := coupdaysnc(settlementDate, maturityDate, freq, basis) / e + n, errResult := coupnum(settlementDate, maturityDate, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + a := coupdaybs(settlementDate, maturityDate, freq, basis) + ret := redemption / math.Pow(1 + yield / freqF, n - 1 + dsc) + ret -= 100 * rate / freqF * a / e + t1 := 100 * rate / freqF + t2 := 1 + yield / freqF + for k := 0.0; k < n; k++ { + ret += t1 / math.Pow(t2, k + dsc) + } + return MakeNumberResult(ret) +} + +// Pricedisc implements the Excel PRICEDISC function. +func Pricedisc(args []Result) Result { + argsNum := len(args) + if argsNum != 4 && argsNum != 5 { + return MakeErrorResult("PRICEDISC requires four or five arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "PRICEDISC") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PRICEDISC requires discount of type number") + } + discount := args[2].ValueNumber + if discount <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICEDISC requires discount to be positive") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PRICEDISC requires redemption of type number") + } + redemption := args[3].ValueNumber + if redemption <= 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICEDISC requires redemption to be positive") + } + basis := 0 + if argsNum == 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PRICEDISC requires basis to be number argument") + } + basis = int(args[4].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for PRICEDISC") + } + } + yf, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + return MakeNumberResult(redemption * (1 - discount * yf)) +} + +// Pricemat implements the Excel PRICEMAT function. +func Pricemat(args []Result) Result { + argsNum := len(args) + if argsNum != 5 && argsNum != 6 { + return MakeErrorResult("PRICEMAT requires five or six arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "PRICEMAT") + if errResult.Type == ResultTypeError { + return errResult + } + issueDate, errResult := parseDate(args[2], "issue date", "PRICEMAT") + if errResult.Type == ResultTypeError { + return errResult + } + if issueDate >= settlementDate { + return MakeErrorResult("PRICEMAT requires issue date to be before settlement date") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PRICEMAT requires rate of type number") + } + rate := args[3].ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICEMAT requires rate to be non negative") + } + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PRICEMAT requires yield of type number") + } + yield := args[4].ValueNumber + if yield < 0 { + return MakeErrorResultType(ErrorTypeNum, "PRICEMAT requires yield to be non negative") + } + basis := 0 + if argsNum == 6 { + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("PRICEMAT requires basis to be number argument") + } + basis = int(args[5].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for PRICEMAT") + } + } + dsmyf, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dimyf, errResult := yearFrac(issueDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + ayf, errResult := yearFrac(issueDate, settlementDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + + num := 1 + dimyf * rate + den := 1 + dsmyf * yield + + return MakeNumberResult((num / den - ayf * rate) * 100) +} + +// Pv implements the Excel PV function. +func Pv(args []Result) Result { + argsNum := len(args) + if argsNum < 3 || argsNum > 5 { + return MakeErrorResult("PV requires number of arguments in range of 3 and 5") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("PV requires rate to be number argument") + } + rate := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("PV requires number of periods to be number argument") + } + nPer := args[1].ValueNumber + if nPer != float64(int(nPer)) { + return MakeErrorResultType(ErrorTypeNum, "PV requires number of periods to be integer number argument") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("PV requires payment to be number argument") + } + pmt := args[2].ValueNumber + futureValue := 0.0 + if argsNum >= 4 && args[3].Type != ResultTypeEmpty { + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("PV requires future value to be number argument") + } + futureValue = args[3].ValueNumber + } + t := 0.0 + if argsNum == 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("PV requires type to be number argument") + } + t = args[4].ValueNumber + if t != 0 { + t = 1 + } + } + if rate == 0 { + return MakeNumberResult(-pmt * nPer - futureValue) + } else { + return MakeNumberResult((((1 - math.Pow(1 + rate, nPer)) / rate) * pmt * (1 + rate * t) - futureValue) / math.Pow(1 + rate, nPer)) + } +} + +// Rate implements the Excel RATE function. +func Rate(args []Result) Result { + argsNum := len(args) + if argsNum < 3 || argsNum > 6 { + return MakeErrorResult("RATE requires number of arguments in range of three and six") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires number of periods to be number argument") + } + nPer := args[0].ValueNumber + if nPer != float64(int(nPer)) { + return MakeErrorResultType(ErrorTypeNum, "RATE requires number of periods to be integer number argument") + } + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires payment to be number argument") + } + pmt := args[1].ValueNumber + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires present value to be number argument") + } + presentValue := args[2].ValueNumber + futureValue := 0.0 + if argsNum >= 4 && args[3].Type != ResultTypeEmpty { + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires future value to be number argument") + } + futureValue = args[3].ValueNumber + } + t := 0.0 + if argsNum >= 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires type to be number argument") + } + t = args[4].ValueNumber + if t != 0 { + t = 1 + } + } + guess := 0.1 + if argsNum >= 6 && args[5].Type != ResultTypeEmpty { + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("RATE requires guess to be number argument") + } + guess = args[5].ValueNumber + } + + maxIter := 100 + iter := 0 + close := false + epsMax := 1e-6 + + rate := guess + for iter < maxIter && !close { + t1 := math.Pow(rate + 1, nPer) + t2 := math.Pow(rate + 1, nPer - 1) + rt := rate * t + 1 + p0 := pmt * (t1 - 1) + f1 := futureValue + t1 * presentValue + p0 * rt / rate + f2 := nPer * t2 * presentValue - p0 * rt / math.Pow(rate, 2) + f3 := (nPer * pmt * t2 * rt + p0 * t) / rate + + delta := f1 / (f2 + f3) + if math.Abs(delta) < epsMax { + close = true + } + iter++ + rate -= delta + } + + return MakeNumberResult(rate) +} + +// Received implements the Excel RECEIVED function. +func Received(args []Result) Result { + argsNum := len(args) + if argsNum != 4 && argsNum != 5 { + return MakeErrorResult("RECEIVED requires four or five arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "RECEIVED") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("RECEIVED requires investment to be number argument") + } + investment := args[2].ValueNumber + if investment <= 0 { + return MakeErrorResultType(ErrorTypeNum, "RECEIVED requires investment to be positive number argument") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("RECEIVED requires discount to be number argument") + } + discount := args[3].ValueNumber + if discount <= 0 { + return MakeErrorResultType(ErrorTypeNum, "RECEIVED requires discount to be positive number argument") + } + basis := 0 + if argsNum == 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("RECEIVED requires basis to be number argument") + } + basis = int(args[4].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for RECEIVED") + } + } + frac, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + return MakeNumberResult(investment / (1 - discount * frac)) +} + +// Rri implements the Excel RRI function. +func Rri(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("RRI requires three arguments") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("RRI requires number of periods to be number argument") + } + nPer := args[0].ValueNumber + if nPer <= 0 { + return MakeErrorResultType(ErrorTypeNum, "RRI requires number of periods to be positive") + } + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("RRI requires present value to be number argument") + } + presentValue := args[1].ValueNumber + if presentValue <= 0 { + return MakeErrorResultType(ErrorTypeNum, "RRI requires present value to be positive") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("RRI requires future value to be number argument") + } + futureValue := args[2].ValueNumber + if futureValue < 0 { + return MakeErrorResultType(ErrorTypeNum, "RRI requires future value to be non negative") + } + + return MakeNumberResult(math.Pow(futureValue / presentValue, 1 / nPer) - 1) +} + +// Sln implements the Excel SLN function. +func Sln(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("SLN requires three arguments") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("SLN requires cost to be number argument") + } + cost := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("SLN requires salvage to be number argument") + } + salvage := args[1].ValueNumber + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("SLN requires life to be number argument") + } + life := args[2].ValueNumber + if life == 0 { + return MakeErrorResultType(ErrorTypeDivideByZero, "SLN requires life to be non zero") + } + + return MakeNumberResult((cost - salvage ) / life) +} + +// Syd implements the Excel SYD function. +func Syd(args []Result) Result { + if len(args) != 4 { + return MakeErrorResult("SYD requires three arguments") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("SYD requires cost to be number argument") + } + cost := args[0].ValueNumber + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("SYD requires salvage to be number argument") + } + salvage := args[1].ValueNumber + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("SYD requires life to be number argument") + } + life := args[2].ValueNumber + if life <= 0 { + return MakeErrorResultType(ErrorTypeNum, "SYD requires life to be positive") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("SYD requires period to be number argument") + } + per := args[3].ValueNumber + if per <= 0 { + return MakeErrorResultType(ErrorTypeNum, "SYD requires period to be positive") + } + if per > life { + return MakeErrorResultType(ErrorTypeNum, "SYD requires period to be equal or less than life") + } + + num := (cost - salvage) * (life - per + 1) * 2 + den := life * (life + 1) + + return MakeNumberResult(num / den) +} + +// Tbilleq implements the Excel TBILLEQ function. +func Tbilleq(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("TBILLEQ requires three arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "TBILLEQ") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("TBILLEQ requires discount to be number argument") + } + dsm := maturityDate - settlementDate + if dsm > 365 { + return MakeErrorResultType(ErrorTypeNum, "TBILLEQ requires maturity to be not more than one year after settlement") + } + discount := args[2].ValueNumber + if discount <= 0 { + return MakeErrorResultType(ErrorTypeNum, "TBILLEQ requires discount to be positive number argument") + } + return MakeNumberResult((365 * discount) / (360 - discount * dsm)) +} + +// Tbillprice implements the Excel TBILLPRICE function. +func Tbillprice(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("TBILLPRICE requires three arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "TBILLPRICE") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("TBILLPRICE requires discount to be number argument") + } + dsm := maturityDate - settlementDate + if dsm > 365 { + return MakeErrorResultType(ErrorTypeNum, "TBILLPRICE requires maturity to be not more than one year after settlement") + } + discount := args[2].ValueNumber + if discount <= 0 { + return MakeErrorResultType(ErrorTypeNum, "TBILLPRICE requires discount to be positive number argument") + } + return MakeNumberResult(100 * (1 - discount * dsm / 360)) +} + +// Tbillyield implements the Excel TBILLYIELD function. +func Tbillyield(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("TBILLYIELD requires three arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "TBILLYIELD") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("TBILLYIELD requires discount to be number argument") + } + dsm := maturityDate - settlementDate + if dsm > 365 { + return MakeErrorResultType(ErrorTypeNum, "TBILLYIELD requires maturity to be not more than one year after settlement") + } + pr := args[2].ValueNumber + if pr <= 0 { + return MakeErrorResultType(ErrorTypeNum, "TBILLYIELD requires pr to be positive number argument") + } + m1 := (100 - pr) / pr + m2 := 360 /dsm + return MakeNumberResult(m1 * m2) +} + +// Vdb implements the Excel VDB function. +func Vdb(args []Result) Result { + argsNum := len(args) + if argsNum < 5 || argsNum > 7 { + return MakeErrorResult("VDB requires number of arguments to be in range between five and seven") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires cost to be number argument") + } + cost := args[0].ValueNumber + if cost < 0 { + return MakeErrorResultType(ErrorTypeNum, "VDB requires cost to be non negative") + } + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires salvage to be number argument") + } + salvage := args[1].ValueNumber + if salvage < 0 { + return MakeErrorResultType(ErrorTypeNum, "VDB requires salvage to be non negative") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires life to be number argument") + } + life := args[2].ValueNumber + if life == 0 { + return MakeErrorResultType(ErrorTypeDivideByZero, "VDB requires life to be positive") + } + if life < 0 { + return MakeErrorResultType(ErrorTypeNum, "VDB requires life to be positive") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires start period to be number argument") + } + startPeriod := args[3].ValueNumber + if startPeriod < 0 { + return MakeErrorResultType(ErrorTypeNum, "VDB requires start period to be not less than one") + } + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires end period to be number argument") + } + endPeriod := args[4].ValueNumber + if startPeriod > endPeriod { + return MakeErrorResultType(ErrorTypeNum, "Incorrect start period for VDB") + } + if endPeriod > life { + return MakeErrorResultType(ErrorTypeNum, "Incorrect end period for VDB") + } + factor := 2.0 + if argsNum > 5 { + if args[5].Type == ResultTypeEmpty { + factor = 0.0 + } else { + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires factor to be number argument") + } + factor = args[5].ValueNumber + if factor < 0 { + return MakeErrorResultType(ErrorTypeNum, "VDB requires factor to be non negative") + } + } + } + noSwitch := false + if argsNum > 6 && args[6].Type != ResultTypeEmpty { + if args[6].Type != ResultTypeNumber { + return MakeErrorResult("VDB requires no_switch to be number argument") + } + noSwitch = args[6].ValueNumber != 0 + } + + vdb := 0.0 + startInt := math.Floor(startPeriod) + endInt := math.Ceil(endPeriod) + + if noSwitch { + for i := startInt + 1; i <= endInt; i++ { + term := getDDB(cost, salvage, life, i, factor) + if i == startInt + 1 { + term *= math.Min(endPeriod, startInt + 1) - startPeriod + } else if i == endInt { + term *= endPeriod + 1 - endInt + } + vdb += term + } + } else { + life1 := life + var part float64 + if !approxEqual(startPeriod, math.Floor(startPeriod)) { + if factor == 1 { + l2 := life / 2 + if startPeriod > l2 || approxEqual(startPeriod, l2) { + part = startPeriod - l2 + startPeriod = l2 + endPeriod -= part + life1++ + } + } + } + if factor != 0 { + cost -= interVDB(cost, salvage, life, life1, startPeriod, factor) + } + vdb = interVDB(cost, salvage, life, life - startPeriod, endPeriod - startPeriod, factor) + } + return MakeNumberResult(vdb) +} + +func interVDB(cost, salvage, life, life1, period, factor float64) float64 { + var ddb, term float64 + vdb := 0.0 + endInt := math.Ceil(period) + cs := cost - salvage + nowSln := false + sln := 0.0 + + for i := 1.0; i <= endInt; i++ { + if !nowSln { + ddb = getDDB(cost, salvage, life, i, factor) + sln = cs / (life - i + 1) + if sln > ddb { + term = sln + nowSln = true + } else { + term = ddb + cs -= ddb + } + } else { + term = sln + } + if i == endInt { + term *= period + 1 - endInt + } + vdb += term + } + return vdb +} + +func getDDB(cost, salvage, life, period, factor float64) float64 { + var oldValue float64 + rate := factor / life + if rate >= 1 { + rate = 1 + if period == 1 { + oldValue = cost + } else { + oldValue = 0 + } + } else { + oldValue = cost * math.Pow(1 - rate, period - 1) + } + newValue := cost * math.Pow(1 - rate, period) + + var ddb float64 + + if newValue < salvage { + ddb = oldValue - salvage + } else { + ddb = oldValue - newValue + } + if ddb < 0 { + ddb = 0 + } + return ddb +} + +func approxEqual(a, b float64) bool { + return math.Abs(a - b) < 1.0e-6 +} diff --git a/spreadsheet/formula/fnindex.go b/spreadsheet/formula/fnindex.go index 914468b0..4ab3ffce 100644 --- a/spreadsheet/formula/fnindex.go +++ b/spreadsheet/formula/fnindex.go @@ -98,7 +98,7 @@ func Index(args []Result) Result { } row := int(rowArg.ValueNumber) - 1 col := -1 - if argsNum == 3 { + if argsNum == 3 && args[2].Type != ResultTypeEmpty { colArg := args[2].AsNumber() if colArg.Type != ResultTypeNumber { return MakeErrorResult("INDEX requires numeric col argument") @@ -192,7 +192,7 @@ func Match(args []Result) Result { } matchType := 1 - if argsNum == 3 { + if argsNum == 3 && args[2].Type != ResultTypeEmpty { if args[2].Type != ResultTypeNumber { return MakeErrorResult("MATCH requires the third argument to be a number") } @@ -354,10 +354,11 @@ func Offset(ctx Context, ev Evaluator, args []Result) Result { // VLookup implements the VLOOKUP function that returns a matching value from a // column in an array. func VLookup(args []Result) Result { - if len(args) < 3 { + argsNum := len(args) + if argsNum < 3 { return MakeErrorResult("VLOOKUP requires at least three arguments") } - if len(args) > 4 { + if argsNum > 4 { return MakeErrorResult("VLOOKUP requires at most four arguments") } lookupValue := args[0] @@ -370,7 +371,7 @@ func VLookup(args []Result) Result { return MakeErrorResult("VLOOKUP requires numeric col argument") } exactMatch := false - if len(args) == 4 { + if argsNum == 4 && args[3].Type != ResultTypeEmpty { em := args[3].AsNumber() if em.Type != ResultTypeNumber { return MakeErrorResult("VLOOKUP requires numeric match argument") @@ -547,7 +548,7 @@ func extractCol(arr Result) []Result { if len(r) > 0 { col = append(col, r[0]) } else { - col = append(col, MakeEmptyResult()) + col = append(col, empty) } } } diff --git a/spreadsheet/formula/fnmathtrig.go b/spreadsheet/formula/fnmathtrig.go index b321e0b9..9c147c45 100644 --- a/spreadsheet/formula/fnmathtrig.go +++ b/spreadsheet/formula/fnmathtrig.go @@ -1022,7 +1022,7 @@ func multinomial(args []Result) (float64, float64, Result) { return 0, 0, arg } } - return num, denom, MakeEmptyResult() + return num, denom, empty } // Multinomial implements the excel MULTINOMIAL function. diff --git a/spreadsheet/formula/fnstatistical.go b/spreadsheet/formula/fnstatistical.go index e94c4bf5..32075b8c 100644 --- a/spreadsheet/formula/fnstatistical.go +++ b/spreadsheet/formula/fnstatistical.go @@ -284,7 +284,7 @@ func checkIfsRanges(args []Result, sumRange bool, fnName string) Result { i-- // after sumRange should go column 1, not 2 } } - return MakeEmptyResult() + return empty } //getIfsMatch returns an array of indexes of cells which meets all *IFS criterias diff --git a/spreadsheet/formula/fntext.go b/spreadsheet/formula/fntext.go index ef215326..899dd1c4 100644 --- a/spreadsheet/formula/fntext.go +++ b/spreadsheet/formula/fntext.go @@ -167,7 +167,8 @@ type parsedSearchObject struct { } func parseSearchResults(fname string, args []Result) (*parsedSearchObject, Result) { - if len(args) != 2 && len(args) != 3 { + argsNum := len(args) + if argsNum != 2 && argsNum != 3 { return nil, MakeErrorResult(fname + " requires two or three arguments") } findTextResult := args[0] @@ -181,7 +182,7 @@ func parseSearchResults(fname string, args []Result) (*parsedSearchObject, Resul text := textResult.ValueString findText := findTextResult.ValueString position := 1 - if len(args) == 3 { + if argsNum == 3 && args[2].Type != ResultTypeEmpty { positionResult := args[2] if positionResult.Type != ResultTypeNumber { return nil, MakeErrorResult("Position should be a number") @@ -198,7 +199,7 @@ func parseSearchResults(fname string, args []Result) (*parsedSearchObject, Resul findText, text, position, - }, MakeEmptyResult() + }, empty } // Find is an implementation of the Excel FIND(). @@ -543,7 +544,7 @@ func T(args []Result) Result { if s.Type == ResultTypeError || s.Type == ResultTypeString { return s } - return MakeEmptyResult() + return empty } // Trim is an implementation of the Excel TRIM function that removes leading, @@ -648,7 +649,7 @@ func parseReplaceResults(fname string, args []Result) (*parsedReplaceObject, Res startPos, length, textToReplace, - }, MakeEmptyResult() + }, empty } // Replace is an implementation of the Excel REPLACE(). diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 05fa889d..3ee3efc2 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -533,6 +533,7 @@ func TestMatch(t *testing.T) { td := []testStruct{ {`MATCH("??ny",A1:A5)`, `2 ResultTypeNumber`}, {`MATCH("*nny",A1:A5)`, `4 ResultTypeNumber`}, + {`MATCH("*nny",A1:A5,)`, `4 ResultTypeNumber`}, {`=MATCH(5,B1:B5,1)`, `2 ResultTypeNumber`}, {`=MATCH(5,C1:C5,-1)`, `3 ResultTypeNumber`}, } @@ -947,6 +948,7 @@ func TestFind(t *testing.T) { td := []testStruct{ {`FIND("",A1)`, `1 ResultTypeNumber`}, + {`FIND("",A1,)`, `1 ResultTypeNumber`}, {`FIND(B1,A1)`, `2 ResultTypeNumber`}, {`FIND(B2,A1,3)`, `3 ResultTypeNumber`}, {`FIND(B2,A1,4)`, `#VALUE! ResultTypeError`}, @@ -975,6 +977,7 @@ func TestFindb(t *testing.T) { td := []testStruct{ {`FINDB("",A1)`, `1 ResultTypeNumber`}, {`FINDB(B1,A1)`, `2 ResultTypeNumber`}, + {`FINDB(B1,A1,)`, `2 ResultTypeNumber`}, {`FINDB(B2,A1,3)`, `3 ResultTypeNumber`}, {`FINDB(B2,A1,4)`, `#VALUE! ResultTypeError`}, {`FINDB(D1,C1)`, `1 ResultTypeNumber`}, @@ -1002,6 +1005,7 @@ func TestSearch(t *testing.T) { td := []testStruct{ {`SEARCH("",A1)`, `1 ResultTypeNumber`}, {`SEARCH(B1,A1)`, `2 ResultTypeNumber`}, + {`SEARCH(B1,A1,)`, `2 ResultTypeNumber`}, {`SEARCH(B2,A1,3)`, `3 ResultTypeNumber`}, {`SEARCH(B2,A1,4)`, `#VALUE! ResultTypeError`}, {`SEARCH(B3,A1,2)`, `2 ResultTypeNumber`}, @@ -1031,6 +1035,7 @@ func TestSearchb(t *testing.T) { td := []testStruct{ {`SEARCHB("",A1)`, `1 ResultTypeNumber`}, {`SEARCHB(B1,A1)`, `2 ResultTypeNumber`}, + {`SEARCHB(B1,A1,)`, `2 ResultTypeNumber`}, {`SEARCHB(B2,A1,3)`, `3 ResultTypeNumber`}, {`SEARCHB(B2,A1,4)`, `#VALUE! ResultTypeError`}, {`SEARCHB(D1,C1)`, `1 ResultTypeNumber`}, @@ -1096,6 +1101,7 @@ func TestYearFrac(t *testing.T) { {`=YEARFRAC(A1,A2)`, `0.00277777777 ResultTypeNumber`}, {`=YEARFRAC(A3,A4)`, `0.16666666666 ResultTypeNumber`}, {`=YEARFRAC(A3,A5)`, `0.00277777777 ResultTypeNumber`}, + {`=YEARFRAC(A3,A5,)`, `0.00277777777 ResultTypeNumber`}, {`=YEARFRAC(A1,A2,1)`, `0.00273972602 ResultTypeNumber`}, {`=YEARFRAC(A6,A7,1)`, `0.00273224043 ResultTypeNumber`}, {`=YEARFRAC(A6,A8,1)`, `1 ResultTypeNumber`}, @@ -1307,6 +1313,8 @@ func TestDuration(t *testing.T) { sheet.Cell("A8").SetString("01/01/2048") // maturity date in string format td := []testStruct{ + {`=DURATION(A1,A2,A3,A4,A5)`, `10.9191452815 ResultTypeNumber`}, + {`=DURATION(A1,A2,A3,A4,A5,)`, `10.9191452815 ResultTypeNumber`}, {`=DURATION(A1,A2,A3,A4,A5,A6)`, `10.9191452815 ResultTypeNumber`}, {`=DURATION(A7,A8,A3,A4,A5,A6)`, `10.9191452815 ResultTypeNumber`}, {`=DURATION(A1,A2,A3,A4,A5,5)`, `#NUM! ResultTypeError`}, @@ -1331,6 +1339,8 @@ func TestMduration(t *testing.T) { sheet.Cell("A8").SetString("01/01/2016") // maturity date in string format td := []testStruct{ + {`=MDURATION(A1,A2,A3,A4,A5)`, `5.73566981391 ResultTypeNumber`}, + {`=MDURATION(A1,A2,A3,A4,A5,)`, `5.73566981391 ResultTypeNumber`}, {`=MDURATION(A1,A2,A3,A4,A5,A6)`, `5.73566981391 ResultTypeNumber`}, {`=MDURATION(A7,A8,A3,A4,A5,A6)`, `5.73566981391 ResultTypeNumber`}, {`=MDURATION(A1,A2,A3,A4,A5,5)`, `#NUM! ResultTypeError`}, @@ -1424,6 +1434,7 @@ func TestVlookup(t *testing.T) { td := []testStruct{ {`=VLOOKUP(150,A1:B4,2)`, `value1 ResultTypeString`}, {`=VLOOKUP(250,A1:B4,2)`, `value2 ResultTypeString`}, + {`=VLOOKUP(250,A1:B4,2,)`, `value2 ResultTypeString`}, {`=VLOOKUP(250,A1:B4,2,FALSE)`, `#N/A ResultTypeError`}, {`=VLOOKUP(300,A1:B4,2,FALSE)`, `value3 ResultTypeString`}, } @@ -1667,11 +1678,13 @@ func TestAccrintm(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ - {`=ACCRINTM(39539,39614,0.1,1000,0))`, `20.5555555555 ResultTypeNumber`}, - {`=ACCRINTM(39539,39614,0.1,1000,1))`, `20.4918032786 ResultTypeNumber`}, - {`=ACCRINTM(39539,39614,0.1,1000,2))`, `20.8333333333 ResultTypeNumber`}, - {`=ACCRINTM(39539,39614,0.1,1000,3))`, `20.5479452054 ResultTypeNumber`}, - {`=ACCRINTM(39539,39614,0.1,1000,4))`, `20.5555555555 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000)`, `20.5555555555 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,)`, `20.5555555555 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,0)`, `20.5555555555 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,1)`, `20.4918032786 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,2)`, `20.8333333333 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,3)`, `20.5479452054 ResultTypeNumber`}, + {`=ACCRINTM(39539,39614,0.1,1000,4)`, `20.5555555555 ResultTypeNumber`}, } runTests(t, ctx, td) @@ -1684,6 +1697,9 @@ func TestAmordegrc(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=AMORDEGRC(2400,39679,39813,300,1,0.15)`, `776 ResultTypeNumber`}, + {`=AMORDEGRC(2400,39679,39813,300,1,0.15,)`, `776 ResultTypeNumber`}, + {`=AMORDEGRC(2400,39679,39813,300,1,0.15,0)`, `776 ResultTypeNumber`}, {`=AMORDEGRC(2400,39679,39813,300,1,0.15,1)`, `776 ResultTypeNumber`}, {`=AMORDEGRC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`}, } @@ -1698,6 +1714,9 @@ func TestAmorlinc(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=AMORLINC(2400,39679,39813,300,1,0.15)`, `360 ResultTypeNumber`}, + {`=AMORLINC(2400,39679,39813,300,1,0.15,)`, `360 ResultTypeNumber`}, + {`=AMORLINC(2400,39679,39813,300,1,0.15,0)`, `360 ResultTypeNumber`}, {`=AMORLINC(2400,39679,39813,300,1,0.15,1)`, `360 ResultTypeNumber`}, {`=AMORLINC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`}, } @@ -1712,6 +1731,8 @@ func TestCoupdaybs(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPDAYBS(40568,40862,2)`, `70 ResultTypeNumber`}, + {`=COUPDAYBS(40568,40862,2,)`, `70 ResultTypeNumber`}, {`=COUPDAYBS(40568,40862,2,0)`, `70 ResultTypeNumber`}, {`=COUPDAYBS(40568,40862,2,1)`, `71 ResultTypeNumber`}, {`=COUPDAYBS(40568,40862,2,2)`, `71 ResultTypeNumber`}, @@ -1740,6 +1761,8 @@ func TestCoupdays(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPDAYS(40964,41228,1)`, `360 ResultTypeNumber`}, + {`=COUPDAYS(40964,41228,1,)`, `360 ResultTypeNumber`}, {`=COUPDAYS(40964,41228,1,0)`, `360 ResultTypeNumber`}, {`=COUPDAYS(40964,41228,1,1)`, `366 ResultTypeNumber`}, {`=COUPDAYS(40964,41228,1,2)`, `360 ResultTypeNumber`}, @@ -1768,6 +1791,8 @@ func TestCoupdaysnc(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPDAYSNC(40933,41228,1)`, `290 ResultTypeNumber`}, + {`=COUPDAYSNC(40933,41228,1,)`, `290 ResultTypeNumber`}, {`=COUPDAYSNC(40933,41228,1,0)`, `290 ResultTypeNumber`}, {`=COUPDAYSNC(40933,41228,1,1)`, `295 ResultTypeNumber`}, {`=COUPDAYSNC(40933,41228,1,2)`, `295 ResultTypeNumber`}, @@ -1796,6 +1821,9 @@ func TestCoupncd(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPNCD(40568,40862,1)`, `40862 ResultTypeNumber`}, + {`=COUPNCD(40568,40862,1,)`, `40862 ResultTypeNumber`}, + {`=COUPNCD(40568,40862,1,0)`, `40862 ResultTypeNumber`}, {`=COUPNCD(40568,40862,1,1)`, `40862 ResultTypeNumber`}, {`=COUPNCD(40568,40862,2,1)`, `40678 ResultTypeNumber`}, {`=COUPNCD(40568,40862,4,1)`, `40589 ResultTypeNumber`}, @@ -1812,6 +1840,9 @@ func TestCouppcd(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPPCD(40568,40862,2)`, `40497 ResultTypeNumber`}, + {`=COUPPCD(40568,40862,2,)`, `40497 ResultTypeNumber`}, + {`=COUPPCD(40568,40862,2,0)`, `40497 ResultTypeNumber`}, {`=COUPPCD(40568,40862,2,1)`, `40497 ResultTypeNumber`}, {`=COUPPCD(40872,40568,2,1)`, `#NUM! ResultTypeError`}, } @@ -1826,6 +1857,9 @@ func TestCoupnum(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=COUPNUM(39107,39767,2)`, `4 ResultTypeNumber`}, + {`=COUPNUM(39107,39767,2,)`, `4 ResultTypeNumber`}, + {`=COUPNUM(39107,39767,2,0)`, `4 ResultTypeNumber`}, {`=COUPNUM(39107,39767,2,1)`, `4 ResultTypeNumber`}, {`=COUPNUM(39767,39107,2,1)`, `#NUM! ResultTypeError`}, } @@ -1925,6 +1959,8 @@ func TestDisc(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=DISC(A1,A2,A3,A4)`, `0.00068644067 ResultTypeNumber`}, + {`=DISC(A1,A2,A3,A4,)`, `0.00068644067 ResultTypeNumber`}, {`=DISC(A1,A2,A3,A4,0)`, `0.00068644067 ResultTypeNumber`}, {`=DISC(A1,A2,A3,A4,1)`, `0.00068638416 ResultTypeNumber`}, {`=DISC(A1,A2,A3,A4,2)`, `0.00067650334 ResultTypeNumber`}, @@ -2028,6 +2064,8 @@ func TestIntrate(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=INTRATE(A1,A2,A3,A4)`, `0.05768 ResultTypeNumber`}, + {`=INTRATE(A1,A2,A3,A4,)`, `0.05768 ResultTypeNumber`}, {`=INTRATE(A1,A2,A3,A4,0)`, `0.05768 ResultTypeNumber`}, {`=INTRATE(A1,A2,A3,A4,1)`, `0.05864133333 ResultTypeNumber`}, {`=INTRATE(A1,A2,A3,A4,2)`, `0.05768 ResultTypeNumber`}, @@ -2165,7 +2203,12 @@ func TestNper(t *testing.T) { td := []testStruct{ {`=NPER(A1/12,A2,A3,A4,1)`, `59.6738656742 ResultTypeNumber`}, {`=NPER(A1/12,A2,A3,A4)`, `60.0821228537 ResultTypeNumber`}, + {`=NPER(A1/12,A2,A3,A4,)`, `60.0821228537 ResultTypeNumber`}, + {`=NPER(A1/12,A2,A3,A4,0)`, `60.0821228537 ResultTypeNumber`}, {`=NPER(A1/12,A2,A3)`, `-9.5785940398 ResultTypeNumber`}, + {`=NPER(A1/12,A2,A3,,)`, `-9.5785940398 ResultTypeNumber`}, + {`=NPER(A1/12,A2,A3,0,)`, `-9.5785940398 ResultTypeNumber`}, + {`=NPER(A1/12,A2,A3,,0)`, `-9.5785940398 ResultTypeNumber`}, } runTests(t, ctx, td) @@ -2192,3 +2235,377 @@ func TestNpv(t *testing.T) { runTests(t, ctx, td) } + +func TestPmt(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetNumber(0.08) + sheet.Cell("A2").SetNumber(10) + sheet.Cell("A3").SetNumber(10000) + sheet.Cell("A4").SetNumber(0.06) + sheet.Cell("A5").SetNumber(18) + sheet.Cell("A6").SetNumber(50000) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PMT(A1/12,A2,A3)`, `-1037.0320893 ResultTypeNumber`}, + {`=PMT(A1/12,A2,A3,)`, `-1037.0320893 ResultTypeNumber`}, + {`=PMT(A1/12,A2,A3,0)`, `-1037.0320893 ResultTypeNumber`}, + {`=PMT(A1/12,A2,A3,1)`, `-1037.1291259 ResultTypeNumber`}, + {`=PMT(A4/12,A5*12,0,A6)`, `-129.08116086 ResultTypeNumber`}, + {`=PMT("A4/12",A5*12,0,A6)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestPpmt(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PPMT(0.1/12,1,2*12,2000)`, `-75.623186008 ResultTypeNumber`}, + {`=PPMT(0.08,10,10,200000)`, `-27598.053462 ResultTypeNumber`}, + {`=PPMT(0.08,11,10,200000)`, `#NUM! ResultTypeError`}, + {`=PPMT("0.08%",10,10,200000)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestPricedisc(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 16, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 3, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(0.0525) + sheet.Cell("A4").SetNumber(100) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PRICEDISC(A1,A2,A3,A4)`, `99.78125 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,)`, `99.78125 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,0)`, `99.78125 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,1)`, `99.7991803278 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,2)`, `99.7958333333 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,3)`, `99.7986301369 ResultTypeNumber`}, + {`=PRICEDISC(A1,A2,A3,A4,4)`, `99.78125 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestPv(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PV(0.08/12,20*12,500)`, `-59777.145851 ResultTypeNumber`}, + {`=PV(0.08/12,20*12,500,,)`, `-59777.145851 ResultTypeNumber`}, + {`=PV(0.08/12,20*12,500,0,)`, `-59777.145851 ResultTypeNumber`}, + {`=PV(0.08/12,20*12,500,,0)`, `-59777.145851 ResultTypeNumber`}, + {`=PV(0.08/12,20*12,500,0,0)`, `-59777.145851 ResultTypeNumber`}, + {`=PV(0.1/12,2*12,1000,10000)`, `-29864.950264 ResultTypeNumber`}, + {`=PV(0.1/12,2*12,1000,10000,)`, `-29864.950264 ResultTypeNumber`}, + {`=PV(0.1/12,2*12,1000,10000,0)`, `-29864.950264 ResultTypeNumber`}, + {`=PV(0.1/12,2*12,1000,10000,1)`, `-30045.540721 ResultTypeNumber`}, + {`=PV(0,2*12,1000,10000,1)`, `-34000 ResultTypeNumber`}, + {`=PV("hello world",2*12,1000,10000,1)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestRate(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=RATE(2*12,-1000,-10000,100000)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,,)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,0,0.1)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,0,0.75)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,0,0.065)`, `0.06517891177 ResultTypeNumber`}, + {`=RATE(2*12,-1000,-10000,100000,1,0.1)`, `0.06323958 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestReceived(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 5, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(1000000) + sheet.Cell("A4").SetNumber(0.0575) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=RECEIVED(A1,A2,A3,A4)`, `1014584.6544 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,)`, `1014584.6544 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,0)`, `1014584.6544 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,1)`, `1014342.13261 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,2)`, `1014584.6544 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,3)`, `1014381.99124 ResultTypeNumber`}, + {`=RECEIVED(A1,A2,A3,A4,4)`, `1014584.6544 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestRri(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=RRI(96,10000,11000)`, `0.00099330737 ResultTypeNumber`}, + {`=RRI(8,10000,11000)`, `0.01198502414 ResultTypeNumber`}, + {`=RRI(0,10000,11000)`, `#NUM! ResultTypeError`}, + {`=RRI(8,0,11000)`, `#NUM! ResultTypeError`}, + {`=RRI(8,10000,-0.000001)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestOddlprice(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 7, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 6, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetDate(time.Date(2007, 10, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A4").SetNumber(0.0375) + sheet.Cell("A5").SetNumber(0.0405) + sheet.Cell("A6").SetNumber(100) + sheet.Cell("A7").SetNumber(2) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=ODDLPRICE(A1,A2,A3,A4,A5,A6,A7,0)`, `99.8782860147 ResultTypeNumber`}, + {`=ODDLPRICE(A1,A2,A3,A4,A5,A6,A7,1)`, `99.8759395207 ResultTypeNumber`}, + {`=ODDLPRICE(A1,A2,A3,A4,A5,A6,A7,2)`, `99.8769016984 ResultTypeNumber`}, + {`=ODDLPRICE(A1,A2,A3,A4,A5,A6,A7,3)`, `99.8787957508 ResultTypeNumber`}, + {`=ODDLPRICE(A1,A2,A3,A4,A5,A6,A7,4)`, `99.8782860147 ResultTypeNumber`}, + {`=ODDLPRICE(A2,A1,A3,A4,A5,A6,A7,4)`, `#NUM! ResultTypeError`}, + {`=ODDLPRICE(A1,A3,A2,A4,A5,A6,A7,4)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestOddyield(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 4, 20, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 6, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetDate(time.Date(2007, 12, 24, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A4").SetNumber(0.0375) + sheet.Cell("A5").SetNumber(99.875) + sheet.Cell("A6").SetNumber(100) + sheet.Cell("A7").SetNumber(2) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=ODDLYIELD(A1,A2,A3,A4,A5,A6,A7,0)`, `0.04519223562 ResultTypeNumber`}, + {`=ODDLYIELD(A1,A2,A3,A4,A5,A6,A7,1)`, `0.04517988549 ResultTypeNumber`}, + {`=ODDLYIELD(A1,A2,A3,A4,A5,A6,A7,2)`, `0.04503841511 ResultTypeNumber`}, + {`=ODDLYIELD(A1,A2,A3,A4,A5,A6,A7,3)`, `0.04515632373 ResultTypeNumber`}, + {`=ODDLYIELD(A1,A2,A3,A4,A5,A6,A7,4)`, `0.04519223562 ResultTypeNumber`}, + {`=ODDLYIELD(A2,A1,A3,A4,A5,A6,A7,4)`, `#NUM! ResultTypeError`}, + {`=ODDLYIELD(A1,A3,A2,A4,A5,A6,A7,4)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestPrice(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2017, 11, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(0.0575) + sheet.Cell("A4").SetNumber(0.065) + sheet.Cell("A5").SetNumber(100) + sheet.Cell("A6").SetNumber(2) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PRICE(A1,A2,A3,A4,A5,A6,0)`, `94.6343616213 ResultTypeNumber`}, + {`=PRICE(A1,A2,A3,A4,A5,A6,1)`, `94.6354492078 ResultTypeNumber`}, + {`=PRICE(A1,A2,A3,A4,A5,A6,2)`, `94.6024171768 ResultTypeNumber`}, + {`=PRICE(A1,A2,A3,A4,A5,A6,3)`, `94.6435945482 ResultTypeNumber`}, + {`=PRICE(A1,A2,A3,A4,A5,A6,4)`, `94.6343616213 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestPricemat(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 4, 13, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetDate(time.Date(2007, 11, 11, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A4").SetNumber(0.061) + sheet.Cell("A5").SetNumber(0.061) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=PRICEMAT(A1,A2,A3,A4,A5,0)`, `99.9844988755 ResultTypeNumber`}, + {`=PRICEMAT(A1,A2,A3,A4,A5,1)`, `99.9802978513 ResultTypeNumber`}, + {`=PRICEMAT(A1,A2,A3,A4,A5,2)`, `99.9841690643 ResultTypeNumber`}, + {`=PRICEMAT(A1,A2,A3,A4,A5,3)`, `99.9845977645 ResultTypeNumber`}, + {`=PRICEMAT(A1,A2,A3,A4,A5,4)`, `99.9844988755 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestSln(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=SLN(30000,7500,10)`, `2250 ResultTypeNumber`}, + {`=SLN(30000,7500,0)`, `#DIV/0! ResultTypeError`}, + {`=SLN("hello world",7500,10)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestSyd(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=SYD(30000,7500,10,1)`, `4090.9090909 ResultTypeNumber`}, + {`=SYD(30000,7500,10,10)`, `409.09090909 ResultTypeNumber`}, + {`=SYD(30000,7500,10,11)`, `#NUM! ResultTypeError`}, + {`=SYD(30000,7500,0,0)`, `#NUM! ResultTypeError`}, + {`=SYD(30000,7500,10,0)`, `#NUM! ResultTypeError`}, + {`=SLN("hello world",7500,10,1)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestTbilleq(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 3, 31, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 6, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(0.0914) + sheet.Cell("A4").SetDate(time.Date(2009, 4, 1, 0, 0, 0, 0, time.UTC)) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=TBILLEQ(A1,A2,A3)`, `0.09415149356 ResultTypeNumber`}, + {`=TBILLEQ("A1",A2,A3)`, `#VALUE! ResultTypeError`}, + {`=TBILLEQ(A1,A2,0)`, `#NUM! ResultTypeError`}, + {`=TBILLEQ(A2,A1,A3)`, `#NUM! ResultTypeError`}, + {`=TBILLEQ(A1,A4,A3)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestTbillprice(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 3, 31, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 6, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(0.09) + sheet.Cell("A4").SetDate(time.Date(2009, 4, 1, 0, 0, 0, 0, time.UTC)) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=TBILLPRICE(A1,A2,A3)`, `98.45 ResultTypeNumber`}, + {`=TBILLPRICE("A1",A2,A3)`, `#VALUE! ResultTypeError`}, + {`=TBILLPRICE(A1,A2,0)`, `#NUM! ResultTypeError`}, + {`=TBILLPRICE(A2,A1,A3)`, `#NUM! ResultTypeError`}, + {`=TBILLPRICE(A1,A4,A3)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestTbillyield(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 3, 31, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 6, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(98.45) + sheet.Cell("A4").SetDate(time.Date(2009, 4, 1, 0, 0, 0, 0, time.UTC)) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=TBILLYIELD(A1,A2,A3)`, `0.09141696292 ResultTypeNumber`}, + {`=TBILLYIELD("A1",A2,A3)`, `#VALUE! ResultTypeError`}, + {`=TBILLYIELD(A1,A2,0)`, `#NUM! ResultTypeError`}, + {`=TBILLYIELD(A2,A1,A3)`, `#NUM! ResultTypeError`}, + {`=TBILLYIELD(A1,A4,A3)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestVdb(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetNumber(2400) + sheet.Cell("A2").SetNumber(300) + sheet.Cell("A3").SetNumber(10) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=VDB(A1,A2,A3*365,0,1)`, `1.31506849315 ResultTypeNumber`}, + {`=VDB(A1,A2,A3*12,0,1)`, `40 ResultTypeNumber`}, + {`=VDB(A1,A2,A3,0,1)`, `480 ResultTypeNumber`}, + {`=VDB(A1,A2,A3*12,6,18)`, `396.306053264 ResultTypeNumber`}, + {`=VDB(A1,A2,A3*12,6,18,)`, `210 ResultTypeNumber`}, + {`=VDB(A1,A2,A3*12,6,18,,1)`, `0 ResultTypeNumber`}, + {`=VDB(A1,A2,A3*12,6,18,1.5)`, `311.808936658 ResultTypeNumber`}, + {`=VDB(A1,A2,A3,0,0.875,1.5)`, `315 ResultTypeNumber`}, + {`=VDB(A1,A2,A3,0,0.875,,)`, `183.75 ResultTypeNumber`}, + {`=VDB(A1,A2,A3,0,0.875,,1)`, `0 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +}