mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
Financial functions - part 1 (#359)
* ACCRINTM, COUPNUM, COUPPCD, AMORDEGRC, AMORLINC * COUPDAYBS * COUPNCD, COUPDAYS, COUPDAYSNC * CUMIPMT, CUMPRINC
This commit is contained in:
parent
e17f3d3164
commit
64edd6c139
@ -416,10 +416,14 @@ func validateDate(year, month, day int) bool {
|
||||
if day < 1 {
|
||||
return false
|
||||
}
|
||||
return day <= getDaysInMonth(year, month)
|
||||
}
|
||||
|
||||
func getDaysInMonth(year, month int) int {
|
||||
if month == 2 && isLeapYear(year) {
|
||||
return day <= 29
|
||||
return 29
|
||||
} else {
|
||||
return day <= daysInMonth[month-1]
|
||||
return daysInMonth[month-1]
|
||||
}
|
||||
}
|
||||
|
||||
@ -779,10 +783,14 @@ func YearFrac(ctx Context, ev Evaluator, args []Result) Result {
|
||||
if err != nil {
|
||||
return MakeErrorResult("incorrect end date")
|
||||
}
|
||||
return yearFrac(startDate, endDate, basis)
|
||||
return yearFracFromTime(startDate, endDate, basis)
|
||||
}
|
||||
|
||||
func yearFrac(startDate, endDate time.Time, basis int) Result {
|
||||
func yearFrac(startDate, endDate float64, basis int) Result {
|
||||
return yearFracFromTime(dateFromDays(startDate), dateFromDays(endDate), basis)
|
||||
}
|
||||
|
||||
func yearFracFromTime(startDate, endDate time.Time, basis int) Result {
|
||||
startDateS := startDate.Unix()
|
||||
endDateS := endDate.Unix()
|
||||
sy, sm, sd := startDate.Date()
|
||||
@ -821,6 +829,21 @@ func yearFrac(startDate, endDate time.Time, basis int) Result {
|
||||
return MakeErrorResultType(ErrorTypeValue, "")
|
||||
}
|
||||
|
||||
func getDaysInYear(year, basis int) int {
|
||||
switch basis {
|
||||
case 1:
|
||||
if isLeapYear(year) {
|
||||
return 366
|
||||
} else {
|
||||
return 365
|
||||
}
|
||||
case 3:
|
||||
return 365
|
||||
default:
|
||||
return 360
|
||||
}
|
||||
}
|
||||
|
||||
func makeDateS(y int, m time.Month, d int) int64 {
|
||||
if y == 1900 && int(m) <= 2 {
|
||||
d--
|
||||
@ -855,3 +878,104 @@ func feb29Between(date1, date2 time.Time) bool {
|
||||
var mar1year2 = makeDateS(year2, time.March, 1)
|
||||
return (isLeapYear(year2) && date2S >= mar1year2 && date1S < mar1year2)
|
||||
}
|
||||
|
||||
func getDiff(from, to time.Time, basis int) float64 {
|
||||
if from.After(to) {
|
||||
from, to = to, from
|
||||
}
|
||||
diff := 0
|
||||
|
||||
yFrom, mFromM, dFromOrig := from.Date()
|
||||
yTo, mToM, dToOrig := to.Date()
|
||||
|
||||
mFrom, mTo := int(mFromM), int(mToM)
|
||||
dFrom, dTo := getDayOnBasis(yFrom, mFrom, dFromOrig, basis), getDayOnBasis(yTo, mTo, dToOrig, basis)
|
||||
|
||||
if !basis30(basis) {
|
||||
return daysFromDate(yTo, mTo, dTo) - daysFromDate(yFrom, mFrom, dFrom)
|
||||
}
|
||||
|
||||
if basis == 0 {
|
||||
if (mFrom == 2 || dFrom < 30 ) && dToOrig == 31 {
|
||||
dTo = 31
|
||||
} else if mTo == 2 && dTo == getDaysInMonth(yTo, mTo) {
|
||||
dTo = getDaysInMonth(yTo, 2)
|
||||
}
|
||||
} else {
|
||||
if mFrom == 2 && dFrom == 30 {
|
||||
dFrom = getDaysInMonth(yFrom, 2)
|
||||
}
|
||||
if mTo == 2 && dTo == 30 {
|
||||
dTo = getDaysInMonth(yTo, 2)
|
||||
}
|
||||
}
|
||||
|
||||
if yFrom < yTo || (yFrom == yTo && mFrom < mTo) {
|
||||
diff = 30 - dFrom + 1
|
||||
dFromOrig = 1
|
||||
dFrom = 1
|
||||
fromNew := time.Date(yFrom, time.Month(mFrom), dFromOrig, 0, 0, 0, 0, time.UTC).AddDate(0,1,0)
|
||||
if fromNew.Year() < yTo {
|
||||
diff += getDaysInMonthRange(fromNew.Year(), int(fromNew.Month()), 12, basis)
|
||||
fromNew = fromNew.AddDate(0, 13 - int(fromNew.Month()), 0)
|
||||
diff += getDaysInYearRange(fromNew.Year(), yTo - 1, basis)
|
||||
}
|
||||
diff += getDaysInMonthRange(yTo, int(fromNew.Month()), mTo - 1, basis)
|
||||
fromNew = fromNew.AddDate(0, mTo - int(fromNew.Month()), 0)
|
||||
mFrom = fromNew.Day()
|
||||
}
|
||||
diff += dTo - dFrom
|
||||
if diff > 0 {
|
||||
return float64(diff)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getDayOnBasis(year, month, dayOrig, basis int) int {
|
||||
if !basis30(basis) {
|
||||
return dayOrig
|
||||
}
|
||||
day := dayOrig
|
||||
dim := getDaysInMonth(year, month)
|
||||
if day > 30 || dayOrig >= dim || day >= dim {
|
||||
day = 30
|
||||
}
|
||||
return day
|
||||
}
|
||||
|
||||
func getDaysInMonthRange(y, from, to, basis int) int {
|
||||
if from > to {
|
||||
return 0
|
||||
}
|
||||
if basis30(basis) {
|
||||
return (to - from + 1) * 30
|
||||
}
|
||||
days := 0
|
||||
for m := from; m <= to; m++ {
|
||||
days += getDaysInMonth(y, m)
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
func getDaysInYearRange(from, to, basis int) int {
|
||||
if from > to {
|
||||
return 0
|
||||
}
|
||||
if basis30(basis) {
|
||||
return (to - from + 1) * 360
|
||||
}
|
||||
days := 0
|
||||
for y := from; y <= to; y++ {
|
||||
dy := 365
|
||||
if isLeapYear(y) {
|
||||
dy = 366
|
||||
}
|
||||
days += dy
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
func basis30(basis int) bool {
|
||||
return basis == 0 || basis == 4
|
||||
}
|
||||
|
@ -17,24 +17,49 @@ func init() {
|
||||
RegisterFunction("MDURATION", Mduration)
|
||||
RegisterFunction("PDURATION", Pduration)
|
||||
RegisterFunction("_xlfn.PDURATION", Pduration)
|
||||
RegisterFunction("ACCRINTM", Accrintm)
|
||||
RegisterFunction("AMORDEGRC", Amordegrc)
|
||||
RegisterFunction("AMORLINC", Amorlinc)
|
||||
RegisterFunction("COUPDAYBS", Coupdaybs)
|
||||
RegisterFunction("COUPDAYS", Coupdays)
|
||||
RegisterFunction("COUPDAYSNC", Coupdaysnc)
|
||||
RegisterFunction("COUPNUM", Coupnum)
|
||||
RegisterFunction("COUPNCD", Coupncd)
|
||||
RegisterFunction("COUPPCD", Couppcd)
|
||||
RegisterFunction("CUMIPMT", Cumipmt)
|
||||
RegisterFunction("CUMPRINC", Cumprinc)
|
||||
}
|
||||
|
||||
// Duration implements the Excel DURATION function.
|
||||
func Duration(args []Result) Result {
|
||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "DURATION")
|
||||
parsedArgs, err := parseDurationData(args, "DURATION")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
return getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
||||
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 {
|
||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "MDURATION")
|
||||
parsedArgs, err := parseDurationData(args, "MDURATION")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
duration := getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
||||
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
|
||||
}
|
||||
@ -71,8 +96,162 @@ func Pduration(args []Result) Result {
|
||||
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate))
|
||||
}
|
||||
|
||||
// getCouppcd finds last coupon date before settlement (can be equal to settlement).
|
||||
func getCouppcd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
||||
type couponArgs struct {
|
||||
settlementDate float64
|
||||
maturityDate float64
|
||||
freq int
|
||||
basis int
|
||||
}
|
||||
|
||||
// Coupdaybs implements the Excel COUPDAYBS function.
|
||||
func Coupdaybs(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPDAYBS")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||
freq := parsedArgs.freq
|
||||
basis := parsedArgs.basis
|
||||
pcd := couppcd(settlementDate, maturityDate, freq, basis)
|
||||
return MakeNumberResult(getDiff(pcd, settlementDate, basis))
|
||||
}
|
||||
|
||||
// Coupdays implements the Excel COUPDAYS function.
|
||||
func Coupdays(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPDAYS")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||
freq := parsedArgs.freq
|
||||
basis := parsedArgs.basis
|
||||
if basis == 1 {
|
||||
pcd := couppcd(settlementDate, maturityDate, freq, 1)
|
||||
next := pcd.AddDate(0, 12 / freq, 0)
|
||||
return MakeNumberResult(getDiff(pcd, next, basis))
|
||||
}
|
||||
return MakeNumberResult(float64(getDaysInYear(0, basis)) / float64(freq))
|
||||
}
|
||||
|
||||
// Coupdaysnc implements the Excel COUPDAYSNC function.
|
||||
func Coupdaysnc(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPDAYSNC")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||
freq := parsedArgs.freq
|
||||
basis := parsedArgs.basis
|
||||
ncd := coupncd(settlementDate, maturityDate, freq)
|
||||
return MakeNumberResult(getDiff(settlementDate, ncd, basis))
|
||||
}
|
||||
|
||||
// Couppcd implements the Excel COUPPCD function.
|
||||
func Couppcd(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPPCD")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||
freq := parsedArgs.freq
|
||||
basis := parsedArgs.basis
|
||||
pcd := couppcd(settlementDate, maturityDate, freq, basis)
|
||||
y, m, d := pcd.Date()
|
||||
return MakeNumberResult(daysFromDate(y, int(m), d))
|
||||
}
|
||||
|
||||
// Coupnum implements the Excel COUPNUM function.
|
||||
func Coupnum(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPNUM")
|
||||
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)
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
return MakeNumberResult(cn)
|
||||
}
|
||||
|
||||
// Coupncd implements the Excel COUPNCD function.
|
||||
func Coupncd(args []Result) Result {
|
||||
parsedArgs, err := parseCouponArgs(args, "COUPNCD")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||
freq := parsedArgs.freq
|
||||
ncd := coupncd(settlementDate, maturityDate, freq)
|
||||
y, m, d := ncd.Date()
|
||||
return MakeNumberResult(daysFromDate(y, int(m), d))
|
||||
}
|
||||
|
||||
func coupncd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
||||
ncd := time.Date(settlementDate.Year(), maturityDate.Month(), maturityDate.Day(), 0, 0, 0, 0, time.UTC)
|
||||
if ncd.After(settlementDate) {
|
||||
ncd = ncd.AddDate(-1, 0, 0)
|
||||
}
|
||||
for !ncd.After(settlementDate) {
|
||||
ncd = ncd.AddDate(0, 12 / freq, 0)
|
||||
}
|
||||
return ncd
|
||||
}
|
||||
|
||||
func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) {
|
||||
argsNum := len(args)
|
||||
if argsNum != 3 && argsNum != 4 {
|
||||
return nil, MakeErrorResult(funcName + " requires 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")
|
||||
}
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires frequency to be number argument")
|
||||
}
|
||||
freq := args[2].ValueNumber
|
||||
if !checkFreq(freq) {
|
||||
return nil, MakeErrorResult("Incorrect frequency for " + funcName)
|
||||
}
|
||||
basis := 0
|
||||
if argsNum == 4 {
|
||||
if args[3].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires basis to be number argument")
|
||||
}
|
||||
basis = int(args[3].ValueNumber)
|
||||
if !checkBasis(basis) {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
||||
}
|
||||
}
|
||||
return &couponArgs{
|
||||
settlementDate,
|
||||
maturityDate,
|
||||
int(freq),
|
||||
basis,
|
||||
}, MakeEmptyResult()
|
||||
}
|
||||
|
||||
// couppcd finds last coupon date before settlement (can be equal to settlement).
|
||||
func couppcd(settlementDate, maturityDate time.Time, freq, basis int) time.Time {
|
||||
rDate := maturityDate
|
||||
diffYears := settlementDate.Year() - maturityDate.Year()
|
||||
rDate = rDate.AddDate(diffYears, 0, 0)
|
||||
@ -86,41 +265,44 @@ func getCouppcd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
||||
return rDate
|
||||
}
|
||||
|
||||
// getCoupnum gets count of coupon dates.
|
||||
func getCoupnum(settlementDate, maturityDate time.Time, freq, basis int) float64 {
|
||||
// coupnum gets count of coupon dates.
|
||||
func coupnum(settlementDate, maturityDate time.Time, freq, basis int) (float64, Result) {
|
||||
if maturityDate.After(settlementDate) {
|
||||
aDate := getCouppcd(settlementDate, maturityDate, freq)
|
||||
aDate := couppcd(settlementDate, maturityDate, freq, basis)
|
||||
months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month())
|
||||
return float64(months * freq) / 12.0
|
||||
return float64(months * freq) / 12.0, MakeEmptyResult()
|
||||
}
|
||||
return 0.0 // replace for error
|
||||
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 time.Time, coup, yield, freq float64, basis int) Result {
|
||||
func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis int) Result {
|
||||
fracResult := yearFrac(settlementDate, maturityDate, basis)
|
||||
if fracResult.Type == ResultTypeError {
|
||||
return fracResult
|
||||
}
|
||||
frac := fracResult.ValueNumber
|
||||
nCoups := getCoupnum(settlementDate, maturityDate, int(freq), basis)
|
||||
coups, err := coupnum(dateFromDays(settlementDate), dateFromDays(maturityDate), int(freq), basis)
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
duration := 0.0
|
||||
p := 0.0
|
||||
coup *= 100 / freq
|
||||
yield /= freq
|
||||
yield++
|
||||
diff := frac * freq - nCoups
|
||||
for t := 1.0; t < nCoups; t++ {
|
||||
diff := frac * freq - coups
|
||||
for t := 1.0; t < coups; t++ {
|
||||
tDiff := t + diff
|
||||
add := coup / math.Pow(yield, tDiff)
|
||||
p += add
|
||||
duration += tDiff * add
|
||||
}
|
||||
|
||||
add := (coup + 100) / math.Pow(yield, nCoups + diff)
|
||||
add := (coup + 100) / math.Pow(yield, coups + diff)
|
||||
|
||||
p += add
|
||||
duration += (nCoups + diff) * add
|
||||
duration += (coups + diff) * add
|
||||
|
||||
duration /= p
|
||||
duration /= freq
|
||||
@ -128,10 +310,19 @@ func getDuration(settlementDate, maturityDate time.Time, coup, yield, freq float
|
||||
return MakeNumberResult(duration)
|
||||
}
|
||||
|
||||
type durationArgs struct {
|
||||
settlementDate float64
|
||||
maturityDate float64
|
||||
coupon float64
|
||||
yield float64
|
||||
freq float64
|
||||
basis int
|
||||
}
|
||||
|
||||
// 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) (float64, float64, float64, float64, float64, int, Result) {
|
||||
func parseDurationData(args []Result, funcName string) (*durationArgs, Result) {
|
||||
if len(args) != 5 && len(args) != 6 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires five or six arguments")
|
||||
return nil, MakeErrorResult(funcName + " requires five or six arguments")
|
||||
}
|
||||
var settlementDate, maturityDate float64
|
||||
settlementResult := args[0]
|
||||
@ -141,11 +332,11 @@ func parseDurationData(args []Result, funcName string) (float64, float64, float6
|
||||
case ResultTypeString:
|
||||
settlementFromString := DateValue([]Result{settlementResult})
|
||||
if settlementFromString.Type == ResultTypeError {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
return nil, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
}
|
||||
settlementDate = settlementFromString.ValueNumber
|
||||
default:
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
return nil, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
}
|
||||
maturityResult := args[1]
|
||||
switch maturityResult.Type {
|
||||
@ -154,49 +345,447 @@ func parseDurationData(args []Result, funcName string) (float64, float64, float6
|
||||
case ResultTypeString:
|
||||
maturityFromString := DateValue([]Result{maturityResult})
|
||||
if maturityFromString.Type == ResultTypeError {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
return nil, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
}
|
||||
maturityDate = maturityFromString.ValueNumber
|
||||
default:
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
return nil, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
}
|
||||
if settlementDate >= maturityDate {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||
}
|
||||
couponResult := args[2]
|
||||
if couponResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires third argument of type number")
|
||||
return nil, MakeErrorResult(funcName + " requires third argument of type number")
|
||||
}
|
||||
coupon := couponResult.ValueNumber
|
||||
if coupon < 0 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Coupon rate should not be negative")
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Coupon rate should not be negative")
|
||||
}
|
||||
yieldResult := args[3]
|
||||
if yieldResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fourth argument of type number")
|
||||
return nil, MakeErrorResult(funcName + " requires fourth argument of type number")
|
||||
}
|
||||
yield := yieldResult.ValueNumber
|
||||
if yield < 0 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Yield rate should not be negative")
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Yield rate should not be negative")
|
||||
}
|
||||
freqResult := args[4]
|
||||
if freqResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fifth argument of type number")
|
||||
return nil, MakeErrorResult(funcName + " requires fifth argument of type number")
|
||||
}
|
||||
freq := float64(int(freqResult.ValueNumber))
|
||||
if freq != 1 && freq != 2 && freq != 4 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
||||
if !checkFreq(freq) {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
||||
}
|
||||
basis := 0
|
||||
if len(args) == 6 {
|
||||
basisResult := args[5]
|
||||
if basisResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires sixth argument of type number")
|
||||
return nil, MakeErrorResult(funcName + " requires sixth argument of type number")
|
||||
}
|
||||
basis = int(basisResult.ValueNumber)
|
||||
if basis < 0 || basis > 4 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value")
|
||||
if !checkBasis(basis) {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value")
|
||||
}
|
||||
}
|
||||
return settlementDate, maturityDate, coupon, yield, freq, basis, MakeEmptyResult()
|
||||
return &durationArgs{
|
||||
settlementDate,
|
||||
maturityDate,
|
||||
coupon,
|
||||
yield,
|
||||
freq,
|
||||
basis,
|
||||
}, MakeEmptyResult()
|
||||
}
|
||||
|
||||
func checkFreq(freq float64) bool {
|
||||
return freq == 1 || freq == 2 || freq == 4
|
||||
}
|
||||
|
||||
func checkBasis(basis int) bool {
|
||||
return basis >= 0 && basis <= 4
|
||||
}
|
||||
|
||||
// Accrintm implements the Excel ACCRINTM function.
|
||||
func Accrintm(args []Result) Result {
|
||||
argsNum := len(args)
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("ACCRINTM requires rate to be number argument")
|
||||
}
|
||||
rate := args[2].ValueNumber
|
||||
if rate <= 0 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires rate to be positive number argument")
|
||||
}
|
||||
if args[3].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("ACCRINTM requires par to be number argument")
|
||||
}
|
||||
par := args[3].ValueNumber
|
||||
if par <= 0 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires par to be positive number argument")
|
||||
}
|
||||
basis := 0
|
||||
if argsNum == 5 {
|
||||
if args[4].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("ACCRINTM requires basis to be number argument")
|
||||
}
|
||||
basis = int(args[4].ValueNumber)
|
||||
if !checkBasis(basis) {
|
||||
return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for ACCRINTM")
|
||||
}
|
||||
}
|
||||
fracResult := yearFrac(issue, settlement, basis)
|
||||
if fracResult.Type == ResultTypeError {
|
||||
return fracResult
|
||||
}
|
||||
return MakeNumberResult(par * rate * fracResult.ValueNumber)
|
||||
}
|
||||
|
||||
// Amordegrc implements the Excel AMORDEGRC function.
|
||||
func Amordegrc(args []Result) Result {
|
||||
parsedArgs, err := parseAmorArgs(args, "AMORDEGRC")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
cost := parsedArgs.cost
|
||||
datePurchased := parsedArgs.datePurchased
|
||||
firstPeriod := parsedArgs.firstPeriod
|
||||
salvage := parsedArgs.salvage
|
||||
period := parsedArgs.period
|
||||
rate := parsedArgs.rate
|
||||
if rate >= 0.5 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "AMORDEGRC requires rate to be less than 0.5")
|
||||
}
|
||||
basis := parsedArgs.basis
|
||||
|
||||
lifeOfAssets := 1.0 / rate
|
||||
amorCoeff := 2.5
|
||||
if lifeOfAssets < 3 {
|
||||
amorCoeff = 1
|
||||
} else if lifeOfAssets < 5 {
|
||||
amorCoeff = 1.5
|
||||
} else if lifeOfAssets <= 6 {
|
||||
amorCoeff = 2
|
||||
}
|
||||
|
||||
rate *= amorCoeff
|
||||
yfResult := yearFrac(datePurchased, firstPeriod, basis)
|
||||
if yfResult.Type == ResultTypeError {
|
||||
return MakeErrorResult("incorrect dates for AMORDEGRC")
|
||||
}
|
||||
nRate := mathRound(yfResult.ValueNumber * rate * cost)
|
||||
cost -= nRate
|
||||
rest := cost - salvage
|
||||
|
||||
for n := 0; n < period; n++ {
|
||||
nRate = mathRound(rate * cost)
|
||||
rest -= nRate
|
||||
if rest < 0 {
|
||||
switch period - n {
|
||||
case 0:
|
||||
case 1:
|
||||
return MakeNumberResult(mathRound(cost * 0.5))
|
||||
default:
|
||||
return MakeNumberResult(0)
|
||||
}
|
||||
}
|
||||
cost -= nRate
|
||||
}
|
||||
|
||||
return MakeNumberResult(nRate)
|
||||
}
|
||||
|
||||
// Amorlinc implements the Excel AMORLINC function.
|
||||
func Amorlinc(args []Result) Result {
|
||||
parsedArgs, err := parseAmorArgs(args, "AMORLINC")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
cost := parsedArgs.cost
|
||||
datePurchased := parsedArgs.datePurchased
|
||||
firstPeriod := parsedArgs.firstPeriod
|
||||
salvage := parsedArgs.salvage
|
||||
period := parsedArgs.period
|
||||
rate := parsedArgs.rate
|
||||
basis := parsedArgs.basis
|
||||
|
||||
yfResult := yearFrac(datePurchased, firstPeriod, basis)
|
||||
if yfResult.Type == ResultTypeError {
|
||||
return MakeErrorResult("incorrect dates for AMORLINC")
|
||||
}
|
||||
r0 := yfResult.ValueNumber * rate * cost
|
||||
if period == 0 {
|
||||
return MakeNumberResult(r0)
|
||||
}
|
||||
|
||||
oneRate := cost * rate
|
||||
costDelta := cost - salvage
|
||||
numOfFullPeriods := int((costDelta - r0) / oneRate)
|
||||
|
||||
if period <= numOfFullPeriods {
|
||||
return MakeNumberResult(oneRate)
|
||||
} else if period == numOfFullPeriods + 1 {
|
||||
return MakeNumberResult(costDelta - oneRate * float64(numOfFullPeriods) - r0)
|
||||
} else {
|
||||
return MakeNumberResult(0)
|
||||
}
|
||||
}
|
||||
|
||||
type amorArgs struct {
|
||||
cost float64
|
||||
datePurchased float64
|
||||
firstPeriod float64
|
||||
salvage float64
|
||||
period int
|
||||
rate float64
|
||||
basis int
|
||||
}
|
||||
|
||||
func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
||||
argsNum := len(args)
|
||||
if argsNum != 6 && argsNum != 7 {
|
||||
return nil, MakeErrorResult(funcName + " requires six or seven arguments")
|
||||
}
|
||||
if args[0].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires cost to be number argument")
|
||||
}
|
||||
cost := args[0].ValueNumber
|
||||
if cost < 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires cost to be positive")
|
||||
}
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires date purchased to be number argument")
|
||||
}
|
||||
datePurchased := args[1].ValueNumber
|
||||
if datePurchased < 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires date purchased to be positive")
|
||||
}
|
||||
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")
|
||||
}
|
||||
if args[3].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires salvage to be number argument")
|
||||
}
|
||||
salvage := args[3].ValueNumber
|
||||
if salvage < 0 || salvage > cost {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires salvage to be between 0 and the initial cost")
|
||||
}
|
||||
if args[4].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires period to be number argument")
|
||||
}
|
||||
period := int(args[4].ValueNumber)
|
||||
if period < 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires period to be non-negative")
|
||||
}
|
||||
if args[5].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires depreciation rate to be number argument")
|
||||
}
|
||||
rate := args[5].ValueNumber
|
||||
if rate < 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be more than 0 and less than 0.5")
|
||||
}
|
||||
basis := 0
|
||||
if argsNum == 7 {
|
||||
if args[6].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires basis to be number argument")
|
||||
}
|
||||
basis = int(args[6].ValueNumber)
|
||||
if !checkBasis(basis) || basis == 2 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
||||
}
|
||||
}
|
||||
return &amorArgs{
|
||||
cost,
|
||||
datePurchased,
|
||||
firstPeriod,
|
||||
salvage,
|
||||
period,
|
||||
rate,
|
||||
basis,
|
||||
}, MakeEmptyResult()
|
||||
}
|
||||
|
||||
func mathRound(x float64) float64 {
|
||||
return float64(int(x + 0.5))
|
||||
}
|
||||
|
||||
type cumulArgs struct {
|
||||
rate float64
|
||||
nPer float64
|
||||
pv float64
|
||||
startPeriod float64
|
||||
endPeriod float64
|
||||
t int
|
||||
}
|
||||
|
||||
// Cumipmt implements the Excel CUMIPMT function.
|
||||
func Cumipmt(args []Result) Result {
|
||||
parsedArgs, err := parseCumulArgs(args, "CUMIPMT")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
rate := parsedArgs.rate
|
||||
nPer := parsedArgs.nPer
|
||||
pv := parsedArgs.pv
|
||||
startPeriod := parsedArgs.startPeriod
|
||||
endPeriod := parsedArgs.endPeriod
|
||||
t := parsedArgs.t
|
||||
|
||||
payment := pmt(rate, nPer, pv, 0, t)
|
||||
interest := 0.0
|
||||
if startPeriod == 1 {
|
||||
if t == 0 {
|
||||
interest = -pv
|
||||
startPeriod++
|
||||
}
|
||||
}
|
||||
for i := startPeriod; i <= endPeriod; i++ {
|
||||
if t == 1 {
|
||||
interest += fv(rate, i - 2, payment, pv, 1) - payment
|
||||
} else {
|
||||
interest += fv(rate, i - 1, payment, pv, 0)
|
||||
}
|
||||
}
|
||||
interest *= rate
|
||||
return MakeNumberResult(interest)
|
||||
}
|
||||
|
||||
// Cumprinc implements the Excel CUMPRINC function.
|
||||
func Cumprinc(args []Result) Result {
|
||||
parsedArgs, err := parseCumulArgs(args, "CUMPRINC")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
rate := parsedArgs.rate
|
||||
nPer := parsedArgs.nPer
|
||||
pv := parsedArgs.pv
|
||||
startPeriod := parsedArgs.startPeriod
|
||||
endPeriod := parsedArgs.endPeriod
|
||||
t := parsedArgs.t
|
||||
|
||||
payment := pmt(rate, nPer, pv, 0, t)
|
||||
principal := 0.0
|
||||
if startPeriod == 1 {
|
||||
if t == 0 {
|
||||
principal = payment + pv * rate
|
||||
} else {
|
||||
principal = payment
|
||||
}
|
||||
startPeriod++
|
||||
}
|
||||
for i := startPeriod; i <= endPeriod; i++ {
|
||||
if t == 1 {
|
||||
principal += payment - (fv(rate, i - 2, payment, pv, 1) - payment) * rate
|
||||
} else {
|
||||
principal += payment - fv(rate, i - 1, payment, pv, 0) * rate
|
||||
}
|
||||
}
|
||||
return MakeNumberResult(principal)
|
||||
}
|
||||
|
||||
func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) {
|
||||
if len(args) != 6 {
|
||||
return nil, MakeErrorResult(funcName + " requires six arguments")
|
||||
}
|
||||
if args[0].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires rate to be number argument")
|
||||
}
|
||||
rate := args[0].ValueNumber
|
||||
if rate <= 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be positive number argument")
|
||||
}
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires number of periods to be number argument")
|
||||
}
|
||||
nPer := args[1].ValueNumber
|
||||
if nPer <= 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires number of periods to be positive number argument")
|
||||
}
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires present value to be number argument")
|
||||
}
|
||||
pv := args[2].ValueNumber
|
||||
if pv <= 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires present value to be positive number argument")
|
||||
}
|
||||
if args[3].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires start period to be number argument")
|
||||
}
|
||||
startPeriod := args[3].ValueNumber
|
||||
if startPeriod <= 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires start period to be positive number argument")
|
||||
}
|
||||
if args[4].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(funcName + " requires end period to be number argument")
|
||||
}
|
||||
endPeriod := args[4].ValueNumber
|
||||
if endPeriod <= 0 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be positive number argument")
|
||||
}
|
||||
if endPeriod < startPeriod {
|
||||
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")
|
||||
}
|
||||
t := int(args[5].ValueNumber)
|
||||
if t != 0 && t != 1 {
|
||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires type to be 0 or 1")
|
||||
}
|
||||
return &cumulArgs{
|
||||
rate,
|
||||
nPer,
|
||||
pv,
|
||||
startPeriod,
|
||||
endPeriod,
|
||||
t,
|
||||
}, MakeEmptyResult()
|
||||
}
|
||||
|
||||
func pmt(rate, periods, present, future float64, t int ) float64 {
|
||||
var result float64
|
||||
if rate == 0 {
|
||||
result = (present + future) / periods
|
||||
} else {
|
||||
term := math.Pow(1 + rate, periods)
|
||||
if t == 1 {
|
||||
result = (future * rate / (term - 1) + present * rate / (1 - 1 / term)) / (1 + rate)
|
||||
} else {
|
||||
result = future * rate / (term - 1) + present * rate / (1 - 1 / term)
|
||||
}
|
||||
}
|
||||
return -result
|
||||
}
|
||||
|
||||
func fv(rate, periods, payment, value float64, t int) float64 {
|
||||
var result float64
|
||||
if rate == 0 {
|
||||
result = value + payment * periods
|
||||
} else {
|
||||
term := math.Pow(1 + rate, periods)
|
||||
if t == 1 {
|
||||
result = value * term + payment * (1 + rate) * (term - 1) / rate
|
||||
} else {
|
||||
result = value * term + payment * (term - 1) / rate
|
||||
}
|
||||
}
|
||||
return -result
|
||||
}
|
||||
|
@ -1659,3 +1659,212 @@ func TestIf(t *testing.T) {
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestAccrintm(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
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`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestAmordegrc(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=AMORDEGRC(2400,39679,39813,300,1,0.15,1)`, `776 ResultTypeNumber`},
|
||||
{`=AMORDEGRC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestAmorlinc(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=AMORLINC(2400,39679,39813,300,1,0.15,1)`, `360 ResultTypeNumber`},
|
||||
{`=AMORLINC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCoupdaybs(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPDAYBS(40568,40862,2,0)`, `70 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40568,40862,2,1)`, `71 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40568,40862,2,2)`, `71 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40568,40862,2,3)`, `71 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40568,40862,2,4)`, `70 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40599,40862,2,0)`, `100 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40599,40862,2,1)`, `102 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40599,40862,2,2)`, `102 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40599,40862,2,3)`, `102 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40599,40862,2,4)`, `100 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40811,40862,2,0)`, `130 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40811,40862,2,1)`, `133 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40811,40862,2,2)`, `133 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40811,40862,2,3)`, `133 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40811,40862,2,4)`, `130 ResultTypeNumber`},
|
||||
{`=COUPDAYBS(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCoupdays(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPDAYS(40964,41228,1,0)`, `360 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,1,1)`, `366 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,1,2)`, `360 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,1,3)`, `365 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,1,4)`, `360 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,2,0)`, `180 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,2,1)`, `182 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,2,2)`, `180 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,2,3)`, `182.5 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,2,4)`, `180 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,4,0)`, `90 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,4,1)`, `90 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,4,2)`, `90 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,4,3)`, `91.25 ResultTypeNumber`},
|
||||
{`=COUPDAYS(40964,41228,4,4)`, `90 ResultTypeNumber`},
|
||||
{`=COUPDAYS(41228,40964,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCoupdaysnc(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPDAYSNC(40933,41228,1,0)`, `290 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,1,1)`, `295 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,1,2)`, `295 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,1,3)`, `295 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,1,4)`, `290 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,2,0)`, `110 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,2,1)`, `111 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,2,2)`, `111 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,2,3)`, `111 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,2,4)`, `110 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,4,0)`, `20 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,4,1)`, `21 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,4,2)`, `21 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,4,3)`, `21 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(40933,41228,4,4)`, `20 ResultTypeNumber`},
|
||||
{`=COUPDAYSNC(41228,40933,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCoupncd(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPNCD(40568,40862,1,1)`, `40862 ResultTypeNumber`},
|
||||
{`=COUPNCD(40568,40862,2,1)`, `40678 ResultTypeNumber`},
|
||||
{`=COUPNCD(40568,40862,4,1)`, `40589 ResultTypeNumber`},
|
||||
{`=COUPNCD(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCouppcd(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPPCD(40568,40862,2,1)`, `40497 ResultTypeNumber`},
|
||||
{`=COUPPCD(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCoupnum(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=COUPNUM(39107,39767,2,1)`, `4 ResultTypeNumber`},
|
||||
{`=COUPNUM(39767,39107,2,1)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCumipmt(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(0.09)
|
||||
sheet.Cell("A2").SetNumber(30)
|
||||
sheet.Cell("A3").SetNumber(125000)
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=CUMIPMT(A1/12,A2*12,A3,13,24,0)`, `-11135.23213 ResultTypeNumber`},
|
||||
{`=CUMIPMT(A1/12,A2*12,A3,1,1,0)`, `-937.5 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestCumprinc(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(0.09)
|
||||
sheet.Cell("A2").SetNumber(30)
|
||||
sheet.Cell("A3").SetNumber(125000)
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
td := []testStruct{
|
||||
{`=CUMPRINC(A1/12,A2*12,A3,13,24,0)`, `-934.10712342 ResultTypeNumber`},
|
||||
{`=CUMPRINC(A1/12,A2*12,A3,1,1,0)`, `-68.27827118 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user