Просмотр исходного кода

#65 fn: DB, DDB, DOLLARDE, DOLLARFR, EFFECT, ISPMT and NOMINAL

xuri 4 лет назад
Родитель
Сommit
68dd5b345c
2 измененных файлов с 320 добавлено и 0 удалено
  1. 246 0
      calc.go
  2. 74 0
      calc_test.go

+ 246 - 0
calc.go

@@ -265,11 +265,16 @@ var tokenPriority = map[string]int{
 //    CUMPRINC
 //    DATE
 //    DATEDIF
+//    DB
+//    DDB
 //    DEC2BIN
 //    DEC2HEX
 //    DEC2OCT
 //    DECIMAL
 //    DEGREES
+//    DOLLARDE
+//    DOLLARFR
+//    EFFECT
 //    ENCODEURL
 //    EVEN
 //    EXACT
@@ -332,6 +337,7 @@ var tokenPriority = map[string]int{
 //    ISODD
 //    ISTEXT
 //    ISO.CEILING
+//    ISPMT
 //    KURT
 //    LARGE
 //    LCM
@@ -357,6 +363,7 @@ var tokenPriority = map[string]int{
 //    MUNIT
 //    N
 //    NA
+//    NOMINAL
 //    NORM.DIST
 //    NORMDIST
 //    NORM.INV
@@ -7237,6 +7244,185 @@ func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg {
 	return newNumberFormulaArg(num)
 }
 
+// DB function calculates the depreciation of an asset, using the Fixed
+// Declining Balance Method, for each period of the asset's lifetime. The
+// syntax of the function is:
+//
+//    DB(cost,salvage,life,period,[month])
+//
+func (fn *formulaFuncs) DB(argsList *list.List) formulaArg {
+	if argsList.Len() < 4 {
+		return newErrorFormulaArg(formulaErrorVALUE, "DB requires at least 4 arguments")
+	}
+	if argsList.Len() > 5 {
+		return newErrorFormulaArg(formulaErrorVALUE, "DB allows at most 5 arguments")
+	}
+	cost := argsList.Front().Value.(formulaArg).ToNumber()
+	if cost.Type != ArgNumber {
+		return cost
+	}
+	salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
+	if salvage.Type != ArgNumber {
+		return salvage
+	}
+	life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
+	if life.Type != ArgNumber {
+		return life
+	}
+	period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
+	if period.Type != ArgNumber {
+		return period
+	}
+	month := newNumberFormulaArg(12)
+	if argsList.Len() == 5 {
+		if month = argsList.Back().Value.(formulaArg).ToNumber(); month.Type != ArgNumber {
+			return month
+		}
+	}
+	if cost.Number == 0 {
+		return newNumberFormulaArg(0)
+	}
+	if (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) || (month.Number < 1) {
+		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+	}
+	dr := 1 - math.Pow(salvage.Number/cost.Number, 1/life.Number)
+	dr = math.Round(dr*1000) / 1000
+	pd, depreciation := 0.0, 0.0
+	for per := 1; per <= int(period.Number); per++ {
+		if per == 1 {
+			depreciation = cost.Number * dr * month.Number / 12
+		} else if per == int(life.Number+1) {
+			depreciation = (cost.Number - pd) * dr * (12 - month.Number) / 12
+		} else {
+			depreciation = (cost.Number - pd) * dr
+		}
+		pd += depreciation
+	}
+	return newNumberFormulaArg(depreciation)
+}
+
+// DDB function calculates the depreciation of an asset, using the Double
+// Declining Balance Method, or another specified depreciation rate. The
+// syntax of the function is:
+//
+//    DDB(cost,salvage,life,period,[factor])
+//
+func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg {
+	if argsList.Len() < 4 {
+		return newErrorFormulaArg(formulaErrorVALUE, "DDB requires at least 4 arguments")
+	}
+	if argsList.Len() > 5 {
+		return newErrorFormulaArg(formulaErrorVALUE, "DDB allows at most 5 arguments")
+	}
+	cost := argsList.Front().Value.(formulaArg).ToNumber()
+	if cost.Type != ArgNumber {
+		return cost
+	}
+	salvage := argsList.Front().Next().Value.(formulaArg).ToNumber()
+	if salvage.Type != ArgNumber {
+		return salvage
+	}
+	life := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
+	if life.Type != ArgNumber {
+		return life
+	}
+	period := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
+	if period.Type != ArgNumber {
+		return period
+	}
+	factor := newNumberFormulaArg(2)
+	if argsList.Len() == 5 {
+		if factor = argsList.Back().Value.(formulaArg).ToNumber(); factor.Type != ArgNumber {
+			return factor
+		}
+	}
+	if cost.Number == 0 {
+		return newNumberFormulaArg(0)
+	}
+	if (cost.Number <= 0) || ((salvage.Number / cost.Number) < 0) || (life.Number <= 0) || (period.Number < 1) || (factor.Number <= 0.0) || (period.Number > life.Number) {
+		return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
+	}
+	pd, depreciation := 0.0, 0.0
+	for per := 1; per <= int(period.Number); per++ {
+		depreciation = math.Min((cost.Number-pd)*(factor.Number/life.Number), (cost.Number - salvage.Number - pd))
+		pd += depreciation
+	}
+	return newNumberFormulaArg(depreciation)
+}
+
+// DOLLARDE function converts a dollar value in fractional notation, into a
+// dollar value expressed as a decimal. The syntax of the function is:
+//
+//    DOLLARDE(fractional_dollar,fraction)
+//
+func (fn *formulaFuncs) DOLLARDE(argsList *list.List) formulaArg {
+	return fn.dollar("DOLLARDE", argsList)
+}
+
+// DOLLARFR function converts a dollar value in decimal notation, into a
+// dollar value that is expressed in fractional notation. The syntax of the
+// function is:
+//
+//    DOLLARFR(decimal_dollar,fraction)
+//
+func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg {
+	return fn.dollar("DOLLARFR", argsList)
+}
+
+// dollar is an implementation of the formula function DOLLARDE and DOLLARFR.
+func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg {
+	if argsList.Len() != 2 {
+		return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
+	}
+	dollar := argsList.Front().Value.(formulaArg).ToNumber()
+	if dollar.Type != ArgNumber {
+		return dollar
+	}
+	frac := argsList.Back().Value.(formulaArg).ToNumber()
+	if frac.Type != ArgNumber {
+		return frac
+	}
+	if frac.Number < 0 {
+		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
+	}
+	if frac.Number == 0 {
+		return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
+	}
+	cents := math.Mod(dollar.Number, 1)
+	if name == "DOLLARDE" {
+		cents /= frac.Number
+		cents *= math.Pow(10, math.Ceil(math.Log10(frac.Number)))
+	} else {
+		cents *= frac.Number
+		cents *= math.Pow(10, -math.Ceil(math.Log10(frac.Number)))
+	}
+	return newNumberFormulaArg(math.Floor(dollar.Number) + cents)
+}
+
+// EFFECT function returns the effective annual interest rate for a given
+// nominal interest rate and number of compounding periods per year. The
+// syntax of the function is:
+//
+//    EFFECT(nominal_rate,npery)
+//
+func (fn *formulaFuncs) EFFECT(argsList *list.List) formulaArg {
+	if argsList.Len() != 2 {
+		return newErrorFormulaArg(formulaErrorVALUE, "EFFECT requires 2 arguments")
+	}
+	rate := argsList.Front().Value.(formulaArg).ToNumber()
+	if rate.Type != ArgNumber {
+		return rate
+	}
+	npery := argsList.Back().Value.(formulaArg).ToNumber()
+	if npery.Type != ArgNumber {
+		return npery
+	}
+	if rate.Number <= 0 || npery.Number < 1 {
+		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
+	}
+	return newNumberFormulaArg(math.Pow((1+rate.Number/npery.Number), npery.Number) - 1)
+}
+
 // IPMT function calculates the interest payment, during a specific period of a
 // loan or investment that is paid in constant periodic payments, with a
 // constant interest rate. The syntax of the function is:
@@ -7310,6 +7496,66 @@ func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg {
 	return newNumberFormulaArg(principal)
 }
 
+// ISPMT function calculates the interest paid during a specific period of a
+// loan or investment. The syntax of the function is:
+//
+//    ISPMT(rate,per,nper,pv)
+//
+func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg {
+	if argsList.Len() != 4 {
+		return newErrorFormulaArg(formulaErrorVALUE, "ISPMT requires 4 arguments")
+	}
+	rate := argsList.Front().Value.(formulaArg).ToNumber()
+	if rate.Type != ArgNumber {
+		return rate
+	}
+	per := argsList.Front().Next().Value.(formulaArg).ToNumber()
+	if per.Type != ArgNumber {
+		return per
+	}
+	nper := argsList.Back().Prev().Value.(formulaArg).ToNumber()
+	if nper.Type != ArgNumber {
+		return nper
+	}
+	pv := argsList.Back().Value.(formulaArg).ToNumber()
+	if pv.Type != ArgNumber {
+		return pv
+	}
+	pr, payment, num := pv.Number, pv.Number/nper.Number, 0.0
+	for i := 0; i <= int(per.Number); i++ {
+		num = rate.Number * pr * -1
+		pr -= payment
+		if i == int(nper.Number) {
+			num = 0
+		}
+	}
+	return newNumberFormulaArg(num)
+}
+
+// NOMINAL function returns the nominal interest rate for a given effective
+// interest rate and number of compounding periods per year. The syntax of
+// the function is:
+//
+//    NOMINAL(effect_rate,npery)
+//
+func (fn *formulaFuncs) NOMINAL(argsList *list.List) formulaArg {
+	if argsList.Len() != 2 {
+		return newErrorFormulaArg(formulaErrorVALUE, "NOMINAL requires 2 arguments")
+	}
+	rate := argsList.Front().Value.(formulaArg).ToNumber()
+	if rate.Type != ArgNumber {
+		return rate
+	}
+	npery := argsList.Back().Value.(formulaArg).ToNumber()
+	if npery.Type != ArgNumber {
+		return npery
+	}
+	if rate.Number <= 0 || npery.Number < 1 {
+		return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
+	}
+	return newNumberFormulaArg(npery.Number * (math.Pow(rate.Number+1, 1/npery.Number) - 1))
+}
+
 // PMT function calculates the constant periodic payment required to pay off
 // (or partially pay off) a loan or investment, with a constant interest
 // rate, over a specified period. The syntax of the function is:

+ 74 - 0
calc_test.go

@@ -1139,9 +1139,35 @@ func TestCalcCellValue(t *testing.T) {
 		// CUMPRINC
 		"=CUMPRINC(0.05/12,60,50000,1,12,0)":  "-9027.762649079885",
 		"=CUMPRINC(0.05/12,60,50000,13,24,0)": "-9489.640119832635",
+		// DB
+		"=DB(0,1000,5,1)":       "0",
+		"=DB(10000,1000,5,1)":   "3690",
+		"=DB(10000,1000,5,2)":   "2328.39",
+		"=DB(10000,1000,5,1,6)": "1845",
+		"=DB(10000,1000,5,6,6)": "238.52712458788187",
+		// DDB
+		"=DDB(0,1000,5,1)":     "0",
+		"=DDB(10000,1000,5,1)": "4000",
+		"=DDB(10000,1000,5,2)": "2400",
+		"=DDB(10000,1000,5,3)": "1440",
+		"=DDB(10000,1000,5,4)": "864",
+		"=DDB(10000,1000,5,5)": "296",
+		// DOLLARDE
+		"=DOLLARDE(1.01,16)": "1.0625",
+		// DOLLARFR
+		"=DOLLARFR(1.0625,16)": "1.01",
+		// EFFECT
+		"=EFFECT(0.1,4)":   "0.103812890625",
+		"=EFFECT(0.025,2)": "0.02515625",
 		// IPMT
 		"=IPMT(0.05/12,2,60,50000)":   "-205.26988187971995",
 		"=IPMT(0.035/4,2,8,0,5000,1)": "5.257455237829077",
+		// ISPMT
+		"=ISPMT(0.05/12,1,60,50000)": "-204.8611111111111",
+		"=ISPMT(0.05/12,2,60,50000)": "-201.38888888888886",
+		"=ISPMT(0.05/12,2,1,50000)":  "208.33333333333334",
+		// NOMINAL
+		"=NOMINAL(0.025,12)": "0.024718035238113",
 		// PMT
 		"=PMT(0,8,0,5000,1)":       "-625",
 		"=PMT(0.035/4,8,0,5000,1)": "-600.8520271804658",
@@ -2058,6 +2084,42 @@ func TestCalcCellValue(t *testing.T) {
 		"=CUMPRINC(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
 		"=CUMPRINC(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
 		"=CUMPRINC(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		// DB
+		"=DB()":             "DB requires at least 4 arguments",
+		"=DB(0,0,0,0,0,0)":  "DB allows at most 5 arguments",
+		"=DB(-1,0,0,0)":     "#N/A",
+		"=DB(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DB(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DB(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DB(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DB(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		// DDB
+		"=DDB()":             "DDB requires at least 4 arguments",
+		"=DDB(0,0,0,0,0,0)":  "DDB allows at most 5 arguments",
+		"=DDB(-1,0,0,0)":     "#N/A",
+		"=DDB(\"\",0,0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DDB(0,\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DDB(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DDB(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DDB(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		// DOLLARDE
+		"=DOLLARDE()":       "DOLLARDE requires 2 arguments",
+		"=DOLLARDE(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DOLLARDE(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DOLLARDE(0,-1)":   "#NUM!",
+		"=DOLLARDE(0,0)":    "#DIV/0!",
+		// DOLLARFR
+		"=DOLLARFR()":       "DOLLARFR requires 2 arguments",
+		"=DOLLARFR(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DOLLARFR(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=DOLLARFR(0,-1)":   "#NUM!",
+		"=DOLLARFR(0,0)":    "#DIV/0!",
+		// EFFECT
+		"=EFFECT()":       "EFFECT requires 2 arguments",
+		"=EFFECT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=EFFECT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=EFFECT(0,0)":    "#NUM!",
+		"=EFFECT(1,0)":    "#NUM!",
 		// IPMT
 		"=IPMT()":               "IPMT requires at least 4 arguments",
 		"=IPMT(0,0,0,0,0,0,0)":  "IPMT allows at most 6 arguments",
@@ -2070,6 +2132,18 @@ func TestCalcCellValue(t *testing.T) {
 		"=IPMT(0,0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
 		"=IPMT(0,0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
 		"=IPMT(0,0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		// ISPMT
+		"=ISPMT()":           "ISPMT requires 4 arguments",
+		"=ISPMT(\"\",0,0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=ISPMT(0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=ISPMT(0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=ISPMT(0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		// NOMINAL
+		"=NOMINAL()":       "NOMINAL requires 2 arguments",
+		"=NOMINAL(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=NOMINAL(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+		"=NOMINAL(0,0)":    "#NUM!",
+		"=NOMINAL(1,0)":    "#NUM!",
 		// PMT
 		"=PMT()":             "PMT requires at least 3 arguments",
 		"=PMT(0,0,0,0,0,0)":  "PMT allows at most 5 arguments",