From f028485caae5e92a9fe30c63b700c8ce17efb8fa Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Mon, 13 May 2024 14:24:37 +0200 Subject: [PATCH] added functions to get the fenerating fraction of a decimal number --- func-base.go | 112 +++++++++++++++++++++++++++++------- funcs_test.go | 2 + operator-fraction.go | 133 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 224 insertions(+), 23 deletions(-) diff --git a/func-base.go b/func-base.go index 56e9be7..f0859ed 100644 --- a/func-base.go +++ b/func-base.go @@ -59,28 +59,96 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err } func intFunc(ctx ExprContext, name string, args []any) (result any, err error) { - if len(args) == 1 { - switch v := args[0].(type) { - case int64: - result = v - case float64: - result = int64(math.Trunc(v)) - case bool: - if v { - result = int64(1) - } else { - result = int64(0) - } - case string: - var i int - if i, err = strconv.Atoi(v); err == nil { - result = int64(i) - } - default: - err = errCantConvert(name, v, "int") + switch v := args[0].(type) { + case int64: + result = v + case float64: + result = int64(math.Trunc(v)) + case bool: + if v { + result = int64(1) + } else { + result = int64(0) } - } else { - err = errOneParam(name) + case string: + var i int + if i, err = strconv.Atoi(v); err == nil { + result = int64(i) + } + default: + err = errCantConvert(name, v, "int") + } + return +} + +func decFunc(ctx ExprContext, name string, args []any) (result any, err error) { + switch v := args[0].(type) { + case int64: + result = float64(v) + case float64: + result = v + case bool: + if v { + result = float64(1) + } else { + result = float64(0) + } + case string: + var f float64 + if f, err = strconv.ParseFloat(v, 64); err == nil { + result = f + } + case *fraction: + result = v.toFloat() + default: + err = errCantConvert(name, v, "float") + } + return +} + +func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) { + switch v := args[0].(type) { + case int64: + var den int64 = 1 + if len(args) > 1 { + var ok bool + if den, ok = args[1].(int64); !ok { + err = errExpectedGot(name, "integer", args[1]) + } else if den == 0 { + err = errDivisionByZero(name) + } + } + if err == nil { + result = newFraction(v, den) + } + case float64: + result, err = float64ToFraction(v) + // var n, d int64 + // if n, d, err = float64ToFraction(v); err == nil { + // result = newFraction(n, d) + // } + case bool: + if v { + result = newFraction(1, 1) + } else { + result = newFraction(0, 1) + } + case string: + result, err = makeGeneratingFraction(v) + // var f float64 + // // TODO temporary implementation + // if f, err = strconv.ParseFloat(v, 64); err == nil { + // var n, d int64 + // if n, d, err = float64ToFraction(f); err == nil { + // result = newFraction(n, d) + // } + // } else { + // errors.New("convertion from string to float is ongoing") + // } + case *fraction: + result = v + default: + err = errCantConvert(name, v, "float") } return } @@ -102,6 +170,8 @@ func ImportBuiltinsFuncs(ctx ExprContext) { ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1) ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1) ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1) + ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1) + ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2) } func init() { diff --git a/funcs_test.go b/funcs_test.go index 957f046..1c58e3e 100644 --- a/funcs_test.go +++ b/funcs_test.go @@ -66,6 +66,8 @@ func TestFuncs(t *testing.T) { /* 53 */ {`isFract(3|1)`, false, nil}, /* 54 */ {`isRational(3|1)`, true, nil}, /* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil}, + /* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil}, + /* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil}, } t.Setenv("EXPR_PATH", ".") diff --git a/operator-fraction.go b/operator-fraction.go index 86d9357..2e11c0e 100644 --- a/operator-fraction.go +++ b/operator-fraction.go @@ -7,6 +7,7 @@ package expr import ( "errors" "fmt" + "math" "strconv" "strings" ) @@ -24,6 +25,134 @@ func newFraction(num, den int64) *fraction { return &fraction{num, den} } +func float64ToFraction(f float64) (fract *fraction, err error) { + var sign string + intPart, decPart := math.Modf(f) + if decPart < 0.0 { + sign="-" + intPart = -intPart + decPart = -decPart + } + dec := fmt.Sprintf("%.12f", decPart) + s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:]) + // fmt.Printf("S: '%s'\n",s) + return makeGeneratingFraction(s) +} + +// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39 +/* +func _float64ToFraction(f float64) (num, den int64, err error) { + const expMask = 1<<11 - 1 + bits := math.Float64bits(f) + mantissa := bits & (1<<52 - 1) + exp := int((bits >> 52) & expMask) + switch exp { + case expMask: // non-finite + err = errors.New("infite") + return + case 0: // denormal + exp -= 1022 + default: // normal + mantissa |= 1 << 52 + exp -= 1023 + } + + shift := 52 - exp + + // Optimization (?): partially pre-normalise. + for mantissa&1 == 0 && shift > 0 { + mantissa >>= 1 + shift-- + } + + if f < 0 { + num = -int64(mantissa) + } else { + num = int64(mantissa) + } + den = int64(1) + + if shift > 0 { + den = den << shift + } else { + num = num << (-shift) + } + return +} +*/ + +func makeGeneratingFraction(s string) (f *fraction, err error) { + var num, den int64 + var sign int64 = 1 + var parts []string + if len(s) == 0 { + goto exit + } + if s[0] == '-' { + sign=int64(-1) + s = s[1:] + } else if s[0] == '+' { + s = s[1:] + } + parts = strings.SplitN(s, ".", 2) + if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil { + return + } + if len(parts) == 1 { + f = newFraction(sign*num, 1) + } else if len(parts) == 2 { + subParts := strings.SplitN(parts[1], "(", 2) + if len(subParts) == 1 { + den = 1 + dec := parts[1] + lsd := len(dec) + for i:=lsd-1; i>= 0 && dec[i]=='0'; i-- { + lsd-- + } + for _, c := range dec[0:lsd] { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + den = den * 10 + } + f = newFraction(sign*num, den) + } else if len(subParts) == 2 { + sub := num + mul := int64(1) + for _, c := range subParts[0] { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + sub = sub*10 + int64(c-'0') + mul *= 10 + } + if len(subParts) == 2 { + if s[len(s)-1] != ')' { + goto exit + } + p := subParts[1][0 : len(subParts[1])-1] + for _, c := range p { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + den = den*10 + 9 + } + den *= mul + } + num -= sub + f = newFraction(sign*num, den) + } + } +exit: + if f == nil { + err = errors.New("bad syntax") + } + return +} + func (f *fraction) toFloat() float64 { return float64(f.num) / float64(f.den) } @@ -146,12 +275,12 @@ func lcm(a, b int64) (l int64) { func sumFract(f1, f2 *fraction) (sum *fraction) { m := lcm(f1.den, f2.den) - sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m) + sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m) return } func mulFract(f1, f2 *fraction) (prod *fraction) { - prod = newFraction(f1.num * f2.num, f1.den * f2.den) + prod = newFraction(f1.num*f2.num, f1.den*f2.den) return }