From e0b014abf2c47eacfc415d3fb8bece92f2d59383 Mon Sep 17 00:00:00 2001 From: lucas Date: Thu, 19 Sep 2024 10:28:48 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E9=87=8D=E5=BB=BA=20=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 9 ++ README.md | 2 +- calc_test.go | 109 ++++++++++++++ formulaTemplateCalculate.go | 276 ++++++++++++++++++++++++++++++++++++ go.mod | 14 ++ go.sum | 12 ++ valueHelper.go | 115 +++++++++++++++ 7 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 calc_test.go create mode 100644 formulaTemplateCalculate.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 valueHelper.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 2048f6e..c2b0f43 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # common_calc -原有 gitea 仓库损坏重建 \ No newline at end of file +通用计算模块 \ No newline at end of file diff --git a/calc_test.go b/calc_test.go new file mode 100644 index 0000000..0a051b5 --- /dev/null +++ b/calc_test.go @@ -0,0 +1,109 @@ +package common_calc + +import ( + "github.com/stretchr/testify/assert" + "log" + "math" + "testing" +) + +func Test_formulaTemplate_demo1(t *testing.T) { + //公式表达式计算 + formula := "(1.1*2 + 5/2)+ 2.4/2 + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo2(t *testing.T) { + //公式表达式计算 + formula := "2² + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo3(t *testing.T) { + //公式表达式计算 + formula := "(1.1*2 + 5/2)+ 2.4/2² + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo4(t *testing.T) { + //公式表达式计算 √ + formula := "√4 + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo5(t *testing.T) { + //公式表达式计算 √ + formula := "√4+12 + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} +func Test_formulaTemplate_demo6(t *testing.T) { + //公式表达式计算 √ + formula := "3*√4*5 + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo7(t *testing.T) { + //公式表达式计算 √ + formula := "3*√(2*8) + 0.8" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo8(t *testing.T) { + //公式表达式计算 √ + formula := "2*[1+2]+0.5" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) + formula2 := "2*[3*(1+2)]+0.5" + result2 := CalculateFormula(formula2) + log.Printf("表达式 %s =>计算结果=%v", formula2, result2) +} + +func Test_formulaTemplate_demo9(t *testing.T) { + formula := "sin(30*π/180)+cos(60*π/180)" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +func Test_formulaTemplate_demo10(t *testing.T) { + formula := "[sin(30*π/180)+1]+sin(60*π/180)" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} +func Test_formulaTemplate_demo11(t *testing.T) { + formula := "[1+2³+3]" + result := CalculateFormula(formula) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} +func Test_formulaTemplate_demo12(t *testing.T) { + formula := "1+2*π" + result := CalculateFormula(formula) + assert.Equal(t, result, 1+2*math.Pi) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} +func Test_formulaTemplate_demo12_2(t *testing.T) { + formula := "1+2π" + result := CalculateFormula(formula) + assert.Equal(t, result, 1+2*math.Pi) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} +func Test_formulaTemplate_demo13(t *testing.T) { + formula := "3^2+1" + result := CalculateFormula(formula) + assert.Equal(t, result, 10.0) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} + +// -0.37-0 +func Test_formulaTemplate_demo14(t *testing.T) { + formula := "-0.37-0" + result := CalculateFormula(formula) + assert.Equal(t, result, -0.37) + log.Printf("表达式 %s =>计算结果=%v", formula, result) +} diff --git a/formulaTemplateCalculate.go b/formulaTemplateCalculate.go new file mode 100644 index 0000000..428465f --- /dev/null +++ b/formulaTemplateCalculate.go @@ -0,0 +1,276 @@ +package common_calc + +import ( + "errors" + "fmt" + "log" + "math" + "strconv" + "strings" + "unicode" +) + +func CalculateFormula(formulaExpression string) float64 { + //log.Printf("计算 %s\n", formulaExpression) + formulaExpression = strings.Replace(formulaExpression, " ", "", -1) + postfix, err := infix2ToPostfix(formulaExpression) + if err != nil { + log.Printf("计算异常[%s]=>%s", err.Error(), formulaExpression) + return -255 + } + return calculatePostfix(postfix) +} + +func calculatePostfix(postfix []string) float64 { + stack := Stack{} + fixLen := len(postfix) + for i := 0; i < fixLen; i++ { + nextChar := postfix[i] + // 数字:直接压栈 + + if _, err := strconv.ParseFloat(nextChar, 64); err == nil { + stack.Push(nextChar) + } else { + if nextChar == "π" { + stack.Push(fmt.Sprintf("%v", math.Pi)) + } else { + + switch nextChar { + case "sin": + // 操作符:取出数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Sin(num1))) + case "cos": + // 操作符:取出数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Cos(num1))) + case "√": + // 操作符:取出数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Sqrt(num1))) + case "²": + // 操作符:取出数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Pow(num1, 2))) + case "³": + // 操作符:取出数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Pow(num1, 3))) + case "+": + // 操作符:取出两个数字计算值,再将结果压栈 + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + num2, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", num2+num1)) + case "-": + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + num2, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", num2-num1)) + case "*": + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + num2, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", num2*num1)) + case "/": + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + num2, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", num2/num1)) + case "^": + // 操作符:取出两个数字计算值,再将结果压栈 + num2, _ := strconv.ParseFloat(stack.Pop(), 64) + num1, _ := strconv.ParseFloat(stack.Pop(), 64) + stack.Push(fmt.Sprintf("%v", math.Pow(num1, num2))) + } + } + } + } + result, _ := strconv.ParseFloat(stack.Top(), 64) + return result +} + +// 中缀表达式转后缀表达式 +func infix2ToPostfix(expStr string) ([]string, error) { + stack := Stack{} + postfix := make([]string, 0) + + //-1.1-2 => 0-1.1-2 + //-1.1*2+1 => 0-1.1*2+1 + if strings.HasPrefix(expStr, "-") { + expStr = fmt.Sprintf("0%s", expStr) + } + + exp := []rune(expStr) + expLen := len(exp) + + // 遍历整个表达式 + itemStr := "" + for i := 0; i < expLen; i++ { + + char := string(exp[i]) + itemStr += char + + switch itemStr { + case " ": + continue + case "[": + // 左括号直接入栈 + stack.Push("[") + case "]": + // 右括号则弹出元素直到遇到左括号 + for !stack.IsEmpty() { + preChar := stack.Top() + if preChar == "[" { + stack.Pop() // 弹出 "(" + break + } + postfix = append(postfix, preChar) + stack.Pop() + } + + case "(": + // 左括号直接入栈 + stack.Push("(") + case ")": + // 右括号则弹出元素直到遇到左括号 + for !stack.IsEmpty() { + preChar := stack.Top() + if preChar == "(" { + stack.Pop() // 弹出 "(" + break + } + postfix = append(postfix, preChar) + stack.Pop() + } + case "π": + postfix = append(postfix, "π") + // 数字则直接输出 + case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".": + j := i + digit := "" + for ; j < expLen && (unicode.IsDigit(exp[j]) || "." == string(exp[j])); j++ { + digit += string(exp[j]) + } + + postfix = append(postfix, digit) + + //支持 如2π这种写法 + if j < expLen && "π" == string(exp[j]) { + postfix = append(postfix, "π", "*") + j++ + } + + i = j - 1 // i 向前跨越一个整数,由于执行了一步多余的 j++,需要减 1 + + default: + //非操作符继续 拼接 用以支持多字符操作符如sin , cos + if isOp := isOperator(itemStr); !isOp { + if i+1 == expLen || len(itemStr) > 5 { + return postfix, errors.New(fmt.Sprintf("不支持的无效的公式片段 %s", itemStr)) + } + + if unicode.IsDigit(exp[i+1]) { + return postfix, errors.New(fmt.Sprintf("不支持的操作符 %s", itemStr)) + } + + continue + } + // 操作符:遇到高优先级的运算符,不断弹出,直到遇见更低优先级运算符 + for !stack.IsEmpty() { + top := stack.Top() + if (top == "(" || top == "[") || isLower(top, itemStr) { + break + } + postfix = append(postfix, top) + stack.Pop() + } + // 低优先级的运算符入栈 + stack.Push(itemStr) + } + itemStr = "" + } + + // 栈不空则全部输出 + for !stack.IsEmpty() { + postfix = append(postfix, stack.Pop()) + } + + return postfix, nil +} + +// (12.3+0.1)*2.0+2 +// 12.30.1+2.0*2+ +// 后缀式计算 +var operatorMap = []string{"√", "²", "³", "+", "-", "*", "/", "sin", "cos", "^"} + +// 优先级定义 +var operatorPriority = map[string]uint16{ + "+": 0, + "-": 0, + "*": 1, + "/": 1, + "√": 2, + "^": 6, + "sin": 7, + "cos": 7, + "²": 8, + "³": 8, + "[": 9, + "(": 10, +} + +func isOperator(op string) bool { + for _, s := range operatorMap { + if op == s { + return true + } + } + return false +} + +// 比较运算符栈栈顶 top 和新运算符 newTop 的优先级高低 +func isLower(top string, newTop string) bool { + // 注意 a + b + c 的后缀表达式是 ab + c +,不是 abc + + + + Priority := operatorPriority[newTop] > operatorPriority[top] + + return Priority + + //switch top { + //case "+", "-": + // if newTop == "*" || newTop == "/" { + // return true + // } + //case "(": + // return true + //} + //return false +} + +type Stack struct { + data [100]string + top int +} + +func (s *Stack) Push(d string) { + s.data[s.top] = d + s.top++ +} + +func (s *Stack) Pop() (d string) { + if s.top == 0 { + //err = fmt.Errorf("stack is empty") + } + s.top-- + d = s.data[s.top] + return +} + +func (s *Stack) Top() (d string) { + if s.top == 0 { + //err = fmt.Errorf("stack is empty") + } + d = s.data[s.top-1] + return +} + +func (s *Stack) IsEmpty() bool { + return s.top == 0 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e049593 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module gitea.anxinyun.cn/container/common_calc + +go 1.22.0 + +require ( + github.com/google/uuid v1.6.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5697539 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/valueHelper.go b/valueHelper.go new file mode 100644 index 0000000..dab8716 --- /dev/null +++ b/valueHelper.go @@ -0,0 +1,115 @@ +package common_calc + +import ( + "crypto/md5" + "github.com/google/uuid" + "log" + "math" + "sort" + "strconv" +) + +// Decimal float64 保留小数点后位数 +// value float64 浮点数 +// prec int 需保留小数点后的位数 +func Decimal(value float64, prec int) float64 { + value, _ = strconv.ParseFloat(strconv.FormatFloat(value, 'f', prec, 64), 64) + return value +} + +// 和scala 计算不一致 注意! +func UUIDFromString(raw string) string { + bys := []byte(raw) + uid := uuid.NewMD5(uuid.Nil, bys) + log.Println(uid.String(), uid.Version()) + uid = uuid.NewMD5(uuid.NameSpaceURL, bys) + log.Println(uid.String(), uid.Version()) + uid = uuid.NewMD5(uuid.NameSpaceX500, bys) + log.Println(uid.String(), uid.Version()) + uid = uuid.NewMD5(uuid.NameSpaceDNS, bys) + log.Println(uid.String(), uid.Version()) + uid = uuid.NewMD5(uuid.NameSpaceOID, bys) + log.Println(uid.String(), uid.Version()) + + return uid.String() + +} + +func NameUUIDFromString(raw string) string { + sb := md5.Sum([]byte(raw)) + sb[6] &= 0x0f + sb[6] |= 0x30 + sb[8] &= 0x3f + sb[8] |= 0x80 + uid, err := uuid.FromBytes(sb[:]) + if err != nil { + log.Printf("uid 错误异常=%x", err.Error()) + } + return uid.String() + +} + +func MinMax(input []float64) (float64, float64) { + sort.Float64s(input) + return input[0], input[len(input)-1] +} +func AbsMax(input []float64) float64 { + sort.Float64s(input) + return max(math.Abs(input[0]), math.Abs(input[len(input)-1])) +} +func MeanSqrt(data []float64) float64 { + var sumSquares float64 + for _, v := range data { + sumSquares += v * v + } + return math.Sqrt(sumSquares / float64(len(data))) +} + +func GetAvg(dataArray []float64) float64 { + sum := 0.0 + for _, f := range dataArray { + sum += f + } + return sum / float64(len(dataArray)) +} + +func GetMedian(dataArray []float64) float64 { + n := len(dataArray) + sort.Float64s(dataArray) + if n%2 == 1 { + // 如果切片元素个数为奇数,中位数即为中间的元素 + return dataArray[n/2] + } else { + // 如果元素个数为偶数,中位数为中间两个元素的平均值 + return (dataArray[n/2-1] + dataArray[n/2]) / 2.0 + } +} + +// GetVariance calculates the unbiased population variance from the provided samples as an unsorted array. +// On a dataset of size N, it will use an N-1 normalizer (Bessel's correction). +// Returns NaN if data has less than two entries or if any entry is NaN. +// 方差 +// Translated from Math.Net +// Source code at https://github.com/mathnet/mathnet-numerics/blob/master/src/Numerics/Statistics/ArrayStatistics.cs +func GetVariance(samples []float64) float64 { + if len(samples) <= 1 { + return math.NaN() + } + + var variance float64 + t := samples[0] + for i := 1; i < len(samples); i++ { + t += samples[i] + diff := float64((i+1)*int(samples[i])) - t + variance += (diff * diff) / (float64(i+1) * float64(i)) + } + + return variance / float64(len(samples)-1) +} + +func StandardDeviation(samples []float64) float64 { + return math.Sqrt(GetVariance(samples)) +} +func MeanStandardDeviation(samples []float64) (float64, float64) { + return GetAvg(samples), StandardDeviation(samples) +}