mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-29 13:49:10 +08:00
formula: trig functions
- ACOT - ACOTH - CEILING (legacy version) - COT - COTH - CSC - CSCH - FLOOR (legacy version) - SEC - SECH
This commit is contained in:
parent
29e03fc7b7
commit
09419b822f
@ -9,6 +9,8 @@ package formula_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -74,7 +76,16 @@ func TestEval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReferenceSheet(t *testing.T) {
|
func TestReferenceSheet(t *testing.T) {
|
||||||
wb, err := spreadsheet.Open("testdata/formulareference.xlsx")
|
testSheet("formulareference.xlsx", t)
|
||||||
|
}
|
||||||
|
func TestMacExcelSheet(t *testing.T) {
|
||||||
|
testSheet("MacExcel365.xlsx", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSheet(fn string, t *testing.T) {
|
||||||
|
// TODO: uncomment once we quit building on 1.8
|
||||||
|
//t.Helper()
|
||||||
|
wb, err := spreadsheet.Open("testdata/" + fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error opening reference sheet: %s", err)
|
t.Fatalf("error opening reference sheet: %s", err)
|
||||||
}
|
}
|
||||||
@ -96,7 +107,7 @@ func TestReferenceSheet(t *testing.T) {
|
|||||||
// so evaluating the formula in the context of the sheet,
|
// so evaluating the formula in the context of the sheet,
|
||||||
// should return the same results that Excel computed
|
// should return the same results that Excel computed
|
||||||
result := cellFormula.Eval(sheet.FormulaContext(), formula.NewEvaluator())
|
result := cellFormula.Eval(sheet.FormulaContext(), formula.NewEvaluator())
|
||||||
if got := result.Value(); got != cachedValue {
|
if got := result.Value(); !cmpValue(got, cachedValue) {
|
||||||
t.Errorf("expected '%s', got '%s' for %s cell %s (%s) %s", cachedValue, got, sheet.Name(), cell.Reference(), cell.GetFormula(), result.ErrorMessage)
|
t.Errorf("expected '%s', got '%s' for %s cell %s (%s) %s", cachedValue, got, sheet.Name(), cell.Reference(), cell.GetFormula(), result.ErrorMessage)
|
||||||
} else {
|
} else {
|
||||||
formulaCount++
|
formulaCount++
|
||||||
@ -106,5 +117,19 @@ func TestReferenceSheet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Logf("evaluated %d formulas from reference sheet", formulaCount)
|
t.Logf("evaluated %d formulas from %s sheet", formulaCount, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpValue(l, r string) bool {
|
||||||
|
if l == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lf, el := strconv.ParseFloat(l, 64)
|
||||||
|
rf, er := strconv.ParseFloat(l, 64)
|
||||||
|
if el == nil && er == nil {
|
||||||
|
if math.Abs(lf-rf) < 1e-7 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ func init() {
|
|||||||
RegisterFunction("ABS", makeMathWrapper("ASIN", math.Abs))
|
RegisterFunction("ABS", makeMathWrapper("ASIN", math.Abs))
|
||||||
RegisterFunction("ACOS", makeMathWrapper("ASIN", math.Acos))
|
RegisterFunction("ACOS", makeMathWrapper("ASIN", math.Acos))
|
||||||
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
||||||
// TODO: RegisterFunction("ACOT", Acot) /// Excel 2013+
|
RegisterFunction("_xlfn.ACOT", makeMathWrapper("ACOT", func(v float64) float64 { return math.Pi/2 - math.Atan(v) }))
|
||||||
// TODO: RegisterFunction("ACOTH", Acoth) /// Excel 2013+
|
RegisterFunction("_xlfn.ACOTH", makeMathWrapper("ACOTH", func(v float64) float64 { return math.Atanh(1 / v) }))
|
||||||
// TODO: RegisterFunction("_xlfn.AGGREGATE", Aggregate) // lots of dependencies
|
// TODO: RegisterFunction("_xlfn.AGGREGATE", Aggregate) // lots of dependencies
|
||||||
RegisterFunction("_xlfn.ARABIC", Arabic)
|
RegisterFunction("_xlfn.ARABIC", Arabic)
|
||||||
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
||||||
@ -34,24 +34,24 @@ func init() {
|
|||||||
RegisterFunction("ATANH", makeMathWrapper("ATANH", math.Atanh))
|
RegisterFunction("ATANH", makeMathWrapper("ATANH", math.Atanh))
|
||||||
RegisterFunction("ATAN2", Atan2)
|
RegisterFunction("ATAN2", Atan2)
|
||||||
RegisterFunction("_xlfn.BASE", Base)
|
RegisterFunction("_xlfn.BASE", Base)
|
||||||
// RegisterFunction("CEILING", ) // TODO: figure out how this acts, Libre doesn't use it
|
RegisterFunction("CEILING", Ceiling)
|
||||||
RegisterFunction("_xlfn.CEILING.MATH", CeilingMath)
|
RegisterFunction("_xlfn.CEILING.MATH", CeilingMath)
|
||||||
RegisterFunction("_xlfn.CEILING.PRECISE", CeilingPrecise)
|
RegisterFunction("_xlfn.CEILING.PRECISE", CeilingPrecise)
|
||||||
RegisterFunction("COMBIN", Combin)
|
RegisterFunction("COMBIN", Combin)
|
||||||
RegisterFunction("_xlfn.COMBINA", Combina)
|
RegisterFunction("_xlfn.COMBINA", Combina)
|
||||||
RegisterFunction("COS", makeMathWrapper("COS", math.Cos))
|
RegisterFunction("COS", makeMathWrapper("COS", math.Cos))
|
||||||
RegisterFunction("COSH", makeMathWrapper("COSH", math.Cosh))
|
RegisterFunction("COSH", makeMathWrapper("COSH", math.Cosh))
|
||||||
//RegisterFunction("COT",
|
RegisterFunction("_xlfn.COT", makeMathWrapperInv("COT", math.Tan))
|
||||||
//RegisterFunction("COTH"
|
RegisterFunction("_xlfn.COTH", makeMathWrapperInv("COTH", math.Tanh))
|
||||||
//RegisterFunction("CSC"
|
RegisterFunction("_xlfn.CSC", makeMathWrapperInv("CSC", math.Sin))
|
||||||
//RegisterFunction("CSCH"
|
RegisterFunction("_xlfn.CSCH", makeMathWrapperInv("CSC", math.Sinh))
|
||||||
RegisterFunction("_xlfn.DECIMAL", Decimal)
|
RegisterFunction("_xlfn.DECIMAL", Decimal)
|
||||||
RegisterFunction("DEGREES", Degrees)
|
RegisterFunction("DEGREES", Degrees)
|
||||||
RegisterFunction("EVEN", Even)
|
RegisterFunction("EVEN", Even)
|
||||||
RegisterFunction("EXP", makeMathWrapper("EXP", math.Exp))
|
RegisterFunction("EXP", makeMathWrapper("EXP", math.Exp))
|
||||||
RegisterFunction("FACT", Fact)
|
RegisterFunction("FACT", Fact)
|
||||||
RegisterFunction("FACTDOUBLE", FactDouble)
|
RegisterFunction("FACTDOUBLE", FactDouble)
|
||||||
//RegisterFunction("FLOOR", )
|
RegisterFunction("FLOOR", Floor)
|
||||||
RegisterFunction("_xlfn.FLOOR.MATH", FloorMath)
|
RegisterFunction("_xlfn.FLOOR.MATH", FloorMath)
|
||||||
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
||||||
RegisterFunction("GCD", GCD)
|
RegisterFunction("GCD", GCD)
|
||||||
@ -80,8 +80,8 @@ func init() {
|
|||||||
RegisterFunction("ROUND", Round)
|
RegisterFunction("ROUND", Round)
|
||||||
RegisterFunction("ROUNDDOWN", RoundDown)
|
RegisterFunction("ROUNDDOWN", RoundDown)
|
||||||
RegisterFunction("ROUNDUP", RoundUp)
|
RegisterFunction("ROUNDUP", RoundUp)
|
||||||
// RegisterFunction("SEC"
|
RegisterFunction("_xlfn.SEC", makeMathWrapperInv("SEC", math.Cos))
|
||||||
// RegisterFunction("SECH"
|
RegisterFunction("_xlfn.SECH", makeMathWrapperInv("SECH", math.Cosh))
|
||||||
RegisterFunction("SERIESSUM", SeriesSum)
|
RegisterFunction("SERIESSUM", SeriesSum)
|
||||||
RegisterFunction("SIGN", Sign)
|
RegisterFunction("SIGN", Sign)
|
||||||
RegisterFunction("SIN", makeMathWrapper("SIN", math.Sin))
|
RegisterFunction("SIN", makeMathWrapper("SIN", math.Sin))
|
||||||
@ -132,6 +132,36 @@ func makeMathWrapper(name string, fn func(x float64) float64) Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
// 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.
|
// and the arguments are (x,y), reversed from normal to match Excel's behaviour.
|
||||||
func Atan2(args []Result) Result {
|
func Atan2(args []Result) Result {
|
||||||
@ -273,6 +303,50 @@ func CeilingMath(args []Result) Result {
|
|||||||
return MakeNumberResult(v * significance)
|
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
|
// CeilingPrecise is an implementation of the CEILING.PRECISE function which
|
||||||
// returns the ceiling of a number.
|
// returns the ceiling of a number.
|
||||||
func CeilingPrecise(args []Result) Result {
|
func CeilingPrecise(args []Result) Result {
|
||||||
@ -296,7 +370,7 @@ func CeilingPrecise(args []Result) Result {
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
sigArg := args[1].AsNumber()
|
sigArg := args[1].AsNumber()
|
||||||
if sigArg.Type != ResultTypeNumber {
|
if sigArg.Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("second argument to CEILING.MATH() must be a number")
|
return MakeErrorResult("second argument to CEILING.PRECISE() must be a number")
|
||||||
}
|
}
|
||||||
// don't care about sign of significance
|
// don't care about sign of significance
|
||||||
significance = math.Abs(sigArg.ValueNumber)
|
significance = math.Abs(sigArg.ValueNumber)
|
||||||
@ -571,6 +645,39 @@ func FloorMath(args []Result) Result {
|
|||||||
return MakeNumberResult(v * significance)
|
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.
|
// FloorPrecise is an implementation of the FlOOR.PRECISE function.
|
||||||
func FloorPrecise(args []Result) Result {
|
func FloorPrecise(args []Result) Result {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@ -593,7 +700,7 @@ func FloorPrecise(args []Result) Result {
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
sigArg := args[1].AsNumber()
|
sigArg := args[1].AsNumber()
|
||||||
if sigArg.Type != ResultTypeNumber {
|
if sigArg.Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("second argument to FLOOR.MATH() must be a number")
|
return MakeErrorResult("second argument to FLOOR.PRECISE() must be a number")
|
||||||
}
|
}
|
||||||
// don't care about sign of significance
|
// don't care about sign of significance
|
||||||
significance = math.Abs(sigArg.ValueNumber)
|
significance = math.Abs(sigArg.ValueNumber)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
package formula
|
package formula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,6 +118,10 @@ func (r Result) AsString() Result {
|
|||||||
|
|
||||||
// MakeNumberResult constructs a number result.
|
// MakeNumberResult constructs a number result.
|
||||||
func MakeNumberResult(v float64) Result {
|
func MakeNumberResult(v float64) Result {
|
||||||
|
// Excel doesn't use negative zero
|
||||||
|
if v == math.Copysign(0, -1) {
|
||||||
|
v = 0
|
||||||
|
}
|
||||||
return Result{Type: ResultTypeNumber, ValueNumber: v}
|
return Result{Type: ResultTypeNumber, ValueNumber: v}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
spreadsheet/formula/testdata/MacExcel365.xlsx
vendored
Normal file
BIN
spreadsheet/formula/testdata/MacExcel365.xlsx
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user