added functions to get the fenerating fraction of a decimal number
This commit is contained in:
parent
ab07405cda
commit
f028485caa
112
func-base.go
112
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) {
|
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
if len(args) == 1 {
|
switch v := args[0].(type) {
|
||||||
switch v := args[0].(type) {
|
case int64:
|
||||||
case int64:
|
result = v
|
||||||
result = v
|
case float64:
|
||||||
case float64:
|
result = int64(math.Trunc(v))
|
||||||
result = int64(math.Trunc(v))
|
case bool:
|
||||||
case bool:
|
if v {
|
||||||
if v {
|
result = int64(1)
|
||||||
result = int64(1)
|
} else {
|
||||||
} else {
|
result = int64(0)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
} else {
|
case string:
|
||||||
err = errOneParam(name)
|
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
|
return
|
||||||
}
|
}
|
||||||
@ -102,6 +170,8 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
|
|||||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 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() {
|
func init() {
|
||||||
|
@ -66,6 +66,8 @@ func TestFuncs(t *testing.T) {
|
|||||||
/* 53 */ {`isFract(3|1)`, false, nil},
|
/* 53 */ {`isFract(3|1)`, false, nil},
|
||||||
/* 54 */ {`isRational(3|1)`, true, nil},
|
/* 54 */ {`isRational(3|1)`, true, nil},
|
||||||
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), 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", ".")
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
@ -7,6 +7,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -24,6 +25,134 @@ func newFraction(num, den int64) *fraction {
|
|||||||
return &fraction{num, den}
|
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 {
|
func (f *fraction) toFloat() float64 {
|
||||||
return float64(f.num) / float64(f.den)
|
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) {
|
func sumFract(f1, f2 *fraction) (sum *fraction) {
|
||||||
m := lcm(f1.den, f2.den)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user