unioffice/spreadsheet/formula/fnmathtrig.go
Todd e0786dae4a formula: support for more math/trig formulas
- EVEN
- EXP
- FACT
- FACTDOUBLE
- FLOOR.MATH
- FLOOR.PRECISE
- GCD
- INT
- ISO.CEILING
- LCM
- LN
- LOG
- LOG10
2017-09-16 09:46:44 -05:00

747 lines
20 KiB
Go

// 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)
}