mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00

* PMT, PPMT * PRICEDISC, fixed YEARFRAC * PV and handling empty arguments * RATE * RECEIVED * RRI * ODDLPRICE, ODDLYIELD * PRICE, PRICEMAT * SLN * SYD * TBILLEQ, TBILLPRICE, TBILLYIELD * VDB
1666 lines
43 KiB
Go
1666 lines
43 KiB
Go
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by the terms of the Affero GNU General
|
|
// Public License version 3.0 as published by the Free Software Foundation and
|
|
// appearing in the file LICENSE included in the packaging of this file. A
|
|
// commercial license can be purchased on https://unidoc.io.
|
|
|
|
package formula
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var rnd *rand.Rand
|
|
|
|
func init() {
|
|
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
RegisterFunction("ABS", makeMathWrapper("ASIN", math.Abs))
|
|
RegisterFunction("ACOS", makeMathWrapper("ASIN", math.Acos))
|
|
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
|
RegisterFunction("_xlfn.ACOT", makeMathWrapper("ACOT", func(v float64) float64 { return math.Pi/2 - math.Atan(v) }))
|
|
RegisterFunction("_xlfn.ACOTH", makeMathWrapper("ACOTH", func(v float64) float64 { return math.Atanh(1 / v) }))
|
|
RegisterFunction("_xlfn.ARABIC", Arabic)
|
|
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
|
RegisterFunction("ASINH", makeMathWrapper("ASINH", math.Asinh))
|
|
RegisterFunction("ATAN", makeMathWrapper("ATAN", math.Atan))
|
|
RegisterFunction("ATANH", makeMathWrapper("ATANH", math.Atanh))
|
|
RegisterFunction("ATAN2", Atan2)
|
|
RegisterFunction("_xlfn.BASE", Base)
|
|
RegisterFunction("CEILING", Ceiling)
|
|
RegisterFunction("_xlfn.CEILING.MATH", CeilingMath)
|
|
RegisterFunction("_xlfn.CEILING.PRECISE", CeilingPrecise)
|
|
RegisterFunction("COMBIN", Combin)
|
|
RegisterFunction("_xlfn.COMBINA", Combina)
|
|
RegisterFunction("COS", makeMathWrapper("COS", math.Cos))
|
|
RegisterFunction("COSH", makeMathWrapper("COSH", math.Cosh))
|
|
RegisterFunction("_xlfn.COT", makeMathWrapperInv("COT", math.Tan))
|
|
RegisterFunction("_xlfn.COTH", makeMathWrapperInv("COTH", math.Tanh))
|
|
RegisterFunction("_xlfn.CSC", makeMathWrapperInv("CSC", math.Sin))
|
|
RegisterFunction("_xlfn.CSCH", makeMathWrapperInv("CSC", math.Sinh))
|
|
RegisterFunction("_xlfn.DECIMAL", Decimal)
|
|
RegisterFunction("DEGREES", Degrees)
|
|
RegisterFunction("EVEN", Even)
|
|
RegisterFunction("EXP", makeMathWrapper("EXP", math.Exp))
|
|
RegisterFunction("FACT", Fact)
|
|
RegisterFunction("FACTDOUBLE", FactDouble)
|
|
RegisterFunction("FLOOR", Floor)
|
|
RegisterFunction("_xlfn.FLOOR.MATH", FloorMath)
|
|
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
|
RegisterFunction("GCD", GCD)
|
|
RegisterFunction("INT", Int)
|
|
RegisterFunction("ISO.CEILING", CeilingPrecise)
|
|
RegisterFunction("LCM", LCM)
|
|
RegisterFunction("LN", makeMathWrapper("LN", math.Log))
|
|
RegisterFunction("LOG", Log)
|
|
RegisterFunction("LOG10", makeMathWrapper("LOG10", math.Log10))
|
|
RegisterFunction("MDETERM", MDeterm)
|
|
RegisterFunction("MOD", Mod)
|
|
RegisterFunction("MROUND", Mround)
|
|
RegisterFunction("MULTINOMIAL", Multinomial)
|
|
RegisterFunction("_xlfn.MUNIT", Munit)
|
|
RegisterFunction("ODD", Odd)
|
|
RegisterFunction("PI", Pi)
|
|
RegisterFunction("POWER", Power)
|
|
RegisterFunction("PRODUCT", Product)
|
|
RegisterFunction("QUOTIENT", Quotient)
|
|
RegisterFunction("RADIANS", Radians)
|
|
RegisterFunction("RAND", Rand)
|
|
RegisterFunction("RANDBETWEEN", RandBetween)
|
|
RegisterFunction("ROMAN", Roman)
|
|
RegisterFunction("ROUND", Round)
|
|
RegisterFunction("ROUNDDOWN", RoundDown)
|
|
RegisterFunction("ROUNDUP", RoundUp)
|
|
RegisterFunction("_xlfn.SEC", makeMathWrapperInv("SEC", math.Cos))
|
|
RegisterFunction("_xlfn.SECH", makeMathWrapperInv("SECH", math.Cosh))
|
|
RegisterFunction("SERIESSUM", SeriesSum)
|
|
RegisterFunction("SIGN", Sign)
|
|
RegisterFunction("SIN", makeMathWrapper("SIN", math.Sin))
|
|
RegisterFunction("SINH", makeMathWrapper("SINH", math.Sinh))
|
|
RegisterFunction("SQRT", makeMathWrapper("SQRT", math.Sqrt))
|
|
RegisterFunction("SQRTPI", makeMathWrapper("SQRTPI", func(v float64) float64 { return math.Sqrt(v * math.Pi) }))
|
|
RegisterFunction("SUM", Sum)
|
|
RegisterFunction("SUMIF", SumIf)
|
|
RegisterFunction("SUMIFS", SumIfs)
|
|
RegisterFunction("SUMPRODUCT", SumProduct)
|
|
RegisterFunction("SUMSQ", SumSquares)
|
|
RegisterFunction("TAN", makeMathWrapper("TAN", math.Tan))
|
|
RegisterFunction("TANH", makeMathWrapper("TANH", math.Tanh))
|
|
RegisterFunction("TRUNC", Trunc)
|
|
}
|
|
|
|
// makeMathWrapper is used to wrap single argument math functions from the Go
|
|
// standard library and present them as a spreadsheet function.
|
|
func makeMathWrapper(name string, fn func(x float64) float64) Function {
|
|
return func(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult(name + " requires one argument")
|
|
}
|
|
|
|
arg := args[0].AsNumber()
|
|
switch arg.Type {
|
|
case ResultTypeNumber:
|
|
v := fn(arg.ValueNumber)
|
|
if math.IsNaN(v) {
|
|
return MakeErrorResult(name + " returned NaN")
|
|
}
|
|
if math.IsInf(v, 0) {
|
|
return MakeErrorResult(name + " returned infinity")
|
|
}
|
|
return MakeNumberResult(v)
|
|
case ResultTypeList, ResultTypeString:
|
|
return MakeErrorResult(name + " requires a numeric argument")
|
|
case ResultTypeError:
|
|
return arg
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled %s() argument type %s", name, arg.Type))
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeMathWrapperInv(name string, fn func(x float64) float64) Function {
|
|
return func(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult(name + " requires one argument")
|
|
}
|
|
|
|
arg := args[0].AsNumber()
|
|
switch arg.Type {
|
|
case ResultTypeNumber:
|
|
v := fn(arg.ValueNumber)
|
|
if math.IsNaN(v) {
|
|
return MakeErrorResult(name + " returned NaN")
|
|
}
|
|
if math.IsInf(v, 0) {
|
|
return MakeErrorResult(name + " returned infinity")
|
|
}
|
|
if v == 0 {
|
|
return MakeErrorResultType(ErrorTypeDivideByZero, name+" divide by zero")
|
|
}
|
|
return MakeNumberResult(1 / v)
|
|
case ResultTypeList, ResultTypeString:
|
|
return MakeErrorResult(name + " requires a numeric argument")
|
|
case ResultTypeError:
|
|
return arg
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled %s() argument type %s", name, arg.Type))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Atan2 implements the Excel ATAN2 function. It accepts two numeric arguments,
|
|
// and the arguments are (x,y), reversed from normal to match Excel's behaviour.
|
|
func Atan2(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("ATAN2 requires two arguments")
|
|
}
|
|
|
|
arg1 := args[0].AsNumber()
|
|
arg2 := args[1].AsNumber()
|
|
if arg1.Type == ResultTypeNumber && arg2.Type == ResultTypeNumber {
|
|
// args are swapped here
|
|
v := math.Atan2(arg2.ValueNumber, arg1.ValueNumber)
|
|
if v != v {
|
|
return MakeErrorResult("ATAN2 returned NaN")
|
|
}
|
|
return MakeNumberResult(v)
|
|
}
|
|
|
|
for _, t := range []ResultType{arg1.Type, arg2.Type} {
|
|
switch t {
|
|
case ResultTypeList, ResultTypeString:
|
|
return MakeErrorResult("ATAN2 requires a numeric argument")
|
|
case ResultTypeError:
|
|
return arg1
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled ATAN2() argument type %s", t))
|
|
}
|
|
}
|
|
return MakeErrorResult("unhandled error for ATAN2()")
|
|
}
|
|
|
|
// Arabic implements the Excel ARABIC function which parses roman numerals. It
|
|
// accepts one numeric argument.
|
|
func Arabic(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("ARABIC requires one argument")
|
|
}
|
|
arg := args[0]
|
|
switch arg.Type {
|
|
case ResultTypeNumber, ResultTypeList, ResultTypeEmpty:
|
|
return MakeErrorResult("ARABIC requires a string argument argument")
|
|
case ResultTypeString:
|
|
res := 0.0
|
|
last := 0.0
|
|
for _, c := range arg.ValueString {
|
|
digit := 0.0
|
|
switch c {
|
|
case 'I':
|
|
digit = 1
|
|
case 'V':
|
|
digit = 5
|
|
case 'X':
|
|
digit = 10
|
|
case 'L':
|
|
digit = 50
|
|
case 'C':
|
|
digit = 100
|
|
case 'D':
|
|
digit = 500
|
|
case 'M':
|
|
digit = 1000
|
|
}
|
|
res += digit
|
|
switch {
|
|
// repeated digits
|
|
case last == digit &&
|
|
(last == 5 || last == 50 || last == 500):
|
|
return MakeErrorResult("invalid ARABIC format")
|
|
// simpler form
|
|
case 2*last == digit:
|
|
return MakeErrorResult("invalid ARABIC format")
|
|
}
|
|
if last < digit {
|
|
res -= 2 * last
|
|
}
|
|
last = digit
|
|
}
|
|
|
|
return MakeNumberResult(res)
|
|
case ResultTypeError:
|
|
return arg
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled ACOSH() argument type %s", arg.Type))
|
|
}
|
|
}
|
|
|
|
// CeilingMath implements _xlfn.CEILING.MATH which rounds numbers to the nearest
|
|
// multiple of the second argument, toward or away from zero as specified by the
|
|
// third argument.
|
|
func CeilingMath(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("CEILING.MATH() requires at least one argument")
|
|
}
|
|
if len(args) > 3 {
|
|
return MakeErrorResult("CEILING.MATH() allows at most three arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to CEILING.MATH() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
if number.ValueNumber < 0 {
|
|
significance = -1
|
|
}
|
|
if len(args) > 1 {
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to CEILING.MATH() must be a number")
|
|
}
|
|
significance = sigArg.ValueNumber
|
|
}
|
|
|
|
// round direction
|
|
direction := float64(1)
|
|
if len(args) > 2 {
|
|
dirArg := args[2].AsNumber()
|
|
if dirArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("third argument to CEILING.MATH() must be a number")
|
|
}
|
|
direction = dirArg.ValueNumber
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
return MakeNumberResult(math.Ceil(number.ValueNumber))
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res != 0 {
|
|
if number.ValueNumber > 0 {
|
|
v++
|
|
} else if direction < 0 {
|
|
v--
|
|
}
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// Ceiling is an implementation of the CEILING function which
|
|
// returns the ceiling of a number.
|
|
func Ceiling(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("CEILING() requires at least one argument")
|
|
}
|
|
if len(args) > 2 {
|
|
return MakeErrorResult("CEILING() allows at most two arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to CEILING() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
if number.ValueNumber < 0 {
|
|
significance = -1
|
|
}
|
|
if len(args) > 1 {
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to CEILING() must be a number")
|
|
}
|
|
significance = sigArg.ValueNumber
|
|
}
|
|
|
|
if significance < 0 && number.ValueNumber > 0 {
|
|
return MakeErrorResultType(ErrorTypeNum, "negative sig to CEILING() invalid")
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
return MakeNumberResult(math.Ceil(number.ValueNumber))
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res > 0 {
|
|
v++
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// CeilingPrecise is an implementation of the CEILING.PRECISE function which
|
|
// returns the ceiling of a number.
|
|
func CeilingPrecise(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("CEILING.PRECISE() requires at least one argument")
|
|
}
|
|
if len(args) > 2 {
|
|
return MakeErrorResult("CEILING.PRECISE() allows at most two arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to CEILING.PRECISE() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
if number.ValueNumber < 0 {
|
|
significance = -1
|
|
}
|
|
if len(args) > 1 {
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to CEILING.PRECISE() must be a number")
|
|
}
|
|
// don't care about sign of significance
|
|
significance = math.Abs(sigArg.ValueNumber)
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
return MakeNumberResult(math.Ceil(number.ValueNumber))
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res != 0 {
|
|
if number.ValueNumber > 0 {
|
|
v++
|
|
}
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// Base is an implementation of the Excel BASE function that returns a string
|
|
// form of an integer in a specified base and of a minimum length with padded
|
|
// zeros.
|
|
func Base(args []Result) Result {
|
|
if len(args) < 2 {
|
|
return MakeErrorResult("BASE() requires at least two arguments")
|
|
}
|
|
if len(args) > 3 {
|
|
return MakeErrorResult("BASE() allows at most three arguments")
|
|
}
|
|
// number to convert
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to BASE() must be a number")
|
|
}
|
|
|
|
radixArg := args[1].AsNumber()
|
|
if radixArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to BASE() must be a number")
|
|
}
|
|
radix := int(radixArg.ValueNumber)
|
|
if radix < 0 || radix > 36 {
|
|
return MakeErrorResult("radix must be in the range [0,36]")
|
|
}
|
|
|
|
// min length of result
|
|
minLength := 0
|
|
if len(args) > 2 {
|
|
lenArg := args[2].AsNumber()
|
|
if lenArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("third argument to BASE() must be a number")
|
|
}
|
|
minLength = int(lenArg.ValueNumber)
|
|
}
|
|
|
|
s := strconv.FormatInt(int64(number.ValueNumber), radix)
|
|
if len(s) < minLength {
|
|
s = strings.Repeat("0", minLength-len(s)) + s
|
|
}
|
|
return MakeStringResult(s)
|
|
}
|
|
|
|
// Combin is an implementation of the Excel COMBINA function whic returns the
|
|
// number of combinations.
|
|
func Combin(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("COMBIN() requires two argument")
|
|
}
|
|
nArg := args[0].AsNumber()
|
|
kArg := args[1].AsNumber()
|
|
if nArg.Type != ResultTypeNumber || kArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("COMBIN() requires numeric arguments")
|
|
}
|
|
n := math.Trunc(nArg.ValueNumber)
|
|
k := math.Trunc(kArg.ValueNumber)
|
|
if k > n {
|
|
return MakeErrorResult("COMBIN() requires k <= n")
|
|
}
|
|
if k == n || k == 0 {
|
|
return MakeNumberResult(1)
|
|
}
|
|
|
|
res := float64(1)
|
|
for i := float64(1); i <= k; i++ {
|
|
res *= (n + 1 - i) / i
|
|
}
|
|
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
// Combina is an implementation of the Excel COMBINA function whic returns the
|
|
// number of combinations with repetitions.
|
|
func Combina(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("COMBINA() requires two argument")
|
|
}
|
|
nArg := args[0].AsNumber()
|
|
kArg := args[1].AsNumber()
|
|
if nArg.Type != ResultTypeNumber || kArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("COMBINA() requires numeric arguments")
|
|
}
|
|
n := math.Trunc(nArg.ValueNumber)
|
|
k := math.Trunc(kArg.ValueNumber)
|
|
if n < k {
|
|
return MakeErrorResult("COMBINA() requires n > k")
|
|
}
|
|
if n == 0 {
|
|
return MakeNumberResult(0)
|
|
}
|
|
args[0] = MakeNumberResult(n + k - 1)
|
|
args[1] = MakeNumberResult(n - 1)
|
|
return Combin(args)
|
|
}
|
|
|
|
// Decimal is an implementation of the Excel function DECIMAL() that parses a string
|
|
// in a given base and returns the numeric result.
|
|
func Decimal(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("DECIMAL() requires two arguments")
|
|
}
|
|
sArg := args[0].AsString()
|
|
if sArg.Type != ResultTypeString {
|
|
return MakeErrorResult("DECIMAL() requires string first argument")
|
|
}
|
|
baseArg := args[1].AsNumber()
|
|
if baseArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("DECIMAL() requires number second argument")
|
|
}
|
|
|
|
sv := sArg.ValueString
|
|
if len(sv) > 2 && (strings.HasPrefix(sv, "0x") || strings.HasPrefix(sv, "0X")) {
|
|
sv = sv[2:]
|
|
}
|
|
i, err := strconv.ParseInt(sv, int(baseArg.ValueNumber), 64)
|
|
if err != nil {
|
|
return MakeErrorResult("DECIMAL() error in conversion")
|
|
}
|
|
return MakeNumberResult(float64(i))
|
|
}
|
|
|
|
// Degrees is an implementation of the Excel function DEGREES() that converts
|
|
// radians to degrees.
|
|
func Degrees(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("DEGREES() requires one argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("DEGREES() requires number argument")
|
|
}
|
|
|
|
return MakeNumberResult(180.0 / math.Pi * vArg.ValueNumber)
|
|
}
|
|
|
|
// Even is an implementation of the Excel EVEN() that rounds a number to the
|
|
// nearest even integer.
|
|
func Even(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("EVEN() requires one argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("EVEN() requires number argument")
|
|
}
|
|
|
|
sign := math.Signbit(vArg.ValueNumber)
|
|
m, r := math.Modf(vArg.ValueNumber / 2)
|
|
v := m * 2
|
|
if r != 0 {
|
|
if !sign {
|
|
v += 2
|
|
} else {
|
|
v -= 2
|
|
}
|
|
}
|
|
return MakeNumberResult(v)
|
|
}
|
|
|
|
func fact(f float64) float64 {
|
|
res := float64(1)
|
|
for i := float64(2); i <= f; i++ {
|
|
res *= i
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Fact is an implementation of the excel FACT function which returns the
|
|
// factorial of a positive numeric input.
|
|
func Fact(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("FACT() accepts a single numeric argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("FACT() accepts a single numeric argument")
|
|
}
|
|
if vArg.ValueNumber < 0 {
|
|
return MakeErrorResult("FACT() accepts only positive arguments")
|
|
}
|
|
return MakeNumberResult(fact(vArg.ValueNumber))
|
|
}
|
|
|
|
// FactDouble is an implementation of the excel FACTDOUBLE function which
|
|
// returns the double factorial of a positive numeric input.
|
|
func FactDouble(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("FACTDOUBLE() accepts a single numeric argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("FACTDOUBLE() accepts a single numeric argument")
|
|
}
|
|
if vArg.ValueNumber < 0 {
|
|
return MakeErrorResult("FACTDOUBLE() accepts only positive arguments")
|
|
}
|
|
|
|
res := float64(1)
|
|
v := math.Trunc(vArg.ValueNumber)
|
|
for i := v; i > 1; i -= 2 {
|
|
res *= i
|
|
}
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
// FloorMath implements _xlfn.FLOOR.MATH which rounds numbers down to the
|
|
// nearest multiple of the second argument, toward or away from zero as
|
|
// specified by the third argument.
|
|
func FloorMath(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("FLOOR.MATH() requires at least one argument")
|
|
}
|
|
if len(args) > 3 {
|
|
return MakeErrorResult("FLOOR.MATH() allows at most three arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to FLOOR.MATH() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
if number.ValueNumber < 0 {
|
|
significance = -1
|
|
}
|
|
if len(args) > 1 {
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to FLOOR.MATH() must be a number")
|
|
}
|
|
significance = sigArg.ValueNumber
|
|
}
|
|
|
|
// round direction
|
|
direction := float64(1)
|
|
if len(args) > 2 {
|
|
dirArg := args[2].AsNumber()
|
|
if dirArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("third argument to FLOOR.MATH() must be a number")
|
|
}
|
|
direction = dirArg.ValueNumber
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
return MakeNumberResult(math.Floor(number.ValueNumber))
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res != 0 && number.ValueNumber < 0 && direction > 0 {
|
|
v++
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// Floor is an implementation of the FlOOR function.
|
|
func Floor(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("FLOOR() requires two arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to FLOOR() must be a number")
|
|
}
|
|
|
|
// significance
|
|
var significance float64
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to FLOOR() must be a number")
|
|
}
|
|
|
|
significance = sigArg.ValueNumber
|
|
if significance < 0 && number.ValueNumber >= 0 {
|
|
return MakeErrorResultType(ErrorTypeNum, "invalid arguments to FLOOR")
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res != 0 {
|
|
if number.ValueNumber < 0 && res < 0 {
|
|
v--
|
|
}
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// FloorPrecise is an implementation of the FlOOR.PRECISE function.
|
|
func FloorPrecise(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("FLOOR.PRECISE() requires at least one argument")
|
|
}
|
|
if len(args) > 2 {
|
|
return MakeErrorResult("FLOOR.PRECISE() allows at most two arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to FLOOR.PRECISE() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
if number.ValueNumber < 0 {
|
|
significance = -1
|
|
}
|
|
if len(args) > 1 {
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to FLOOR.PRECISE() must be a number")
|
|
}
|
|
// don't care about sign of significance
|
|
significance = math.Abs(sigArg.ValueNumber)
|
|
}
|
|
|
|
if len(args) == 1 {
|
|
return MakeNumberResult(math.Floor(number.ValueNumber))
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if res != 0 {
|
|
if number.ValueNumber < 0 {
|
|
v--
|
|
}
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
func gcd(a, b float64) float64 {
|
|
a = math.Trunc(a)
|
|
b = math.Trunc(b)
|
|
if a == 0 {
|
|
return b
|
|
}
|
|
if b == 0 {
|
|
return a
|
|
}
|
|
for a != b {
|
|
if a > b {
|
|
a = a - b
|
|
} else {
|
|
b = b - a
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// GCD implements the Excel GCD() function which returns the greatest common
|
|
// divisor of a range of numbers.
|
|
func GCD(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("GCD() requires at least one argument")
|
|
}
|
|
|
|
numbers := []float64{}
|
|
for _, arg := range args {
|
|
switch arg.Type {
|
|
case ResultTypeString:
|
|
na := arg.AsNumber()
|
|
if na.Type != ResultTypeNumber {
|
|
return MakeErrorResult("GCD() only accepts numeric arguments")
|
|
}
|
|
numbers = append(numbers, na.ValueNumber)
|
|
case ResultTypeList, ResultTypeArray:
|
|
res := GCD(arg.ListValues())
|
|
if res.Type != ResultTypeNumber {
|
|
return res
|
|
}
|
|
numbers = append(numbers, res.ValueNumber)
|
|
case ResultTypeNumber:
|
|
numbers = append(numbers, arg.ValueNumber)
|
|
case ResultTypeError:
|
|
return arg
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("GCD() unsupported argument type %s", arg.Type))
|
|
}
|
|
}
|
|
if numbers[0] < 0 {
|
|
return MakeErrorResult("GCD() only accepts positive arguments")
|
|
}
|
|
|
|
if len(numbers) == 1 {
|
|
return MakeNumberResult(numbers[0])
|
|
}
|
|
res := numbers[0]
|
|
for i := 1; i < len(numbers); i++ {
|
|
if numbers[i] < 0 {
|
|
return MakeErrorResult("GCD() only accepts positive arguments")
|
|
}
|
|
res = gcd(res, numbers[i])
|
|
}
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
func lcm(a, b float64) float64 {
|
|
a = math.Trunc(a)
|
|
b = math.Trunc(b)
|
|
if a == 0 && b == 0 {
|
|
return 0
|
|
}
|
|
return a * b / gcd(a, b)
|
|
}
|
|
|
|
// LCM implements the Excel LCM() function which returns the least common
|
|
// multiple of a range of numbers.
|
|
func LCM(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("LCM() requires at least one argument")
|
|
}
|
|
|
|
numbers := []float64{}
|
|
for _, arg := range args {
|
|
switch arg.Type {
|
|
case ResultTypeString:
|
|
na := arg.AsNumber()
|
|
if na.Type != ResultTypeNumber {
|
|
return MakeErrorResult("LCM() only accepts numeric arguments")
|
|
}
|
|
numbers = append(numbers, na.ValueNumber)
|
|
case ResultTypeList:
|
|
res := LCM(arg.ValueList)
|
|
if res.Type != ResultTypeNumber {
|
|
return res
|
|
}
|
|
numbers = append(numbers, res.ValueNumber)
|
|
case ResultTypeNumber:
|
|
numbers = append(numbers, arg.ValueNumber)
|
|
case ResultTypeError:
|
|
return arg
|
|
}
|
|
}
|
|
if numbers[0] < 0 {
|
|
return MakeErrorResult("LCM() only accepts positive arguments")
|
|
}
|
|
|
|
if len(numbers) == 1 {
|
|
return MakeNumberResult(numbers[0])
|
|
}
|
|
res := numbers[0]
|
|
for i := 1; i < len(numbers); i++ {
|
|
if numbers[i] < 0 {
|
|
return MakeErrorResult("LCM() only accepts positive arguments")
|
|
}
|
|
res = lcm(res, numbers[i])
|
|
}
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
// Int is an implementation of the Excel INT() function that rounds a number
|
|
// down to an integer.
|
|
func Int(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("INT() requires a single numeric argument")
|
|
}
|
|
nArg := args[0].AsNumber()
|
|
if nArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("INT() requires a single numeric argument")
|
|
}
|
|
trunc, rem := math.Modf(nArg.ValueNumber)
|
|
if rem < 0 {
|
|
trunc--
|
|
}
|
|
return MakeNumberResult(trunc)
|
|
}
|
|
|
|
// Log implements the Excel LOG function which returns the log of a number. By
|
|
// default the result is base 10, however the second argument to the function
|
|
// can specify a different base.
|
|
func Log(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("LOG() requires at least one numeric argument")
|
|
}
|
|
if len(args) > 2 {
|
|
return MakeErrorResult("LOG() accepts a maximum of two arguments")
|
|
}
|
|
nArg := args[0].AsNumber()
|
|
if nArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("LOG() requires at least one numeric argument")
|
|
}
|
|
base := 10.0
|
|
if len(args) > 1 {
|
|
bArg := args[1].AsNumber()
|
|
if bArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("LOG() requires second argument to be numeric")
|
|
}
|
|
base = args[1].ValueNumber
|
|
}
|
|
if nArg.ValueNumber == 0 {
|
|
return MakeErrorResult("LOG() requires first argument to be non-zero")
|
|
}
|
|
if base == 0 {
|
|
return MakeErrorResult("LOG() requires second argument to be non-zero")
|
|
}
|
|
|
|
return MakeNumberResult(math.Log(nArg.ValueNumber) / math.Log(base))
|
|
|
|
}
|
|
|
|
func minor(sqMtx [][]Result, idx int) [][]Result {
|
|
ret := [][]Result{}
|
|
for i := range sqMtx {
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
row := []Result{}
|
|
for j := range sqMtx {
|
|
if j == idx {
|
|
continue
|
|
}
|
|
row = append(row, sqMtx[i][j])
|
|
}
|
|
ret = append(ret, row)
|
|
}
|
|
return ret
|
|
}
|
|
func det(sqMtx [][]Result) float64 {
|
|
// two by two
|
|
if len(sqMtx) == 2 {
|
|
m00 := sqMtx[0][0].AsNumber()
|
|
m01 := sqMtx[0][1].AsNumber()
|
|
m10 := sqMtx[1][0].AsNumber()
|
|
m11 := sqMtx[1][1].AsNumber()
|
|
if m00.Type != ResultTypeNumber || m01.Type != ResultTypeNumber ||
|
|
m10.Type != ResultTypeNumber || m11.Type != ResultTypeNumber {
|
|
return math.NaN()
|
|
}
|
|
return m00.ValueNumber*m11.ValueNumber -
|
|
m10.ValueNumber*m01.ValueNumber
|
|
}
|
|
|
|
res := float64(0)
|
|
sgn := float64(1)
|
|
for j := range sqMtx {
|
|
res += sgn * sqMtx[0][j].ValueNumber * det(minor(sqMtx, j))
|
|
sgn *= -1
|
|
}
|
|
return res
|
|
}
|
|
|
|
// MDeterm is an implementation of the Excel MDETERM which finds the determinant
|
|
// of a matrix.
|
|
func MDeterm(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("MDETERM() requires a single array argument")
|
|
}
|
|
|
|
mtx := args[0]
|
|
if mtx.Type != ResultTypeArray {
|
|
return MakeErrorResult("MDETERM() requires a single array argument")
|
|
}
|
|
|
|
numRows := len(mtx.ValueArray)
|
|
for _, row := range mtx.ValueArray {
|
|
if len(row) != numRows {
|
|
return MakeErrorResult("MDETERM() requires a square matrix")
|
|
}
|
|
}
|
|
return MakeNumberResult(det(mtx.ValueArray))
|
|
}
|
|
|
|
// Mod is an implementation of the Excel MOD function which returns the
|
|
// remainder after division. It requires two numeric argumnts.
|
|
func Mod(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("MOD() requires two numeric arguments")
|
|
}
|
|
n := args[0].AsNumber()
|
|
d := args[1].AsNumber()
|
|
if n.Type != ResultTypeNumber || d.Type != ResultTypeNumber {
|
|
return MakeErrorResult("MOD() requires two numeric arguments")
|
|
}
|
|
if d.ValueNumber == 0 {
|
|
return MakeErrorResultType(ErrorTypeDivideByZero, "MOD() divide by zero")
|
|
}
|
|
|
|
// Per MS page, MOD(n, d) = n - d*INT(n/d)
|
|
// where INT is trunc in:
|
|
trunc, rem := math.Modf(n.ValueNumber / d.ValueNumber)
|
|
if rem < 0 {
|
|
trunc--
|
|
}
|
|
return MakeNumberResult(n.ValueNumber - d.ValueNumber*trunc)
|
|
}
|
|
|
|
// Mround is an implementation of the Excel MROUND function. It is not a
|
|
// generic rounding function and has some oddities to match Excel's behavior.
|
|
func Mround(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("MROUND() requires two numeric arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to MROUND() must be a number")
|
|
}
|
|
|
|
// significance
|
|
significance := float64(1)
|
|
sigArg := args[1].AsNumber()
|
|
if sigArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to MROUND() must be a number")
|
|
}
|
|
significance = sigArg.ValueNumber
|
|
|
|
if significance < 0 && number.ValueNumber > 0 ||
|
|
significance > 0 && number.ValueNumber < 0 {
|
|
return MakeErrorResult("MROUND() argument signs must match")
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
v, res := math.Modf(v / significance)
|
|
if math.Trunc(res+0.5) > 0 {
|
|
v++
|
|
}
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
func multinomial(args []Result) (float64, float64, Result) {
|
|
num := 0.0
|
|
denom := 1.0
|
|
for _, arg := range args {
|
|
switch arg.Type {
|
|
case ResultTypeNumber:
|
|
num += arg.ValueNumber
|
|
denom *= fact(arg.ValueNumber)
|
|
case ResultTypeList, ResultTypeArray:
|
|
n, d, e := multinomial(arg.ListValues())
|
|
num += n
|
|
denom *= fact(d)
|
|
if e.Type == ResultTypeError {
|
|
return 0, 0, e
|
|
}
|
|
case ResultTypeString:
|
|
return 0, 0, MakeErrorResult("MULTINOMIAL() requires numeric arguments")
|
|
case ResultTypeError:
|
|
return 0, 0, arg
|
|
}
|
|
}
|
|
return num, denom, empty
|
|
}
|
|
|
|
// Multinomial implements the excel MULTINOMIAL function.
|
|
func Multinomial(args []Result) Result {
|
|
if len(args) < 1 {
|
|
return MakeErrorResult("MULTINOMIAL() requires at least one numeric input")
|
|
}
|
|
num, denom, err := multinomial(args)
|
|
if err.Type == ResultTypeError {
|
|
return err
|
|
}
|
|
return MakeNumberResult(fact(num) / denom)
|
|
}
|
|
|
|
// Munit is an implementation of the Excel MUNIT function that returns an
|
|
// identity matrix.
|
|
func Munit(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("MUNIT() requires one numeric input")
|
|
}
|
|
dim := args[0].AsNumber()
|
|
if dim.Type != ResultTypeNumber {
|
|
return MakeErrorResult("MUNIT() requires one numeric input")
|
|
}
|
|
dimInt := int(dim.ValueNumber)
|
|
mtx := make([][]Result, 0, dimInt)
|
|
for i := 0; i < dimInt; i++ {
|
|
row := make([]Result, dimInt)
|
|
for j := 0; j < dimInt; j++ {
|
|
if i == j {
|
|
row[j] = MakeNumberResult(1.0)
|
|
} else {
|
|
row[j] = MakeNumberResult(0.0)
|
|
}
|
|
}
|
|
mtx = append(mtx, row)
|
|
}
|
|
return MakeArrayResult(mtx)
|
|
}
|
|
|
|
// Odd is an implementation of the Excel ODD() that rounds a number to the
|
|
// nearest odd integer.
|
|
func Odd(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("ODD() requires one argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("ODD() requires number argument")
|
|
}
|
|
|
|
sign := math.Signbit(vArg.ValueNumber)
|
|
m, r := math.Modf((vArg.ValueNumber - 1) / 2)
|
|
v := m*2 + 1
|
|
if r != 0 {
|
|
if !sign {
|
|
v += 2
|
|
} else {
|
|
v -= 2
|
|
}
|
|
}
|
|
return MakeNumberResult(v)
|
|
}
|
|
|
|
// Pi is an implementation of the Excel Pi() function that just returns the Pi
|
|
// constant.
|
|
func Pi(args []Result) Result {
|
|
if len(args) != 0 {
|
|
return MakeErrorResult("PI() accepts no arguments")
|
|
}
|
|
return MakeNumberResult(math.Pi)
|
|
}
|
|
|
|
// Power is an implementation of the Excel POWER function that raises a number
|
|
// to a power. It requires two numeric arguments.
|
|
func Power(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("POWER() requires two numeric arguments")
|
|
}
|
|
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to POWER() must be a number")
|
|
}
|
|
|
|
exp := args[1].AsNumber()
|
|
if exp.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to POWER() must be a number")
|
|
}
|
|
|
|
return MakeNumberResult(math.Pow(number.ValueNumber, exp.ValueNumber))
|
|
}
|
|
|
|
// Product is an implementation of the Excel PRODUCT() function.
|
|
func Product(args []Result) Result {
|
|
res := 1.0
|
|
for _, a := range args {
|
|
a = a.AsNumber()
|
|
switch a.Type {
|
|
case ResultTypeNumber:
|
|
res *= a.ValueNumber
|
|
case ResultTypeList, ResultTypeArray:
|
|
subSum := Product(a.ListValues())
|
|
if subSum.Type != ResultTypeNumber {
|
|
return subSum
|
|
}
|
|
res *= subSum.ValueNumber
|
|
case ResultTypeString:
|
|
// treated as zero by Excel
|
|
case ResultTypeError:
|
|
return a
|
|
case ResultTypeEmpty:
|
|
// skip
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled PRODUCT() argument type %s", a.Type))
|
|
}
|
|
}
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
// Quotient is an implementation of the Excel QUOTIENT function that returns the
|
|
// integer portion of division.
|
|
func Quotient(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("QUOTIENT() requires two numeric arguments")
|
|
}
|
|
arg1 := args[0].AsNumber()
|
|
arg2 := args[1].AsNumber()
|
|
if arg1.Type != ResultTypeNumber || arg2.Type != ResultTypeNumber {
|
|
return MakeErrorResult("QUOTIENT() requires two numeric arguments")
|
|
}
|
|
if arg2.ValueNumber == 0 {
|
|
return MakeErrorResultType(ErrorTypeDivideByZero, "QUOTIENT() divide by zero")
|
|
}
|
|
|
|
return MakeNumberResult(math.Trunc(arg1.ValueNumber / arg2.ValueNumber))
|
|
}
|
|
|
|
// Radians is an implementation of the Excel function RADIANS() that converts
|
|
// degrees to radians.
|
|
func Radians(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("RADIANS() requires one argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("RADIANS() requires number argument")
|
|
}
|
|
|
|
return MakeNumberResult(math.Pi / 180.0 * vArg.ValueNumber)
|
|
}
|
|
|
|
// Rand is an implementation of the Excel RAND() function that returns random
|
|
// numbers in the range [0,1).
|
|
func Rand(args []Result) Result {
|
|
if len(args) != 0 {
|
|
return MakeErrorResult("RAND() accepts no arguments")
|
|
}
|
|
return MakeNumberResult(rnd.Float64())
|
|
}
|
|
|
|
// RandBetween is an implementation of the Excel RANDBETWEEN() function that returns a random
|
|
// integer in the range specified.
|
|
func RandBetween(args []Result) Result {
|
|
if len(args) != 2 {
|
|
return MakeErrorResult("RANDBETWEEN() requires two numeric arguments")
|
|
}
|
|
arg1 := args[0].AsNumber()
|
|
arg2 := args[1].AsNumber()
|
|
if arg1.Type != ResultTypeNumber || arg2.Type != ResultTypeNumber {
|
|
return MakeErrorResult("RANDBETWEEN() requires two numeric arguments")
|
|
}
|
|
if arg2.ValueNumber < arg1.ValueNumber {
|
|
return MakeErrorResult("RANDBETWEEN() requires second argument to be larger")
|
|
}
|
|
bottom := int64(arg1.ValueNumber)
|
|
top := int64(arg2.ValueNumber)
|
|
return MakeNumberResult(float64(rnd.Int63n(top-bottom+1) + bottom))
|
|
}
|
|
|
|
type ri struct {
|
|
n float64
|
|
s string
|
|
}
|
|
|
|
var r1tables = []ri{
|
|
{1000, "M"},
|
|
{900, "CM"},
|
|
{500, "D"},
|
|
{400, "CD"},
|
|
{100, "C"},
|
|
{90, "XC"},
|
|
{50, "L"},
|
|
{40, "XL"},
|
|
{10, "X"},
|
|
{9, "IX"},
|
|
{5, "V"},
|
|
{4, "IV"},
|
|
{1, "I"},
|
|
}
|
|
|
|
var r2tables = []ri{
|
|
{1000, "M"},
|
|
{950, "LM"},
|
|
{900, "CM"},
|
|
{500, "D"},
|
|
{450, "LD"},
|
|
{400, "CD"},
|
|
{100, "C"},
|
|
{95, "VC"},
|
|
{90, "XC"},
|
|
{50, "L"},
|
|
{45, "VL"},
|
|
{40, "XL"},
|
|
{10, "X"},
|
|
{9, "IX"},
|
|
{5, "V"},
|
|
{4, "IV"},
|
|
{1, "I"},
|
|
}
|
|
|
|
var r3tables = []ri{
|
|
{1000, "M"},
|
|
{990, "XM"},
|
|
{950, "LM"},
|
|
{900, "CM"},
|
|
{500, "D"},
|
|
{490, "XD"},
|
|
{450, "LD"},
|
|
{400, "CD"},
|
|
{100, "C"},
|
|
{99, "IC"},
|
|
{90, "XC"},
|
|
{50, "L"},
|
|
{45, "VL"},
|
|
{40, "XL"},
|
|
{10, "X"},
|
|
{9, "IX"},
|
|
{5, "V"},
|
|
{4, "IV"},
|
|
{1, "I"},
|
|
}
|
|
|
|
var r4tables = []ri{
|
|
{1000, "M"},
|
|
{995, "VM"},
|
|
{990, "XM"},
|
|
{950, "LM"},
|
|
{900, "CM"},
|
|
{500, "D"},
|
|
{495, "VD"},
|
|
{490, "XD"},
|
|
{450, "LD"},
|
|
{400, "CD"},
|
|
{100, "C"},
|
|
{99, "IC"},
|
|
{90, "XC"},
|
|
{50, "L"},
|
|
{45, "VL"},
|
|
{40, "XL"},
|
|
{10, "X"},
|
|
{9, "IX"},
|
|
{5, "V"},
|
|
{4, "IV"},
|
|
{1, "I"},
|
|
}
|
|
|
|
var r5tables = []ri{
|
|
{1000, "M"},
|
|
{999, "IM"},
|
|
{995, "VM"},
|
|
{990, "XM"},
|
|
{950, "LM"},
|
|
{900, "CM"},
|
|
{500, "D"},
|
|
{499, "ID"},
|
|
{495, "VD"},
|
|
{490, "XD"},
|
|
{450, "LD"},
|
|
{400, "CD"},
|
|
{100, "C"},
|
|
{99, "IC"},
|
|
{90, "XC"},
|
|
{50, "L"},
|
|
{45, "VL"},
|
|
{40, "XL"},
|
|
{10, "X"},
|
|
{9, "IX"},
|
|
{5, "V"},
|
|
{4, "IV"},
|
|
{1, "I"},
|
|
}
|
|
|
|
// Roman is an implementation of the Excel ROMAN function that convers numbers
|
|
// to roman numerals in one of 5 formats.
|
|
func Roman(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("ROMAN() requires at least one numeric argument")
|
|
}
|
|
if len(args) > 2 {
|
|
return MakeErrorResult("ROMAN() requires at most two numeric arguments")
|
|
}
|
|
|
|
nArg := args[0].AsNumber()
|
|
if nArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("ROMAN() requires at least one numeric argument")
|
|
}
|
|
format := 0
|
|
if len(args) > 1 {
|
|
fmtArg := args[1]
|
|
if fmtArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("ROMAN() requires second argument to be numeric")
|
|
}
|
|
format = int(fmtArg.ValueNumber)
|
|
if format < 0 {
|
|
format = 0
|
|
} else if format > 4 {
|
|
format = 4
|
|
}
|
|
}
|
|
dt := r1tables
|
|
switch format {
|
|
case 1:
|
|
dt = r2tables
|
|
case 2:
|
|
dt = r3tables
|
|
case 3:
|
|
dt = r4tables
|
|
case 4:
|
|
dt = r5tables
|
|
}
|
|
v := math.Trunc(nArg.ValueNumber)
|
|
buf := bytes.Buffer{}
|
|
for _, r := range dt {
|
|
for v >= r.n {
|
|
buf.WriteString(r.s)
|
|
v -= r.n
|
|
}
|
|
}
|
|
return MakeStringResult(buf.String())
|
|
}
|
|
|
|
type rmode byte
|
|
|
|
const (
|
|
closest rmode = iota
|
|
down
|
|
up
|
|
)
|
|
|
|
// Round is an implementation of the Excel ROUND function that rounds a number
|
|
// to a specified number of digits.
|
|
func round(args []Result, mode rmode) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("ROUND() requires at least one numeric arguments")
|
|
}
|
|
// number to round
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to ROUND() must be a number")
|
|
}
|
|
|
|
digits := float64(0)
|
|
if len(args) > 1 {
|
|
digitArg := args[1].AsNumber()
|
|
if digitArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to ROUND() must be a number")
|
|
}
|
|
digits = digitArg.ValueNumber
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
|
|
significance := 1.0
|
|
if digits > 0 {
|
|
significance = math.Pow(1/10.0, digits)
|
|
} else {
|
|
significance = math.Pow(10.0, -digits)
|
|
}
|
|
|
|
v, res := math.Modf(v / significance)
|
|
switch mode {
|
|
case closest:
|
|
const eps = 0.499999999
|
|
if res >= eps {
|
|
v++
|
|
} else if res <= -eps {
|
|
v--
|
|
}
|
|
case down:
|
|
// do nothing, truncates
|
|
case up:
|
|
if res > 0 {
|
|
v++
|
|
} else if res < 0 {
|
|
v--
|
|
}
|
|
}
|
|
|
|
return MakeNumberResult(v * significance)
|
|
}
|
|
|
|
// Round is an implementation of the Excel ROUND function that rounds a number
|
|
// to a specified number of digits.
|
|
func Round(args []Result) Result {
|
|
return round(args, closest)
|
|
}
|
|
|
|
// RoundDown is an implementation of the Excel ROUNDDOWN function that rounds a number
|
|
// down to a specified number of digits.
|
|
func RoundDown(args []Result) Result {
|
|
return round(args, down)
|
|
}
|
|
|
|
// RoundUp is an implementation of the Excel ROUNDUP function that rounds a number
|
|
// up to a specified number of digits.
|
|
func RoundUp(args []Result) Result {
|
|
return round(args, up)
|
|
}
|
|
|
|
// SeriesSum implements the Excel SERIESSUM function.
|
|
func SeriesSum(args []Result) Result {
|
|
if len(args) != 4 {
|
|
return MakeErrorResult("SERIESSUM() requires 4 arguments")
|
|
}
|
|
x := args[0].AsNumber()
|
|
n := args[1].AsNumber()
|
|
m := args[2].AsNumber()
|
|
coeffs := args[3].ListValues()
|
|
if x.Type != ResultTypeNumber || n.Type != ResultTypeNumber || m.Type != ResultTypeNumber {
|
|
return MakeErrorResult("SERIESSUM() requires first three arguments to be numeric")
|
|
}
|
|
res := float64(0)
|
|
for i, c := range coeffs {
|
|
res += c.ValueNumber * math.Pow(x.ValueNumber, n.ValueNumber+float64(i)*m.ValueNumber)
|
|
}
|
|
return MakeNumberResult(res)
|
|
}
|
|
|
|
func Sign(args []Result) Result {
|
|
if len(args) != 1 {
|
|
return MakeErrorResult("SIGN() requires one argument")
|
|
}
|
|
vArg := args[0].AsNumber()
|
|
if vArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("SIGN() requires a numeric argument")
|
|
}
|
|
if vArg.ValueNumber < 0 {
|
|
return MakeNumberResult(-1)
|
|
} else if vArg.ValueNumber > 0 {
|
|
return MakeNumberResult(1)
|
|
}
|
|
return MakeNumberResult(0)
|
|
}
|
|
|
|
// Sum is an implementation of the Excel SUM() function.
|
|
func Sum(args []Result) Result {
|
|
// Sum returns zero with no arguments
|
|
res := MakeNumberResult(0)
|
|
for _, a := range args {
|
|
a = a.AsNumber()
|
|
switch a.Type {
|
|
case ResultTypeNumber:
|
|
res.ValueNumber += a.ValueNumber
|
|
case ResultTypeList, ResultTypeArray:
|
|
subSum := Sum(a.ListValues())
|
|
// error as sum returns only numbers and errors
|
|
if subSum.Type != ResultTypeNumber {
|
|
return subSum
|
|
}
|
|
res.ValueNumber += subSum.ValueNumber
|
|
case ResultTypeString:
|
|
// treated as zero by Excel
|
|
case ResultTypeError:
|
|
return a
|
|
case ResultTypeEmpty:
|
|
// skip
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled SUM() argument type %s", a.Type))
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// SumIf implements the SUMIF function.
|
|
func SumIf(args []Result) Result {
|
|
if len(args) < 3 {
|
|
return MakeErrorResult("SUMIF requires three arguments")
|
|
}
|
|
|
|
arrResult := args[0]
|
|
if arrResult.Type != ResultTypeArray && arrResult.Type != ResultTypeList {
|
|
return MakeErrorResult("SUMIF requires first argument of type array")
|
|
}
|
|
arr := arrayFromRange(arrResult)
|
|
|
|
sumArrResult := args[2]
|
|
if sumArrResult.Type != ResultTypeArray && sumArrResult.Type != ResultTypeList {
|
|
return MakeErrorResult("SUMIF requires last argument of type array")
|
|
}
|
|
sumArr := arrayFromRange(sumArrResult)
|
|
|
|
criteria := parseCriteria(args[1])
|
|
sum := 0.0
|
|
for ir, r := range arr {
|
|
for ic, value := range r {
|
|
if compare(value, criteria) {
|
|
sum += sumArr[ir][ic].ValueNumber
|
|
}
|
|
}
|
|
}
|
|
return MakeNumberResult(sum)
|
|
}
|
|
|
|
// SumIfs implements the SUMIFS function.
|
|
func SumIfs(args []Result) Result {
|
|
errorResult := checkIfsRanges(args, true, "SUMIFS")
|
|
if errorResult.Type != ResultTypeEmpty {
|
|
return errorResult
|
|
}
|
|
match := getIfsMatch(args[1:])
|
|
sum := 0.0
|
|
sumArr := arrayFromRange(args[0])
|
|
for _, indexes := range match {
|
|
sum += sumArr[indexes.rowIndex][indexes.colIndex].ValueNumber
|
|
}
|
|
return MakeNumberResult(float64(sum))
|
|
}
|
|
|
|
// SumProduct is an implementation of the Excel SUMPRODUCT() function.
|
|
func SumProduct(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("SUMPRODUCT() requires at least one argument")
|
|
}
|
|
t := args[0].Type
|
|
for _, a := range args {
|
|
if a.Type != t {
|
|
return MakeErrorResult("SUMPRODUCT() requires all arguments of the same type")
|
|
}
|
|
}
|
|
switch t {
|
|
case ResultTypeNumber:
|
|
return Product(args)
|
|
case ResultTypeList, ResultTypeArray:
|
|
n := len(args[0].ListValues())
|
|
res := make([]float64, n)
|
|
for i := range res {
|
|
res[i] = 1.0
|
|
}
|
|
for _, a := range args {
|
|
if len(a.ListValues()) != n {
|
|
return MakeErrorResult("SUMPRODUCT() requires all arguments to have the same dimension")
|
|
}
|
|
for i, v := range a.ListValues() {
|
|
v = v.AsNumber()
|
|
if v.Type != ResultTypeNumber {
|
|
return MakeErrorResult("SUMPRODUCT() requires all arguments to be numeric")
|
|
}
|
|
res[i] = res[i] * v.ValueNumber
|
|
}
|
|
}
|
|
v := 0.0
|
|
for _, r := range res {
|
|
v += r
|
|
}
|
|
return MakeNumberResult(v)
|
|
|
|
}
|
|
return MakeNumberResult(1.0)
|
|
}
|
|
|
|
// SumSquares is an implementation of the Excel SUMSQ() function.
|
|
func SumSquares(args []Result) Result {
|
|
// Sum returns zero with no arguments
|
|
res := MakeNumberResult(0)
|
|
for _, a := range args {
|
|
a = a.AsNumber()
|
|
switch a.Type {
|
|
case ResultTypeNumber:
|
|
res.ValueNumber += a.ValueNumber * a.ValueNumber
|
|
case ResultTypeList, ResultTypeArray:
|
|
subSum := SumSquares(a.ListValues())
|
|
// error as sum returns only numbers and errors
|
|
if subSum.Type != ResultTypeNumber {
|
|
return subSum
|
|
}
|
|
res.ValueNumber += subSum.ValueNumber
|
|
case ResultTypeString:
|
|
// treated as zero by Excel
|
|
case ResultTypeError:
|
|
return a
|
|
case ResultTypeEmpty:
|
|
// skip
|
|
default:
|
|
return MakeErrorResult(fmt.Sprintf("unhandled SUMSQUARES() argument type %s", a.Type))
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func Trunc(args []Result) Result {
|
|
if len(args) == 0 {
|
|
return MakeErrorResult("TRUNC() requires at least one numeric arguments")
|
|
}
|
|
// number to truncate
|
|
number := args[0].AsNumber()
|
|
if number.Type != ResultTypeNumber {
|
|
return MakeErrorResult("first argument to TRUNC() must be a number")
|
|
}
|
|
|
|
digits := float64(0)
|
|
if len(args) > 1 {
|
|
digitArg := args[1].AsNumber()
|
|
if digitArg.Type != ResultTypeNumber {
|
|
return MakeErrorResult("second argument to TRUNC() must be a number")
|
|
}
|
|
digits = digitArg.ValueNumber
|
|
}
|
|
|
|
v := number.ValueNumber
|
|
|
|
significance := 1.0
|
|
if digits >= 0 {
|
|
significance = math.Pow(1/10.0, digits)
|
|
} else {
|
|
// Excel returns zero for this case
|
|
return MakeNumberResult(0)
|
|
}
|
|
|
|
v, res := math.Modf(v / significance)
|
|
|
|
eps := 0.99999
|
|
if res > eps {
|
|
v++
|
|
} else if res < -eps {
|
|
v--
|
|
}
|
|
_ = res
|
|
return MakeNumberResult(v * significance)
|
|
}
|