// Copyright 2017 Baliance. 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 by contacting sales@baliance.com. package formula import ( "fmt" "math" "strconv" "strings" ) func init() { RegisterFunction("ABS", makeMathWrapper("ASIN", math.Abs)) RegisterFunction("ACOS", makeMathWrapper("ASIN", math.Acos)) RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh)) // TODO: RegisterFunction("ACOT", Acot) /// Excel 2013+ // TODO: RegisterFunction("ACOTH", Acoth) /// Excel 2013+ // TODO: RegisterFunction("_xlfn.AGGREGATE", Aggregate) // lots of dependencies 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", ) // TODO: figure out how this acts, Libre doesn't use it 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("COT", //RegisterFunction("COTH" //RegisterFunction("CSC" //RegisterFunction("CSCH" RegisterFunction("_xlfn.DECIMAL", Decimal) RegisterFunction("DEGREES", Degrees) RegisterFunction("EVEN", Even) RegisterFunction("EXP", makeMathWrapper("EXP", math.Exp)) RegisterFunction("FACT", Fact) RegisterFunction("FACTDOUBLE", FactDouble) //RegisterFunction("FLOOR", ) RegisterFunction("_xlfn.FLOOR.MATH", FloorMath) RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise) RegisterFunction("GCD", GCD) RegisterFunction("INT", Int) RegisterFunction("ISO.CEILING", CeilingPrecise) // appears to be the same from what I can tell RegisterFunction("LCM", LCM) RegisterFunction("LN", makeMathWrapper("LN", math.Log)) RegisterFunction("LOG", Log) RegisterFunction("LOG10", makeMathWrapper("LOG10", math.Log10)) RegisterFunction("PI", Pi) } // 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)) } } } // 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) } // 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.MATH() 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) } // 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.MATH() 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: res := GCD(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("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)) } // 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) }