diff --git a/calc.go b/calc.go index 55290563..66978bd7 100644 --- a/calc.go +++ b/calc.go @@ -29,6 +29,8 @@ import ( "unsafe" "github.com/xuri/efp" + "golang.org/x/text/language" + "golang.org/x/text/message" ) // Excel formula errors @@ -273,6 +275,7 @@ var tokenPriority = map[string]int{ // FINDB // FISHER // FISHERINV +// FIXED // FLOOR // FLOOR.MATH // FLOOR.PRECISE @@ -4883,6 +4886,55 @@ func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg { return newBoolFormulaArg(text1 == text2) } +// FIXED function rounds a supplied number to a specified number of decimal +// places and then converts this into text. The syntax of the function is: +// +// FIXED(number,[decimals],[no_commas]) +// +func (fn *formulaFuncs) FIXED(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FIXED requires at least 1 argument") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "FIXED allows at most 3 arguments") + } + numArg := argsList.Front().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + precision, decimals, noCommas := 0, 0, false + s := strings.Split(argsList.Front().Value.(formulaArg).Value(), ".") + if argsList.Len() == 1 && len(s) == 2 { + precision = len(s[1]) + decimals = len(s[1]) + } + if argsList.Len() >= 2 { + decimalsArg := argsList.Front().Next().Value.(formulaArg).ToNumber() + if decimalsArg.Type != ArgNumber { + return decimalsArg + } + decimals = int(decimalsArg.Number) + } + if argsList.Len() == 3 { + noCommasArg := argsList.Back().Value.(formulaArg).ToBool() + if noCommasArg.Type == ArgError { + return noCommasArg + } + noCommas = noCommasArg.Boolean + } + n := math.Pow(10, float64(decimals)) + r := numArg.Number * n + fixed := float64(int(r+math.Copysign(0.5, r))) / n + if decimals > 0 { + precision = decimals + } + if noCommas { + return newStringFormulaArg(fmt.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) + } + p := message.NewPrinter(language.English) + return newStringFormulaArg(p.Sprintf(fmt.Sprintf("%%.%df", precision), fixed)) +} + // FIND function returns the position of a specified character or sub-string // within a supplied text string. The function is case-sensitive. The syntax // of the function is: diff --git a/calc_test.go b/calc_test.go index 6fc61b07..d6a15c91 100644 --- a/calc_test.go +++ b/calc_test.go @@ -759,6 +759,15 @@ func TestCalcCellValue(t *testing.T) { "=EXACT(1,\"1\")": "TRUE", "=EXACT(1,1)": "TRUE", "=EXACT(\"A\",\"a\")": "FALSE", + // FIXED + "=FIXED(5123.591)": "5,123.591", + "=FIXED(5123.591,1)": "5,123.6", + "=FIXED(5123.591,0)": "5,124", + "=FIXED(5123.591,-1)": "5,120", + "=FIXED(5123.591,-2)": "5,100", + "=FIXED(5123.591,-3,TRUE)": "5000", + "=FIXED(5123.591,-5)": "0", + "=FIXED(-77262.23973,-5)": "-100,000", // FIND "=FIND(\"T\",\"Original Text\")": "10", "=FIND(\"t\",\"Original Text\")": "13", @@ -1478,6 +1487,12 @@ func TestCalcCellValue(t *testing.T) { // EXACT "=EXACT()": "EXACT requires 2 arguments", "=EXACT(1,2,3)": "EXACT requires 2 arguments", + // FIXED + "=FIXED()": "FIXED requires at least 1 argument", + "=FIXED(0,1,2,3)": "FIXED allows at most 3 arguments", + "=FIXED(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=FIXED(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=FIXED(0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax", // FIND "=FIND()": "FIND requires at least 2 arguments", "=FIND(1,2,3,4)": "FIND allows at most 3 arguments", diff --git a/go.mod b/go.mod index 4318ff85..01e837ac 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/richardlehane/mscfb v1.0.3 github.com/stretchr/testify v1.6.1 github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17 - golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 - golang.org/x/image v0.0.0-20201208152932-35266b937fa6 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 + golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb + golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 golang.org/x/text v0.3.5 ) diff --git a/go.sum b/go.sum index ee79f1c4..4dfc806e 100644 --- a/go.sum +++ b/go.sum @@ -13,18 +13,15 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17 h1:Ou4I7pYPQBk/qE9K2y31rawl/ftLHbTJJAFYJPVSyQo= github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo= +golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=