diff --git a/.travis.yml b/.travis.yml index cd22ebb8..f302eede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ go: - 1.13.x - 1.14.x - 1.15.x + - 1.16.x os: - linux diff --git a/calc.go b/calc.go index ca44bf5c..a11315a1 100644 --- a/calc.go +++ b/calc.go @@ -26,6 +26,7 @@ import ( "strings" "time" "unicode" + "unsafe" "github.com/xuri/efp" ) @@ -240,6 +241,9 @@ var tokenPriority = map[string]int{ // CSC // CSCH // DATE +// DEC2BIN +// DEC2HEX +// DEC2OCT // DECIMAL // DEGREES // ENCODEURL @@ -1140,7 +1144,100 @@ func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, er return } -// Math and Trigonometric functions +// Engineering Functions + +// DEC2BIN function converts a decimal number into a Binary (Base 2) number. +// The syntax of the function is: +// +// DEC2BIN(number,[places]) +// +func (fn *formulaFuncs) DEC2BIN(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2BIN requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2BIN allows at most 2 arguments") + } + return fn.dec2x("DEC2BIN", argsList) +} + +// DEC2HEX function converts a decimal number into a Hexadecimal (Base 16) +// number. The syntax of the function is: +// +// DEC2HEX(number,[places]) +// +func (fn *formulaFuncs) DEC2HEX(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2HEX requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2HEX allows at most 2 arguments") + } + return fn.dec2x("DEC2HEX", argsList) +} + +// DEC2OCT function converts a decimal number into an Octal (Base 8) number. +// The syntax of the function is: +// +// DEC2OCT(number,[places]) +// +func (fn *formulaFuncs) DEC2OCT(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2OCT requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "DEC2OCT allows at most 2 arguments") + } + return fn.dec2x("DEC2OCT", argsList) +} + +// dec2x is an implementation of the formula function DEC2BIN, DEC2HEX and DEC2OCT. +func (fn *formulaFuncs) dec2x(name string, argsList *list.List) formulaArg { + decimal := argsList.Front().Value.(formulaArg).ToNumber() + if decimal.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, decimal.Error) + } + maxLimitMap := map[string]float64{ + "DEC2BIN": 511, + "DEC2HEX": 549755813887, + "DEC2OCT": 536870911, + } + minLimitMap := map[string]float64{ + "DEC2BIN": -512, + "DEC2HEX": -549755813888, + "DEC2OCT": -536870912, + } + baseMap := map[string]int{ + "DEC2BIN": 2, + "DEC2HEX": 16, + "DEC2OCT": 8, + } + maxLimit := maxLimitMap[name] + minLimit := minLimitMap[name] + base := baseMap[name] + if decimal.Number < minLimit || decimal.Number > maxLimit { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + n := int64(decimal.Number) + binary := strconv.FormatUint(*(*uint64)(unsafe.Pointer(&n)), base) + if argsList.Len() == 2 { + places := argsList.Back().Value.(formulaArg).ToNumber() + if places.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, places.Error) + } + binaryPlaces := len(binary) + if places.Number < 0 || places.Number > 10 || binaryPlaces > int(places.Number) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%s%s", strings.Repeat("0", int(places.Number)-binaryPlaces), binary))) + } + if decimal.Number < 0 && len(binary) > 10 { + return newStringFormulaArg(strings.ToUpper(binary[len(binary)-10:])) + } + return newStringFormulaArg(strings.ToUpper(binary)) +} + +// Math and Trigonometric Functions // ABS function returns the absolute value of any supplied number. The syntax // of the function is: @@ -4357,7 +4454,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg { return newStringFormulaArg(result) } -// Excel Lookup and Reference Functions +// Lookup and Reference Functions // CHOOSE function returns a value from an array, that corresponds to a // supplied index number (position). The syntax of the function is: diff --git a/calc_test.go b/calc_test.go index 5b406bcc..28ffb6cf 100644 --- a/calc_test.go +++ b/calc_test.go @@ -46,6 +46,25 @@ func TestCalcCellValue(t *testing.T) { "=2>=1": "TRUE", "=2>=3": "FALSE", "=1&2": "12", + // Engineering Functions + // DEC2BIN + "=DEC2BIN(2)": "10", + "=DEC2BIN(3)": "11", + "=DEC2BIN(2,10)": "0000000010", + "=DEC2BIN(-2)": "1111111110", + "=DEC2BIN(6)": "110", + // DEC2HEX + "=DEC2HEX(10)": "A", + "=DEC2HEX(31)": "1F", + "=DEC2HEX(16,10)": "0000000010", + "=DEC2HEX(-16)": "FFFFFFFFF0", + "=DEC2HEX(273)": "111", + // DEC2OCT + "=DEC2OCT(8)": "10", + "=DEC2OCT(18)": "22", + "=DEC2OCT(8,10)": "0000000010", + "=DEC2OCT(-8)": "7777777770", + "=DEC2OCT(237)": "355", // ABS "=ABS(-1)": "1", "=ABS(-6.5)": "6.5", @@ -707,6 +726,31 @@ func TestCalcCellValue(t *testing.T) { } mathCalcError := map[string]string{ "=1/0": "#DIV/0!", + // Engineering Functions + // DEC2BIN + "=DEC2BIN()": "DEC2BIN requires at least 1 argument", + "=DEC2BIN(1,1,1)": "DEC2BIN allows at most 2 arguments", + "=DEC2BIN(\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2BIN(1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2BIN(-513,10)": "#NUM!", + "=DEC2BIN(1,-1)": "#NUM!", + "=DEC2BIN(2,1)": "#NUM!", + // DEC2HEX + "=DEC2HEX()": "DEC2HEX requires at least 1 argument", + "=DEC2HEX(1,1,1)": "DEC2HEX allows at most 2 arguments", + "=DEC2HEX(\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2HEX(1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2HEX(-549755813888,10)": "#NUM!", + "=DEC2HEX(1,-1)": "#NUM!", + "=DEC2HEX(31,1)": "#NUM!", + // DEC2OCT + "=DEC2OCT()": "DEC2OCT requires at least 1 argument", + "=DEC2OCT(1,1,1)": "DEC2OCT allows at most 2 arguments", + "=DEC2OCT(\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2OCT(1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=DEC2OCT(-536870912 ,10)": "#NUM!", + "=DEC2OCT(1,-1)": "#NUM!", + "=DEC2OCT(8,1)": "#NUM!", // ABS "=ABS()": "ABS requires 1 numeric argument", `=ABS("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",