added functions to get the fenerating fraction of a decimal number

This commit is contained in:
Celestino Amoroso 2024-05-13 14:24:37 +02:00
parent ab07405cda
commit f028485caa
3 changed files with 224 additions and 23 deletions

View File

@ -59,7 +59,6 @@ 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
@ -79,8 +78,77 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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 {
err = errOneParam(name)
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() {

View File

@ -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", ".")

View File

@ -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)
}