ref #65: new formula functions ACCRINTM and AMORDEGRC

This commit is contained in:
xuri 2021-10-17 22:51:33 +08:00
parent cf8766df83
commit c89b64c53c
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
2 changed files with 183 additions and 0 deletions

147
calc.go
View File

@ -261,10 +261,12 @@ type formulaFuncs struct {
// Supported formula functions: // Supported formula functions:
// //
// ABS // ABS
// ACCRINTM
// ACOS // ACOS
// ACOSH // ACOSH
// ACOT // ACOT
// ACOTH // ACOTH
// AMORDEGRC
// AND // AND
// ARABIC // ARABIC
// ASIN // ASIN
@ -8312,6 +8314,151 @@ func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg {
// Financial Functions // Financial Functions
// ACCRINTM function returns the accrued interest for a security that pays
// interest at maturity. The syntax of the function is:
//
// ACCRINTM(issue,settlement,rate,[par],[basis])
//
func (fn *formulaFuncs) ACCRINTM(argsList *list.List) formulaArg {
if argsList.Len() != 4 && argsList.Len() != 5 {
return newErrorFormulaArg(formulaErrorVALUE, "ACCRINTM requires 4 or 5 arguments")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
issue := fn.DATEVALUE(args)
if issue.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args.Init()
args.PushBack(argsList.Front().Next().Value.(formulaArg))
settlement := fn.DATEVALUE(args)
if settlement.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if settlement.Number < issue.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
par := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber || par.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if par.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
frac := yearFrac(issue.Number, settlement.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
return newNumberFormulaArg(frac.Number * rate.Number * par.Number)
}
// prepareAmorArgs checking and prepare arguments for the formula functions
// AMORDEGRC and AMORLINC.
func (fn *formulaFuncs) prepareAmorArgs(name string, argsList *list.List) formulaArg {
cost := argsList.Front().Value.(formulaArg).ToNumber()
if cost.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost to be number argument", name))
}
if cost.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires cost >= 0", name))
}
args := list.New().Init()
args.PushBack(argsList.Front().Next().Value.(formulaArg))
datePurchased := fn.DATEVALUE(args)
if datePurchased.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args.Init()
args.PushBack(argsList.Front().Next().Next().Value.(formulaArg))
firstPeriod := fn.DATEVALUE(args)
if firstPeriod.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if firstPeriod.Number < datePurchased.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
salvage := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
if salvage.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if salvage.Number < 0 || salvage.Number > cost.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
period := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if period.Type != ArgNumber || period.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
rate := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
if rate.Type != ArgNumber || rate.Number < 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 7 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
return newListFormulaArg([]formulaArg{cost, datePurchased, firstPeriod, salvage, period, rate, basis})
}
// AMORDEGRC function is provided for users of the French accounting system.
// The function calculates the prorated linear depreciation of an asset for a
// specified accounting period. The syntax of the function is:
//
// AMORDEGRC(cost,date_purchased,first_period,salvage,period,rate,[basis])
//
func (fn *formulaFuncs) AMORDEGRC(argsList *list.List) formulaArg {
if argsList.Len() != 6 && argsList.Len() != 7 {
return newErrorFormulaArg(formulaErrorVALUE, "AMORDEGRC requires 6 or 7 arguments")
}
args := fn.prepareAmorArgs("AMORDEGRC", argsList)
if args.Type != ArgList {
return args
}
cost, datePurchased, firstPeriod, salvage, period, rate, basis := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6]
if rate.Number >= 0.5 {
return newErrorFormulaArg(formulaErrorNUM, "AMORDEGRC requires rate to be < 0.5")
}
assetsLife, amorCoeff := 1/rate.Number, 2.5
if assetsLife < 3 {
amorCoeff = 1
} else if assetsLife < 5 {
amorCoeff = 1.5
} else if assetsLife <= 6 {
amorCoeff = 2
}
rate.Number *= amorCoeff
frac := yearFrac(datePurchased.Number, firstPeriod.Number, int(basis.Number))
if frac.Type != ArgNumber {
return frac
}
nRate := float64(int((frac.Number * cost.Number * rate.Number) + 0.5))
cost.Number -= nRate
rest := cost.Number - salvage.Number
for n := 0; n < int(period.Number); n++ {
nRate = float64(int((cost.Number * rate.Number) + 0.5))
rest -= nRate
if rest < 0 {
switch int(period.Number) - n {
case 0:
case 1:
return newNumberFormulaArg(float64(int((cost.Number * 0.5) + 0.5)))
default:
return newNumberFormulaArg(0)
}
}
cost.Number -= nRate
}
return newNumberFormulaArg(nRate)
}
// CUMIPMT function calculates the cumulative interest paid on a loan or // CUMIPMT function calculates the cumulative interest paid on a loan or
// investment, between two specified periods. The syntax of the function is: // investment, between two specified periods. The syntax of the function is:
// //

View File

@ -1232,6 +1232,16 @@ func TestCalcCellValue(t *testing.T) {
// ENCODEURL // ENCODEURL
"=ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As", "=ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As",
// Financial Functions // Financial Functions
// ACCRINTM
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000)": "800",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,3)": "800",
// AMORDEGRC
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%)": "42",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,4)": "42",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,40%,4)": "42",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,25%,4)": "41",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",109,1,25%,4)": "54",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",110,2,25%,4)": "0",
// CUMIPMT // CUMIPMT
"=CUMIPMT(0.05/12,60,50000,1,12,0)": "-2294.97753732664", "=CUMIPMT(0.05/12,60,50000,1,12,0)": "-2294.97753732664",
"=CUMIPMT(0.05/12,60,50000,13,24,0)": "-1833.1000665738893", "=CUMIPMT(0.05/12,60,50000,13,24,0)": "-1833.1000665738893",
@ -2291,6 +2301,32 @@ func TestCalcCellValue(t *testing.T) {
// ENCODEURL // ENCODEURL
"=ENCODEURL()": "ENCODEURL requires 1 argument", "=ENCODEURL()": "ENCODEURL requires 1 argument",
// Financial Functions // Financial Functions
// ACCRINTM
"=ACCRINTM()": "ACCRINTM requires 4 or 5 arguments",
"=ACCRINTM(\"\",\"01/01/2012\",8%,10000)": "#VALUE!",
"=ACCRINTM(\"01/01/2012\",\"\",8%,10000)": "#VALUE!",
"=ACCRINTM(\"12/31/2012\",\"01/01/2012\",8%,10000)": "#NUM!",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",\"\",10000)": "#NUM!",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,\"\",10000)": "#NUM!",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,-1,10000)": "#NUM!",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,\"\")": "#NUM!",
"=ACCRINTM(\"01/01/2012\",\"12/31/2012\",8%,10000,5)": "invalid basis",
// AMORDEGRC
"=AMORDEGRC()": "AMORDEGRC requires 6 or 7 arguments",
"=AMORDEGRC(\"\",\"01/01/2015\",\"09/30/2015\",20,1,20%)": "AMORDEGRC requires cost to be number argument",
"=AMORDEGRC(-1,\"01/01/2015\",\"09/30/2015\",20,1,20%)": "AMORDEGRC requires cost >= 0",
"=AMORDEGRC(150,\"\",\"09/30/2015\",20,1,20%)": "#VALUE!",
"=AMORDEGRC(150,\"01/01/2015\",\"\",20,1,20%)": "#VALUE!",
"=AMORDEGRC(150,\"09/30/2015\",\"01/01/2015\",20,1,20%)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",\"\",1,20%)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",-1,1,20%)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,\"\",20%)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,-1,20%)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,\"\")": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,-1)": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,\"\")": "#NUM!",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,50%)": "AMORDEGRC requires rate to be < 0.5",
"=AMORDEGRC(150,\"01/01/2015\",\"09/30/2015\",20,1,20%,5)": "invalid basis",
// CUMIPMT // CUMIPMT
"=CUMIPMT()": "CUMIPMT requires 6 arguments", "=CUMIPMT()": "CUMIPMT requires 6 arguments",
"=CUMIPMT(0,0,0,0,0,2)": "#N/A", "=CUMIPMT(0,0,0,0,0,2)": "#N/A",