ref #65, add support for 10 formula functions
- Add support for 10 formula functions: ARRAYTOTEXT, FORECAST, FORECAST.LINEAR, FREQUENCY, INTERCEPT, ODDFYIELD, ODDLPRICE, ODDLYIELD, PROB and VALUETOTEXT - Update unit tests
This commit is contained in:
parent
15614badfc
commit
4957ee9abc
540
calc.go
540
calc.go
|
@ -365,6 +365,7 @@ type formulaFuncs struct {
|
|||
// AMORLINC
|
||||
// AND
|
||||
// ARABIC
|
||||
// ARRAYTOTEXT
|
||||
// ASIN
|
||||
// ASINH
|
||||
// ATAN
|
||||
|
@ -510,7 +511,10 @@ type formulaFuncs struct {
|
|||
// FLOOR
|
||||
// FLOOR.MATH
|
||||
// FLOOR.PRECISE
|
||||
// FORECAST
|
||||
// FORECAST.LINEAR
|
||||
// FORMULATEXT
|
||||
// FREQUENCY
|
||||
// FTEST
|
||||
// FV
|
||||
// FVSCHEDULE
|
||||
|
@ -567,6 +571,7 @@ type formulaFuncs struct {
|
|||
// INDEX
|
||||
// INDIRECT
|
||||
// INT
|
||||
// INTERCEPT
|
||||
// INTRATE
|
||||
// IPMT
|
||||
// IRR
|
||||
|
@ -649,6 +654,9 @@ type formulaFuncs struct {
|
|||
// OCT2HEX
|
||||
// ODD
|
||||
// ODDFPRICE
|
||||
// ODDFYIELD
|
||||
// ODDLPRICE
|
||||
// ODDLYIELD
|
||||
// OR
|
||||
// PDURATION
|
||||
// PEARSON
|
||||
|
@ -670,6 +678,7 @@ type formulaFuncs struct {
|
|||
// PRICE
|
||||
// PRICEDISC
|
||||
// PRICEMAT
|
||||
// PROB
|
||||
// PRODUCT
|
||||
// PROPER
|
||||
// PV
|
||||
|
@ -763,6 +772,7 @@ type formulaFuncs struct {
|
|||
// UNICODE
|
||||
// UPPER
|
||||
// VALUE
|
||||
// VALUETOTEXT
|
||||
// VAR
|
||||
// VAR.P
|
||||
// VAR.S
|
||||
|
@ -5657,6 +5667,76 @@ func (fn *formulaFuncs) POISSON(argsList *list.List) formulaArg {
|
|||
return newNumberFormulaArg(math.Exp(0-mean.Number) * math.Pow(mean.Number, x.Number) / fact(x.Number))
|
||||
}
|
||||
|
||||
// prepareProbArgs checking and prepare arguments for the formula function
|
||||
// PROB.
|
||||
func prepareProbArgs(argsList *list.List) []formulaArg {
|
||||
if argsList.Len() < 3 {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at least 3 arguments")}
|
||||
}
|
||||
if argsList.Len() > 4 {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, "PROB requires at most 4 arguments")}
|
||||
}
|
||||
var lower, upper formulaArg
|
||||
xRange := argsList.Front().Value.(formulaArg)
|
||||
probRange := argsList.Front().Next().Value.(formulaArg)
|
||||
if lower = argsList.Front().Next().Next().Value.(formulaArg); lower.Type != ArgNumber {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)}
|
||||
}
|
||||
upper = lower
|
||||
if argsList.Len() == 4 {
|
||||
if upper = argsList.Back().Value.(formulaArg); upper.Type != ArgNumber {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)}
|
||||
}
|
||||
}
|
||||
nR1, nR2 := len(xRange.Matrix), len(probRange.Matrix)
|
||||
if nR1 == 0 || nR2 == 0 {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)}
|
||||
}
|
||||
if nR1 != nR2 {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)}
|
||||
}
|
||||
nC1, nC2 := len(xRange.Matrix[0]), len(probRange.Matrix[0])
|
||||
if nC1 != nC2 {
|
||||
return []formulaArg{newErrorFormulaArg(formulaErrorNA, formulaErrorNA)}
|
||||
}
|
||||
return []formulaArg{xRange, probRange, lower, upper}
|
||||
}
|
||||
|
||||
// PROB function calculates the probability associated with a given range. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
// PROB(x_range,prob_range,lower_limit,[upper_limit])
|
||||
func (fn *formulaFuncs) PROB(argsList *list.List) formulaArg {
|
||||
args := prepareProbArgs(argsList)
|
||||
if len(args) == 1 {
|
||||
return args[0]
|
||||
}
|
||||
xRange, probRange, lower, upper := args[0], args[1], args[2], args[3]
|
||||
var sum, res, fP, fW float64
|
||||
var stop bool
|
||||
for r := 0; r < len(xRange.Matrix) && !stop; r++ {
|
||||
for c := 0; c < len(xRange.Matrix[0]) && !stop; c++ {
|
||||
p := probRange.Matrix[r][c]
|
||||
x := xRange.Matrix[r][c]
|
||||
if p.Type == ArgNumber && x.Type == ArgNumber {
|
||||
if fP, fW = p.Number, x.Number; fP < 0 || fP > 1 {
|
||||
stop = true
|
||||
continue
|
||||
}
|
||||
if sum += fP; fW >= lower.Number && fW <= upper.Number {
|
||||
res += fP
|
||||
}
|
||||
continue
|
||||
}
|
||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
}
|
||||
if stop || math.Abs(sum-1) > 1.0e-7 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
return newNumberFormulaArg(res)
|
||||
}
|
||||
|
||||
// SUBTOTAL function performs a specified calculation (e.g. the sum, product,
|
||||
// average, etc.) for a supplied set of values. The syntax of the function is:
|
||||
//
|
||||
|
@ -7933,6 +8013,92 @@ func (fn *formulaFuncs) FISHERINV(argsList *list.List) formulaArg {
|
|||
return newErrorFormulaArg(formulaErrorVALUE, "FISHERINV requires 1 numeric argument")
|
||||
}
|
||||
|
||||
// FORECAST function predicts a future point on a linear trend line fitted to a
|
||||
// supplied set of x- and y- values. The syntax of the function is:
|
||||
//
|
||||
// FORECAST(x,known_y's,known_x's)
|
||||
func (fn *formulaFuncs) FORECAST(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("FORECAST", 3, argsList)
|
||||
}
|
||||
|
||||
// FORECASTdotLINEAR function predicts a future point on a linear trend line
|
||||
// fitted to a supplied set of x- and y- values. The syntax of the function is:
|
||||
//
|
||||
// FORECAST.LINEAR(x,known_y's,known_x's)
|
||||
func (fn *formulaFuncs) FORECASTdotLINEAR(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("FORECAST.LINEAR", 3, argsList)
|
||||
}
|
||||
|
||||
// maritxToSortedColumnList convert matrix formula arguments to a ascending
|
||||
// order list by column.
|
||||
func maritxToSortedColumnList(arg formulaArg) formulaArg {
|
||||
mtx, cols := []formulaArg{}, len(arg.Matrix[0])
|
||||
for colIdx := 0; colIdx < cols; colIdx++ {
|
||||
for _, row := range arg.Matrix {
|
||||
cell := row[colIdx]
|
||||
if cell.Type == ArgError {
|
||||
return cell
|
||||
}
|
||||
if cell.Type == ArgNumber {
|
||||
mtx = append(mtx, cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
argsList := newListFormulaArg(mtx)
|
||||
sort.Slice(argsList.List, func(i, j int) bool {
|
||||
return argsList.List[i].Number < argsList.List[j].Number
|
||||
})
|
||||
return argsList
|
||||
}
|
||||
|
||||
// FREQUENCY function to count how many children fall into different age
|
||||
// ranges. The syntax of the function is:
|
||||
//
|
||||
// FREQUENCY(data_array,bins_array)
|
||||
func (fn *formulaFuncs) FREQUENCY(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "FREQUENCY requires 2 arguments")
|
||||
}
|
||||
data, bins := argsList.Front().Value.(formulaArg), argsList.Back().Value.(formulaArg)
|
||||
if len(data.Matrix) == 0 {
|
||||
data.Matrix = [][]formulaArg{{data}}
|
||||
}
|
||||
if len(bins.Matrix) == 0 {
|
||||
bins.Matrix = [][]formulaArg{{bins}}
|
||||
}
|
||||
var (
|
||||
dataMtx, binsMtx formulaArg
|
||||
c [][]formulaArg
|
||||
i, j int
|
||||
)
|
||||
if dataMtx = maritxToSortedColumnList(data); dataMtx.Type != ArgList {
|
||||
return dataMtx
|
||||
}
|
||||
if binsMtx = maritxToSortedColumnList(bins); binsMtx.Type != ArgList {
|
||||
return binsMtx
|
||||
}
|
||||
for row := 0; row < len(binsMtx.List)+1; row++ {
|
||||
rows := []formulaArg{}
|
||||
for col := 0; col < 1; col++ {
|
||||
rows = append(rows, newNumberFormulaArg(0))
|
||||
}
|
||||
c = append(c, rows)
|
||||
}
|
||||
for j = 0; j < len(binsMtx.List); j++ {
|
||||
n := 0.0
|
||||
for i < len(dataMtx.List) && dataMtx.List[i].Number <= binsMtx.List[j].Number {
|
||||
n++
|
||||
i++
|
||||
}
|
||||
c[j] = []formulaArg{newNumberFormulaArg(n)}
|
||||
}
|
||||
c[j] = []formulaArg{newNumberFormulaArg(float64(len(dataMtx.List) - i))}
|
||||
if len(c) > 2 {
|
||||
c[1], c[2] = c[2], c[1]
|
||||
}
|
||||
return newMatrixFormulaArg(c)
|
||||
}
|
||||
|
||||
// GAMMA function returns the value of the Gamma Function, Γ(n), for a
|
||||
// specified number, n. The syntax of the function is:
|
||||
//
|
||||
|
@ -8953,6 +9119,15 @@ func (fn *formulaFuncs) HYPGEOMDIST(argsList *list.List) formulaArg {
|
|||
binomCoeff(numberPop.Number, numberSample.Number))
|
||||
}
|
||||
|
||||
// INTERCEPT function calculates the intercept (the value at the intersection
|
||||
// of the y axis) of the linear regression line through a supplied set of x-
|
||||
// and y- values. The syntax of the function is:
|
||||
//
|
||||
// INTERCEPT(known_y's,known_x's)
|
||||
func (fn *formulaFuncs) INTERCEPT(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("INTERCEPT", 2, argsList)
|
||||
}
|
||||
|
||||
// KURT function calculates the kurtosis of a supplied set of values. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
|
@ -10013,19 +10188,23 @@ func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg {
|
|||
return newNumberFormulaArg(min)
|
||||
}
|
||||
|
||||
// pearsonProduct is an implementation of the formula functions PEARSON, RSQ
|
||||
// and SLOPE.
|
||||
func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
|
||||
// pearsonProduct is an implementation of the formula functions FORECAST,
|
||||
// FORECAST.LINEAR, INTERCEPT, PEARSON, RSQ and SLOPE.
|
||||
func (fn *formulaFuncs) pearsonProduct(name string, n int, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != n {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires %d arguments", name, n))
|
||||
}
|
||||
var array1, array2 []formulaArg
|
||||
if name == "SLOPE" {
|
||||
array1 = argsList.Back().Value.(formulaArg).ToList()
|
||||
array2 = argsList.Front().Value.(formulaArg).ToList()
|
||||
} else {
|
||||
array1 = argsList.Front().Value.(formulaArg).ToList()
|
||||
array2 = argsList.Back().Value.(formulaArg).ToList()
|
||||
var fx formulaArg
|
||||
array1 := argsList.Back().Value.(formulaArg).ToList()
|
||||
array2 := argsList.Front().Value.(formulaArg).ToList()
|
||||
if name == "PEARSON" || name == "RSQ" {
|
||||
array1, array2 = array2, array1
|
||||
}
|
||||
if n == 3 {
|
||||
if fx = argsList.Front().Value.(formulaArg).ToNumber(); fx.Type != ArgNumber {
|
||||
return fx
|
||||
}
|
||||
array2 = argsList.Front().Next().Value.(formulaArg).ToList()
|
||||
}
|
||||
if len(array1) != len(array2) {
|
||||
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
||||
|
@ -10051,16 +10230,17 @@ func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formula
|
|||
deltaX += (num1.Number - x) * (num1.Number - x)
|
||||
deltaY += (num2.Number - y) * (num2.Number - y)
|
||||
}
|
||||
if deltaX == 0 || deltaY == 0 {
|
||||
if sum*deltaX*deltaY == 0 {
|
||||
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
|
||||
}
|
||||
if name == "RSQ" {
|
||||
return newNumberFormulaArg(math.Pow(sum/math.Sqrt(deltaX*deltaY), 2))
|
||||
}
|
||||
if name == "PEARSON" {
|
||||
return newNumberFormulaArg(sum / math.Sqrt(deltaX*deltaY))
|
||||
}
|
||||
return newNumberFormulaArg(sum / deltaX)
|
||||
return newNumberFormulaArg(map[string]float64{
|
||||
"FORECAST": y + sum/deltaX*(fx.Number-x),
|
||||
"FORECAST.LINEAR": y + sum/deltaX*(fx.Number-x),
|
||||
"INTERCEPT": y - sum/deltaX*x,
|
||||
"PEARSON": sum / math.Sqrt(deltaX*deltaY),
|
||||
"RSQ": math.Pow(sum/math.Sqrt(deltaX*deltaY), 2),
|
||||
"SLOPE": sum / deltaX,
|
||||
}[name])
|
||||
}
|
||||
|
||||
// PEARSON function calculates the Pearson Product-Moment Correlation
|
||||
|
@ -10068,7 +10248,7 @@ func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formula
|
|||
//
|
||||
// PEARSON(array1,array2)
|
||||
func (fn *formulaFuncs) PEARSON(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("PEARSON", argsList)
|
||||
return fn.pearsonProduct("PEARSON", 2, argsList)
|
||||
}
|
||||
|
||||
// PERCENTILEdotEXC function returns the k'th percentile (i.e. the value below
|
||||
|
@ -10407,7 +10587,7 @@ func (fn *formulaFuncs) RANK(argsList *list.List) formulaArg {
|
|||
//
|
||||
// RSQ(known_y's,known_x's)
|
||||
func (fn *formulaFuncs) RSQ(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("RSQ", argsList)
|
||||
return fn.pearsonProduct("RSQ", 2, argsList)
|
||||
}
|
||||
|
||||
// skew is an implementation of the formula functions SKEW and SKEW.P.
|
||||
|
@ -10475,7 +10655,7 @@ func (fn *formulaFuncs) SKEWdotP(argsList *list.List) formulaArg {
|
|||
//
|
||||
// SLOPE(known_y's,known_x's)
|
||||
func (fn *formulaFuncs) SLOPE(argsList *list.List) formulaArg {
|
||||
return fn.pearsonProduct("SLOPE", argsList)
|
||||
return fn.pearsonProduct("SLOPE", 2, argsList)
|
||||
}
|
||||
|
||||
// SMALL function returns the k'th smallest value from an array of numeric
|
||||
|
@ -13203,8 +13383,65 @@ func (fn *formulaFuncs) WEEKNUM(argsList *list.List) formulaArg {
|
|||
|
||||
// Text Functions
|
||||
|
||||
// prepareToText checking and prepare arguments for the formula functions
|
||||
// ARRAYTOTEXT and VALUETOTEXT.
|
||||
func prepareToText(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() < 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name))
|
||||
}
|
||||
if argsList.Len() > 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name))
|
||||
}
|
||||
format := newNumberFormulaArg(0)
|
||||
if argsList.Len() == 2 {
|
||||
if format = argsList.Back().Value.(formulaArg).ToNumber(); format.Type != ArgNumber {
|
||||
return format
|
||||
}
|
||||
}
|
||||
if format.Number != 0 && format.Number != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// ARRAYTOTEXT function returns an array of text values from any specified
|
||||
// range. It passes text values unchanged, and converts non-text values to
|
||||
// text. The syntax of the function is:
|
||||
//
|
||||
// ARRAYTOTEXT(array,[format])
|
||||
func (fn *formulaFuncs) ARRAYTOTEXT(argsList *list.List) formulaArg {
|
||||
var mtx [][]string
|
||||
format := prepareToText("ARRAYTOTEXT", argsList)
|
||||
if format.Type != ArgNumber {
|
||||
return format
|
||||
}
|
||||
for _, rows := range argsList.Front().Value.(formulaArg).Matrix {
|
||||
var row []string
|
||||
for _, cell := range rows {
|
||||
if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 {
|
||||
row = append(row, fmt.Sprintf("\"%s\"", cell.Value()))
|
||||
continue
|
||||
}
|
||||
row = append(row, cell.Value())
|
||||
}
|
||||
mtx = append(mtx, row)
|
||||
}
|
||||
var text []string
|
||||
for _, row := range mtx {
|
||||
if format.Number == 1 {
|
||||
text = append(text, strings.Join(row, ","))
|
||||
continue
|
||||
}
|
||||
text = append(text, strings.Join(row, ", "))
|
||||
}
|
||||
if format.Number == 1 {
|
||||
return newStringFormulaArg(fmt.Sprintf("{%s}", strings.Join(text, ";")))
|
||||
}
|
||||
return newStringFormulaArg(strings.Join(text, ", "))
|
||||
}
|
||||
|
||||
// CHAR function returns the character relating to a supplied character set
|
||||
// number (from 1 to 255). syntax of the function is:
|
||||
// number (from 1 to 255). The syntax of the function is:
|
||||
//
|
||||
// CHAR(number)
|
||||
func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg {
|
||||
|
@ -13873,6 +14110,22 @@ func (fn *formulaFuncs) VALUE(argsList *list.List) formulaArg {
|
|||
return newNumberFormulaArg(dateValue + timeValue)
|
||||
}
|
||||
|
||||
// VALUETOTEXT function returns text from any specified value. It passes text
|
||||
// values unchanged, and converts non-text values to text.
|
||||
//
|
||||
// VALUETOTEXT(value,[format])
|
||||
func (fn *formulaFuncs) VALUETOTEXT(argsList *list.List) formulaArg {
|
||||
format := prepareToText("VALUETOTEXT", argsList)
|
||||
if format.Type != ArgNumber {
|
||||
return format
|
||||
}
|
||||
cell := argsList.Front().Value.(formulaArg)
|
||||
if num := cell.ToNumber(); num.Type != ArgNumber && format.Number == 1 {
|
||||
return newStringFormulaArg(fmt.Sprintf("\"%s\"", cell.Value()))
|
||||
}
|
||||
return newStringFormulaArg(cell.Value())
|
||||
}
|
||||
|
||||
// Conditional Functions
|
||||
|
||||
// IF function tests a supplied condition and returns one result if the
|
||||
|
@ -16401,43 +16654,56 @@ func coupNumber(maturity, settlement, numMonths float64) float64 {
|
|||
return result
|
||||
}
|
||||
|
||||
// prepareOddfpriceArgs checking and prepare arguments for the formula
|
||||
// function ODDFPRICE.
|
||||
func (fn *formulaFuncs) prepareOddfpriceArgs(argsList *list.List) formulaArg {
|
||||
// prepareOddYldOrPrArg checking and prepare yield or price arguments for the
|
||||
// formula functions ODDFPRICE, ODDFYIELD, ODDLPRICE and ODDLYIELD.
|
||||
func prepareOddYldOrPrArg(name string, arg formulaArg) formulaArg {
|
||||
yldOrPr := arg.ToNumber()
|
||||
if yldOrPr.Type != ArgNumber {
|
||||
return yldOrPr
|
||||
}
|
||||
if (name == "ODDFPRICE" || name == "ODDLPRICE") && yldOrPr.Number < 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires yld >= 0", name))
|
||||
}
|
||||
if (name == "ODDFYIELD" || name == "ODDLYIELD") && yldOrPr.Number <= 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires pr > 0", name))
|
||||
}
|
||||
return yldOrPr
|
||||
}
|
||||
|
||||
// prepareOddfArgs checking and prepare arguments for the formula
|
||||
// functions ODDFPRICE and ODDFYIELD.
|
||||
func (fn *formulaFuncs) prepareOddfArgs(name string, argsList *list.List) formulaArg {
|
||||
dateValues := fn.prepareDataValueArgs(4, argsList)
|
||||
if dateValues.Type != ArgList {
|
||||
return dateValues
|
||||
}
|
||||
settlement, maturity, issue, firstCoupon := dateValues.List[0], dateValues.List[1], dateValues.List[2], dateValues.List[3]
|
||||
if issue.Number >= settlement.Number {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires settlement > issue")
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > issue", name))
|
||||
}
|
||||
if settlement.Number >= firstCoupon.Number {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires first_coupon > settlement")
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires first_coupon > settlement", name))
|
||||
}
|
||||
if firstCoupon.Number >= maturity.Number {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires maturity > first_coupon")
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > first_coupon", name))
|
||||
}
|
||||
rate := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if rate.Type != ArgNumber {
|
||||
return rate
|
||||
}
|
||||
if rate.Number < 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires rate >= 0")
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
|
||||
}
|
||||
yld := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if yld.Type != ArgNumber {
|
||||
return yld
|
||||
}
|
||||
if yld.Number < 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires yld >= 0")
|
||||
yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg))
|
||||
if yldOrPr.Type != ArgNumber {
|
||||
return yldOrPr
|
||||
}
|
||||
redemption := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if redemption.Type != ArgNumber {
|
||||
return redemption
|
||||
}
|
||||
if redemption.Number <= 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "ODDFPRICE requires redemption > 0")
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
|
||||
}
|
||||
frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if frequency.Type != ArgNumber {
|
||||
|
@ -16452,7 +16718,7 @@ func (fn *formulaFuncs) prepareOddfpriceArgs(argsList *list.List) formulaArg {
|
|||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
}
|
||||
return newListFormulaArg([]formulaArg{settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basis})
|
||||
return newListFormulaArg([]formulaArg{settlement, maturity, issue, firstCoupon, rate, yldOrPr, redemption, frequency, basis})
|
||||
}
|
||||
|
||||
// ODDFPRICE function calculates the price per $100 face value of a security
|
||||
|
@ -16463,7 +16729,7 @@ func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg {
|
|||
if argsList.Len() != 8 && argsList.Len() != 9 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ODDFPRICE requires 8 or 9 arguments")
|
||||
}
|
||||
args := fn.prepareOddfpriceArgs(argsList)
|
||||
args := fn.prepareOddfArgs("ODDFPRICE", argsList)
|
||||
if args.Type != ArgList {
|
||||
return args
|
||||
}
|
||||
|
@ -16583,6 +16849,198 @@ func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg {
|
|||
return newNumberFormulaArg(term1 + term2 + term3[0] - term4)
|
||||
}
|
||||
|
||||
// getODDFPRICE is a part of implementation of the formula function ODDFPRICE.
|
||||
func getODDFPRICE(f func(yld float64) float64, x, cnt, prec float64) float64 {
|
||||
const maxCnt = 20.0
|
||||
d := func(f func(yld float64) float64, x float64) float64 {
|
||||
return (f(x+prec) - f(x-prec)) / (2 * prec)
|
||||
}
|
||||
fx, Fx := f(x), d(f, x)
|
||||
newX := x - (fx / Fx)
|
||||
if math.Abs(newX-x) < prec {
|
||||
return newX
|
||||
} else if cnt > maxCnt {
|
||||
return newX
|
||||
}
|
||||
return getODDFPRICE(f, newX, cnt+1, prec)
|
||||
}
|
||||
|
||||
// ODDFYIELD function calculates the yield of a security with an odd (short or
|
||||
// long) first period. The syntax of the function is:
|
||||
//
|
||||
// ODDFYIELD(settlement,maturity,issue,first_coupon,rate,pr,redemption,frequency,[basis])
|
||||
func (fn *formulaFuncs) ODDFYIELD(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 8 && argsList.Len() != 9 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ODDFYIELD requires 8 or 9 arguments")
|
||||
}
|
||||
args := fn.prepareOddfArgs("ODDFYIELD", argsList)
|
||||
if args.Type != ArgList {
|
||||
return args
|
||||
}
|
||||
settlement, maturity, issue, firstCoupon, rate, pr, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8]
|
||||
if basisArg.Number < 0 || basisArg.Number > 4 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
|
||||
}
|
||||
settlementTime := timeFromExcelTime(settlement.Number, false)
|
||||
maturityTime := timeFromExcelTime(maturity.Number, false)
|
||||
years := coupdays(settlementTime, maturityTime, int(basisArg.Number))
|
||||
px := pr.Number - 100
|
||||
num := rate.Number*years*100 - px
|
||||
denum := px/4 + years*px/2 + years*100
|
||||
guess := num / denum
|
||||
f := func(yld float64) float64 {
|
||||
fnArgs := list.New().Init()
|
||||
fnArgs.PushBack(settlement)
|
||||
fnArgs.PushBack(maturity)
|
||||
fnArgs.PushBack(issue)
|
||||
fnArgs.PushBack(firstCoupon)
|
||||
fnArgs.PushBack(rate)
|
||||
fnArgs.PushBack(newNumberFormulaArg(yld))
|
||||
fnArgs.PushBack(redemption)
|
||||
fnArgs.PushBack(frequency)
|
||||
fnArgs.PushBack(basisArg)
|
||||
return pr.Number - fn.ODDFPRICE(fnArgs).Number
|
||||
}
|
||||
if result := getODDFPRICE(f, guess, 0, 1e-7); !math.IsInf(result, 0) {
|
||||
return newNumberFormulaArg(result)
|
||||
}
|
||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
|
||||
// prepareOddlArgs checking and prepare arguments for the formula
|
||||
// functions ODDLPRICE and ODDLYIELD.
|
||||
func (fn *formulaFuncs) prepareOddlArgs(name string, argsList *list.List) formulaArg {
|
||||
dateValues := fn.prepareDataValueArgs(3, argsList)
|
||||
if dateValues.Type != ArgList {
|
||||
return dateValues
|
||||
}
|
||||
settlement, maturity, lastInterest := dateValues.List[0], dateValues.List[1], dateValues.List[2]
|
||||
if lastInterest.Number >= settlement.Number {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires settlement > last_interest", name))
|
||||
}
|
||||
if settlement.Number >= maturity.Number {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
|
||||
}
|
||||
rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if rate.Type != ArgNumber {
|
||||
return rate
|
||||
}
|
||||
if rate.Number < 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
|
||||
}
|
||||
yldOrPr := prepareOddYldOrPrArg(name, argsList.Front().Next().Next().Next().Next().Value.(formulaArg))
|
||||
if yldOrPr.Type != ArgNumber {
|
||||
return yldOrPr
|
||||
}
|
||||
redemption := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if redemption.Type != ArgNumber {
|
||||
return redemption
|
||||
}
|
||||
if redemption.Number <= 0 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
|
||||
}
|
||||
frequency := argsList.Front().Next().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if frequency.Type != ArgNumber {
|
||||
return frequency
|
||||
}
|
||||
if !validateFrequency(frequency.Number) {
|
||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
basis := newNumberFormulaArg(0)
|
||||
if argsList.Len() == 8 {
|
||||
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
|
||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||
}
|
||||
}
|
||||
return newListFormulaArg([]formulaArg{settlement, maturity, lastInterest, rate, yldOrPr, redemption, frequency, basis})
|
||||
}
|
||||
|
||||
// oddl is an implementation of the formula functions ODDLPRICE and ODDLYIELD.
|
||||
func (fn *formulaFuncs) oddl(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 7 && argsList.Len() != 8 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 7 or 8 arguments", name))
|
||||
}
|
||||
args := fn.prepareOddlArgs(name, argsList)
|
||||
if args.Type != ArgList {
|
||||
return args
|
||||
}
|
||||
settlement, maturity, lastInterest, rate, prOrYld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7]
|
||||
if basisArg.Number < 0 || basisArg.Number > 4 {
|
||||
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
|
||||
}
|
||||
settlementTime := timeFromExcelTime(settlement.Number, false)
|
||||
maturityTime := timeFromExcelTime(maturity.Number, false)
|
||||
basis := int(basisArg.Number)
|
||||
numMonths := 12 / frequency.Number
|
||||
fnArgs := list.New().Init()
|
||||
fnArgs.PushBack(lastInterest)
|
||||
fnArgs.PushBack(maturity)
|
||||
fnArgs.PushBack(frequency)
|
||||
fnArgs.PushBack(basisArg)
|
||||
nc := fn.COUPNUM(fnArgs)
|
||||
earlyCoupon := lastInterest.Number
|
||||
aggrFunc := func(acc []float64, index float64) []float64 {
|
||||
earlyCouponTime := timeFromExcelTime(earlyCoupon, false)
|
||||
lateCouponTime := changeMonth(earlyCouponTime, numMonths, false)
|
||||
lateCoupon, _ := timeToExcelTime(lateCouponTime, false)
|
||||
nl := coupdays(earlyCouponTime, lateCouponTime, basis)
|
||||
dci := coupdays(earlyCouponTime, maturityTime, basis)
|
||||
if index < nc.Number {
|
||||
dci = nl
|
||||
}
|
||||
var a float64
|
||||
if lateCoupon < settlement.Number {
|
||||
a = dci
|
||||
} else if earlyCoupon < settlement.Number {
|
||||
a = coupdays(earlyCouponTime, settlementTime, basis)
|
||||
}
|
||||
startDate := earlyCoupon
|
||||
if settlement.Number > earlyCoupon {
|
||||
startDate = settlement.Number
|
||||
}
|
||||
endDate := lateCoupon
|
||||
if maturity.Number < lateCoupon {
|
||||
endDate = maturity.Number
|
||||
}
|
||||
startDateTime := timeFromExcelTime(startDate, false)
|
||||
endDateTime := timeFromExcelTime(endDate, false)
|
||||
dsc := coupdays(startDateTime, endDateTime, basis)
|
||||
earlyCoupon = lateCoupon
|
||||
dcnl := acc[0]
|
||||
anl := acc[1]
|
||||
dscnl := acc[2]
|
||||
return []float64{dcnl + dci/nl, anl + a/nl, dscnl + dsc/nl}
|
||||
}
|
||||
ag := aggrBetween(1, math.Floor(nc.Number), []float64{0, 0, 0}, aggrFunc)
|
||||
dcnl, anl, dscnl := ag[0], ag[1], ag[2]
|
||||
x := 100.0 * rate.Number / frequency.Number
|
||||
term1 := dcnl*x + redemption.Number
|
||||
if name == "ODDLPRICE" {
|
||||
term2 := dscnl*prOrYld.Number/frequency.Number + 1
|
||||
term3 := anl * x
|
||||
return newNumberFormulaArg(term1/term2 - term3)
|
||||
}
|
||||
term2 := anl*x + prOrYld.Number
|
||||
term3 := frequency.Number / dscnl
|
||||
return newNumberFormulaArg((term1 - term2) / term2 * term3)
|
||||
}
|
||||
|
||||
// ODDLPRICE function calculates the price per $100 face value of a security
|
||||
// with an odd (short or long) last period. The syntax of the function is:
|
||||
//
|
||||
// ODDLPRICE(settlement,maturity,last_interest,rate,yld,redemption,frequency,[basis])
|
||||
func (fn *formulaFuncs) ODDLPRICE(argsList *list.List) formulaArg {
|
||||
return fn.oddl("ODDLPRICE", argsList)
|
||||
}
|
||||
|
||||
// ODDLYIELD function calculates the yield of a security with an odd (short or
|
||||
// long) last period. The syntax of the function is:
|
||||
//
|
||||
// ODDLYIELD(settlement,maturity,last_interest,rate,pr,redemption,frequency,[basis])
|
||||
func (fn *formulaFuncs) ODDLYIELD(argsList *list.List) formulaArg {
|
||||
return fn.oddl("ODDLYIELD", argsList)
|
||||
}
|
||||
|
||||
// PDURATION function calculates the number of periods required for an
|
||||
// investment to reach a specified future value. The syntax of the function
|
||||
// is:
|
||||
|
@ -17743,7 +18201,7 @@ func (fn *formulaFuncs) database(name string, argsList *list.List) formulaArg {
|
|||
|
||||
// DAVERAGE function calculates the average (statistical mean) of values in a
|
||||
// field (column) in a database for selected records, that satisfy
|
||||
// user-specified criteria. The syntax of the Excel Daverage function is:
|
||||
// user-specified criteria. The syntax of the function is:
|
||||
//
|
||||
// DAVERAGE(database,field,criteria)
|
||||
func (fn *formulaFuncs) DAVERAGE(argsList *list.List) formulaArg {
|
||||
|
|
171
calc_test.go
171
calc_test.go
|
@ -1060,6 +1060,13 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=FISHERINV(INT(0))": "0",
|
||||
"=FISHERINV(\"0\")": "0",
|
||||
"=FISHERINV(2.8)": "0.992631520201128",
|
||||
// FORECAST
|
||||
"=FORECAST(7,A1:A7,B1:B7)": "4",
|
||||
// FORECAST.LINEAR
|
||||
"=FORECAST.LINEAR(7,A1:A7,B1:B7)": "4",
|
||||
// FREQUENCY
|
||||
"=SUM(FREQUENCY(A2,B2))": "1",
|
||||
"=SUM(FREQUENCY(A1:A5,B1:B2))": "4",
|
||||
// GAMMA
|
||||
"=GAMMA(0.1)": "9.51350769866873",
|
||||
"=GAMMA(INT(1))": "1",
|
||||
|
@ -1109,6 +1116,8 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=HYPGEOMDIST(2,4,4,12)": "0.339393939393939",
|
||||
"=HYPGEOMDIST(3,4,4,12)": "0.0646464646464646",
|
||||
"=HYPGEOMDIST(4,4,4,12)": "0.00202020202020202",
|
||||
// INTERCEPT
|
||||
"=INTERCEPT(A1:A4,B1:B4)": "-3",
|
||||
// KURT
|
||||
"=KURT(F1:F9)": "-1.03350350255137",
|
||||
"=KURT(F1,F2:F9)": "-1.03350350255137",
|
||||
|
@ -1652,6 +1661,10 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=WEEKNUM(\"01/01/2017\",21)": "52",
|
||||
"=WEEKNUM(\"01/01/2021\",21)": "53",
|
||||
// Text Functions
|
||||
// ARRAYTOTEXT
|
||||
"=ARRAYTOTEXT(A1:D2)": "1, 4, , Month, 2, 5, , Jan",
|
||||
"=ARRAYTOTEXT(A1:D2,0)": "1, 4, , Month, 2, 5, , Jan",
|
||||
"=ARRAYTOTEXT(A1:D2,1)": "{1,4,,\"Month\";2,5,,\"Jan\"}",
|
||||
// CHAR
|
||||
"=CHAR(65)": "A",
|
||||
"=CHAR(97)": "a",
|
||||
|
@ -1820,6 +1833,13 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=VALUE(\"20%\")": "0.2",
|
||||
"=VALUE(\"12:00:00\")": "0.5",
|
||||
"=VALUE(\"01/02/2006 15:04:05\")": "38719.6278356481",
|
||||
// VALUETOTEXT
|
||||
"=VALUETOTEXT(A1)": "1",
|
||||
"=VALUETOTEXT(A1,0)": "1",
|
||||
"=VALUETOTEXT(A1,1)": "1",
|
||||
"=VALUETOTEXT(D1)": "Month",
|
||||
"=VALUETOTEXT(D1,0)": "Month",
|
||||
"=VALUETOTEXT(D1,1)": "\"Month\"",
|
||||
// Conditional Functions
|
||||
// IF
|
||||
"=IF(1=1)": "TRUE",
|
||||
|
@ -2050,13 +2070,23 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// NPV
|
||||
"=NPV(0.02,-5000,\"\",800)": "-4133.02575932334",
|
||||
// ODDFPRICE
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": "107.691830256629",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,1)": "106.766915010929",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,3)": "106.7819138147",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,4)": "106.771913772467",
|
||||
"=ODDFPRICE(\"11/11/2008\",\"03/01/2021\",\"10/15/2008\",\"03/01/2009\",7.85%,6.25%,100,2,1)": "113.597717474079",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"09/30/2017\",5.5%,3.5%,100,4,0)": "106.72930611878",
|
||||
"=ODDFPRICE(\"11/11/2008\",\"03/29/2021\", \"08/15/2008\", \"03/29/2009\", 0.0785, 0.0625, 100, 2, 1)": "113.61826640814",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": "107.691830256629",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,1)": "106.766915010929",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,3)": "106.7819138147",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,4,4)": "106.771913772467",
|
||||
"=ODDFPRICE(\"11/11/2008\",\"03/01/2021\",\"10/15/2008\",\"03/01/2009\",7.85%,6.25%,100,2,1)": "113.597717474079",
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"09/30/2017\",5.5%,3.5%,100,4,0)": "106.72930611878",
|
||||
"=ODDFPRICE(\"11/11/2008\",\"03/29/2021\",\"08/15/2008\",\"03/29/2009\",0.0785,0.0625,100,2,1)": "113.61826640814",
|
||||
// ODDFYIELD
|
||||
"=ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,1)": "0.0495998049937776",
|
||||
"=ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,2)": "0.0496289417392839",
|
||||
"=ODDFYIELD(\"05/01/2017\",\"06/30/2021\",\"03/15/2017\",\"06/30/2017\",5.5%,102,100,4,1)": "0.0464750282973541",
|
||||
// ODDLPRICE
|
||||
"=ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "5.0517841252892",
|
||||
"=ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "10.3667274303228",
|
||||
// ODDLYIELD
|
||||
"=ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,2)": "0.0451922356291692",
|
||||
"=ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"12/24/2007\",3.75%,99.875,100,4,1)": "0.0882287538349037",
|
||||
// PDURATION
|
||||
"=PDURATION(0.04,10000,15000)": "10.3380350715076",
|
||||
// PMT
|
||||
|
@ -2710,6 +2740,15 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=POISSON(0,\"\",FALSE)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=POISSON(0,0,\"\")": {"#VALUE!", "strconv.ParseBool: parsing \"\": invalid syntax"},
|
||||
"=POISSON(0,-1,TRUE)": {"#N/A", "#N/A"},
|
||||
// PROB
|
||||
"=PROB()": {"#VALUE!", "PROB requires at least 3 arguments"},
|
||||
"=PROB(A1:A2,B1:B2,1,1,1)": {"#VALUE!", "PROB requires at most 4 arguments"},
|
||||
"=PROB(A1:A2,B1:B2,\"\")": {"#VALUE!", "#VALUE!"},
|
||||
"=PROB(A1:A2,B1:B2,1,\"\")": {"#VALUE!", "#VALUE!"},
|
||||
"=PROB(A1,B1,1)": {"#NUM!", "#NUM!"},
|
||||
"=PROB(A1:A2,B1:B3,1)": {"#N/A", "#N/A"},
|
||||
"=PROB(A1:A2,B1:C2,1)": {"#N/A", "#N/A"},
|
||||
"=PROB(A1:A2,B1:B2,1)": {"#NUM!", "#NUM!"},
|
||||
// SUBTOTAL
|
||||
"=SUBTOTAL()": {"#VALUE!", "SUBTOTAL requires at least 2 arguments"},
|
||||
"=SUBTOTAL(\"\",A4:A5)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
|
@ -2967,6 +3006,20 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// FISHERINV
|
||||
"=FISHERINV()": {"#VALUE!", "FISHERINV requires 1 numeric argument"},
|
||||
"=FISHERINV(F1)": {"#VALUE!", "FISHERINV requires 1 numeric argument"},
|
||||
// FORECAST
|
||||
"=FORECAST()": {"#VALUE!", "FORECAST requires 3 arguments"},
|
||||
"=FORECAST(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=FORECAST(1,A1:A2,B1:B1)": {"#N/A", "#N/A"},
|
||||
"=FORECAST(1,A4,A4)": {"#DIV/0!", "#DIV/0!"},
|
||||
// FORECAST.LINEAR
|
||||
"=FORECAST.LINEAR()": {"#VALUE!", "FORECAST.LINEAR requires 3 arguments"},
|
||||
"=FORECAST.LINEAR(\"\",A1:A7,B1:B7)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=FORECAST.LINEAR(1,A1:A2,B1:B1)": {"#N/A", "#N/A"},
|
||||
"=FORECAST.LINEAR(1,A4,A4)": {"#DIV/0!", "#DIV/0!"},
|
||||
// FREQUENCY
|
||||
"=FREQUENCY()": {"#VALUE!", "FREQUENCY requires 2 arguments"},
|
||||
"=FREQUENCY(NA(),A1:A3)": {"#N/A", "#N/A"},
|
||||
"=FREQUENCY(A1:A3,NA())": {"#N/A", "#N/A"},
|
||||
// GAMMA
|
||||
"=GAMMA()": {"#VALUE!", "GAMMA requires 1 numeric argument"},
|
||||
"=GAMMA(F1)": {"#VALUE!", "GAMMA requires 1 numeric argument"},
|
||||
|
@ -3059,6 +3112,10 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=HYPGEOMDIST(1,4,4,2)": {"#NUM!", "#NUM!"},
|
||||
"=HYPGEOMDIST(1,4,0,12)": {"#NUM!", "#NUM!"},
|
||||
"=HYPGEOMDIST(1,4,4,0)": {"#NUM!", "#NUM!"},
|
||||
// INTERCEPT
|
||||
"=INTERCEPT()": {"#VALUE!", "INTERCEPT requires 2 arguments"},
|
||||
"=INTERCEPT(A1:A2,B1:B1)": {"#N/A", "#N/A"},
|
||||
"=INTERCEPT(A4,A4)": {"#DIV/0!", "#DIV/0!"},
|
||||
// KURT
|
||||
"=KURT()": {"#VALUE!", "KURT requires at least 1 argument"},
|
||||
"=KURT(F1,INT(1))": {"#DIV/0!", "#DIV/0!"},
|
||||
|
@ -3662,6 +3719,11 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=WEEKNUM(0,0)": {"#NUM!", "#NUM!"},
|
||||
"=WEEKNUM(-1,1)": {"#NUM!", "#NUM!"},
|
||||
// Text Functions
|
||||
// ARRAYTOTEXT
|
||||
"=ARRAYTOTEXT()": {"#VALUE!", "ARRAYTOTEXT requires at least 1 argument"},
|
||||
"=ARRAYTOTEXT(A1,0,0)": {"#VALUE!", "ARRAYTOTEXT allows at most 2 arguments"},
|
||||
"=ARRAYTOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ARRAYTOTEXT(A1,2)": {"#VALUE!", "#VALUE!"},
|
||||
// CHAR
|
||||
"=CHAR()": {"#VALUE!", "CHAR requires 1 argument"},
|
||||
"=CHAR(-1)": {"#VALUE!", "#VALUE!"},
|
||||
|
@ -3779,6 +3841,11 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// VALUE
|
||||
"=VALUE()": {"#VALUE!", "VALUE requires 1 argument"},
|
||||
"=VALUE(\"\")": {"#VALUE!", "#VALUE!"},
|
||||
// VALUETOTEXT
|
||||
"=VALUETOTEXT()": {"#VALUE!", "VALUETOTEXT requires at least 1 argument"},
|
||||
"=VALUETOTEXT(A1,0,0)": {"#VALUE!", "VALUETOTEXT allows at most 2 arguments"},
|
||||
"=VALUETOTEXT(A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=VALUETOTEXT(A1,2)": {"#VALUE!", "#VALUE!"},
|
||||
// UPPER
|
||||
"=UPPER()": {"#VALUE!", "UPPER requires 1 argument"},
|
||||
"=UPPER(1,2)": {"#VALUE!", "UPPER requires 1 argument"},
|
||||
|
@ -4187,6 +4254,60 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"},
|
||||
"=ODDFPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
|
||||
// ODDFYIELD
|
||||
"=ODDFYIELD()": {"#VALUE!", "ODDFYIELD requires 8 or 9 arguments"},
|
||||
"=ODDFYIELD(\"\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"02/01/2017\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires settlement > issue"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"02/01/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires first_coupon > settlement"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"02/01/2017\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires maturity > first_coupon"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",-1,3.5%,100,2)": {"#NUM!", "ODDFYIELD requires rate >= 0"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,0,100,2)": {"#NUM!", "ODDFYIELD requires pr > 0"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,0,2)": {"#NUM!", "ODDFYIELD requires redemption > 0"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/30/2017\",5.5%,3.5%,100,4)": {"#NUM!", "#NUM!"},
|
||||
"=ODDFYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"03/31/2017\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
|
||||
// ODDLPRICE
|
||||
"=ODDLPRICE()": {"#VALUE!", "ODDLPRICE requires 7 or 8 arguments"},
|
||||
"=ODDLPRICE(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLPRICE(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires settlement > last_interest"},
|
||||
"=ODDLPRICE(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLPRICE requires maturity > settlement"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLPRICE requires rate >= 0"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,-1,100,2)": {"#NUM!", "ODDLPRICE requires yld >= 0"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLPRICE requires redemption > 0"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
|
||||
"=ODDLPRICE(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
|
||||
// ODDLYIELD
|
||||
"=ODDLYIELD()": {"#VALUE!", "ODDLYIELD requires 7 or 8 arguments"},
|
||||
"=ODDLYIELD(\"\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"\",\"12/01/2016\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"\",5.5%,3.5%,100,2)": {"#VALUE!", "#VALUE!"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",\"\",3.5%,100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,\"\",100,2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,\"\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
"=ODDLYIELD(\"04/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires settlement > last_interest"},
|
||||
"=ODDLYIELD(\"06/20/2008\",\"06/15/2008\",\"04/30/2008\",3.75%,99.875,100,2)": {"#NUM!", "ODDLYIELD requires maturity > settlement"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",-1,3.5%,100,2)": {"#NUM!", "ODDLYIELD requires rate >= 0"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,0,100,2)": {"#NUM!", "ODDLYIELD requires pr > 0"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,0,2)": {"#NUM!", "ODDLYIELD requires redemption > 0"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,\"\")": {"#NUM!", "#NUM!"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,3)": {"#NUM!", "#NUM!"},
|
||||
"=ODDLYIELD(\"02/01/2017\",\"03/31/2021\",\"12/01/2016\",5.5%,3.5%,100,2,5)": {"#NUM!", "invalid basis"},
|
||||
// PDURATION
|
||||
"=PDURATION()": {"#VALUE!", "PDURATION requires 3 arguments"},
|
||||
"=PDURATION(\"\",0,0)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
|
||||
|
@ -5500,6 +5621,42 @@ func TestCalcPEARSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCalcPROB(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{"x", "probability"},
|
||||
{0, 0.1},
|
||||
{1, 0.15},
|
||||
{2, 0.17},
|
||||
{3, 0.22},
|
||||
{4, 0.21},
|
||||
{5, 0.09},
|
||||
{6, 0.05},
|
||||
{7, 0.01},
|
||||
}
|
||||
f := prepareCalcData(cellData)
|
||||
formulaList := map[string]string{
|
||||
"=PROB(A2:A9,B2:B9,3)": "0.22",
|
||||
"=PROB(A2:A9,B2:B9,3,5)": "0.52",
|
||||
"=PROB(A2:A9,B2:B9,8,10)": "0",
|
||||
}
|
||||
for formula, expected := range formulaList {
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
|
||||
result, err := f.CalcCellValue("Sheet1", "C1")
|
||||
assert.NoError(t, err, formula)
|
||||
assert.Equal(t, expected, result, formula)
|
||||
}
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "=NA()"))
|
||||
calcError := map[string][]string{
|
||||
"=PROB(A2:A9,B2:B9,3)": {"#NUM!", "#NUM!"},
|
||||
}
|
||||
for formula, expected := range calcError {
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
|
||||
result, err := f.CalcCellValue("Sheet1", "C1")
|
||||
assert.Equal(t, expected[0], result, formula)
|
||||
assert.EqualError(t, err, expected[1], formula)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcRSQ(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{"known_y's", "known_x's"},
|
||||
|
|
Loading…
Reference in New Issue