From c870b2d02341ef99e43e6275cfaabf6a26f4befe Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:56:00 +0200 Subject: [PATCH 1/9] feat: rework float definition using decimal gda impl --- engine/float.go | 114 ++++++++++++++++++++++++++++++++++++++++++------ go.mod | 3 ++ go.sum | 6 +++ 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/engine/float.go b/engine/float.go index 0086842..4c3c96b 100644 --- a/engine/float.go +++ b/engine/float.go @@ -1,22 +1,81 @@ package engine import ( + "fmt" + "github.com/cockroachdb/apd" "io" - "strconv" "strings" ) // Float is a prolog floating-point number. -type Float float64 +// The underlying implementation is not based on floating-point, it's a [GDA](https://speleotrove.com/decimal/) +// compatible implementation to avoid approximation and determinism issues. +// It uses under the hood a decimal128 with 34 precision digits. +type Float struct { + dec *apd.Decimal +} + +// The context that must be used for operations on Float. +var decimal128Ctx = apd.Context{ + Precision: 34, + MaxExponent: 6144, + MinExponent: -6143, + Traps: apd.DefaultTraps, +} + +func NewFloatFromString(s string) (Float, error) { + dec, c, err := decimal128Ctx.NewFromString(s) + if err != nil { + return Float{}, decimalConditionAsErr(c) + } + + return Float{dec: dec}, nil +} + +func NewFloatFromInt64(i int64) Float { + var dec apd.Decimal + dec.SetInt64(i) + + return Float{dec: &dec} +} + +func decimalConditionAsErr(flags apd.Condition) error { + e := flags & decimal128Ctx.Traps + if e == 0 { + return exceptionalValueUndefined + } + + for m := apd.Condition(1); m > 0; m <<= 1 { + err := e & m + if err == 0 { + continue + } + + switch err { + case apd.Overflow: + return exceptionalValueFloatOverflow + case apd.Underflow: + return exceptionalValueUnderflow + case apd.Subnormal: + return exceptionalValueUnderflow + case apd.DivisionByZero: + return exceptionalValueZeroDivisor + default: + return exceptionalValueUndefined + } + } + + return exceptionalValueUndefined +} func (f Float) number() {} // WriteTerm outputs the Float to an io.Writer. func (f Float) WriteTerm(w io.Writer, opts *WriteOptions, _ *Env) error { ew := errWriter{w: w} - openClose := opts.left.name == atomMinus && opts.left.specifier.class() == operatorClassPrefix && f > 0 + openClose := opts.left.name == atomMinus && opts.left.specifier.class() == operatorClassPrefix && !f.Negative() - if openClose || (f < 0 && opts.left != operator{}) { + if openClose || (f.Negative() && opts.left != operator{}) { _, _ = ew.Write([]byte(" ")) } @@ -24,7 +83,7 @@ func (f Float) WriteTerm(w io.Writer, opts *WriteOptions, _ *Env) error { _, _ = ew.Write([]byte("(")) } - s := strconv.FormatFloat(float64(f), 'g', -1, 64) + s := fmt.Sprintf("%g", f.dec) if !strings.ContainsRune(s, '.') { if strings.ContainsRune(s, 'e') { s = strings.Replace(s, "e", ".0e", 1) @@ -51,15 +110,44 @@ func (f Float) Compare(t Term, env *Env) int { case Variable: return 1 case Float: - switch { - case f > t: - return 1 - case f < t: - return -1 - default: - return 0 - } + return f.dec.Cmp(t.dec) default: // Integer, Atom, custom atomic terms, Compound. return -1 } } + +func (f Float) String() string { + return fmt.Sprintf("%g", f.dec) +} + +func (f Float) Negative() bool { + return f.dec.Sign() < 0 +} + +func (f Float) Positive() bool { + return f.dec.Sign() > 0 +} + +func (f Float) Zero() bool { + return f.dec.Sign() == 0 +} + +func (f Float) Eq(other Float) bool { + return f.dec.Cmp(other.dec) == 0 +} + +func (f Float) Gt(other Float) bool { + return f.dec.Cmp(other.dec) == 1 +} + +func (f Float) Gte(other Float) bool { + return f.dec.Cmp(other.dec) >= 0 +} + +func (f Float) Lt(other Float) bool { + return f.dec.Cmp(other.dec) == -1 +} + +func (f Float) Lte(other Float) bool { + return f.dec.Cmp(other.dec) <= 0 +} diff --git a/go.mod b/go.mod index 8e9d7ab..26715fb 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ichiban/prolog go 1.19 require ( + github.com/cockroachdb/apd v1.1.0 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 ) @@ -10,6 +11,8 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.1.1 // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect diff --git a/go.sum b/go.sum index faf92ce..a175b59 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -6,6 +8,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From ed8e9d37b46af5875510026c3326b1d3ae8f91d9 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:56:39 +0200 Subject: [PATCH 2/9] feat: allow to parse float --- engine/parser.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/parser.go b/engine/parser.go index 9ca9ddf..efde588 100644 --- a/engine/parser.go +++ b/engine/parser.go @@ -805,11 +805,11 @@ func integer(sign int64, s string) (Integer, error) { } func float(sign float64, s string) (Float, error) { - bf, _, _ := big.ParseFloat(s, 10, 0, big.ToZero) - bf.Mul(big.NewFloat(sign), bf) + if sign < 0 { + s = "-" + s + } - f, _ := bf.Float64() - return Float(f), nil + return NewFloatFromString(s) } var ( From 3b123311ddb55ea05f178e4ddb81d7f999a946c6 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:56:53 +0200 Subject: [PATCH 3/9] feat: prevent to use float as placeholders --- engine/parser.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/parser.go b/engine/parser.go index efde588..bd51e93 100644 --- a/engine/parser.go +++ b/engine/parser.go @@ -70,8 +70,6 @@ func (p *Parser) SetPlaceholder(placeholder Atom, args ...interface{}) error { func (p *Parser) termOf(o reflect.Value) (Term, error) { switch o.Kind() { - case reflect.Float32, reflect.Float64: - return Float(o.Float()), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return Integer(o.Int()), nil case reflect.String: From ad3d119543e103d1bae69426d1832995b6b0c1ee Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 3 Jul 2024 01:05:02 +0200 Subject: [PATCH 4/9] feat: make unification of float work --- engine/env.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/engine/env.go b/engine/env.go index 8583fff..73fe325 100644 --- a/engine/env.go +++ b/engine/env.go @@ -306,6 +306,16 @@ func (e *Env) unify(x, y Term, occursCheck bool) (*Env, bool) { switch y := y.(type) { case Variable: return e.unify(y, x, occursCheck) + case Float: + if x, ok := x.(Float); ok { + return e, y.Eq(x) + } + return e, false + case Integer: + if x, ok := x.(Integer); ok { + return e, y == x + } + return e, false default: return e, x == y } From fe1702f52404859e86382dab21f1b91dcee279b4 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 3 Jul 2024 01:05:51 +0200 Subject: [PATCH 5/9] feat: scan float solution as string --- solutions.go | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/solutions.go b/solutions.go index 691fdaa..d84b08b 100644 --- a/solutions.go +++ b/solutions.go @@ -118,10 +118,6 @@ func convertAssign(dest interface{}, vm *engine.VM, t engine.Term, env *engine.E return convertAssignInt32(d, t, env) case *int64: return convertAssignInt64(d, t, env) - case *float32: - return convertAssignFloat32(d, t, env) - case *float64: - return convertAssignFloat64(d, t, env) case Scanner: return d.Scan(vm, t, env) default: @@ -145,7 +141,11 @@ func convertAssignAny(d *interface{}, vm *engine.VM, t engine.Term, env *engine. *d = int(t) return nil case engine.Float: - *d = float64(t) + var s string + if err := convertAssignString(&s, t, env); err != nil { + return err + } + *d = s return nil case engine.Compound: var s []interface{} @@ -226,26 +226,6 @@ func convertAssignInt64(d *int64, t engine.Term, env *engine.Env) error { } } -func convertAssignFloat32(d *float32, t engine.Term, env *engine.Env) error { - switch t := env.Resolve(t).(type) { - case engine.Float: - *d = float32(t) - return nil - default: - return errConversion - } -} - -func convertAssignFloat64(d *float64, t engine.Term, env *engine.Env) error { - switch t := env.Resolve(t).(type) { - case engine.Float: - *d = float64(t) - return nil - default: - return errConversion - } -} - func convertAssignSlice(d interface{}, vm *engine.VM, t engine.Term, env *engine.Env) error { v := reflect.ValueOf(d).Elem() From c04887686c5a881e97eef56ffa2cb939ced3616f Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 3 Jul 2024 01:07:11 +0200 Subject: [PATCH 6/9] feat: reimplement arithmetic functors trigonometric functions have been removed --- engine/number.go | 468 ++++++++++++++++++----------------------------- 1 file changed, 182 insertions(+), 286 deletions(-) diff --git a/engine/number.go b/engine/number.go index 27de55c..663dd8b 100644 --- a/engine/number.go +++ b/engine/number.go @@ -2,16 +2,26 @@ package engine import ( "errors" + "github.com/cockroachdb/apd" "math" ) var ( - maxInt = Integer(math.MaxInt64) - minInt = Integer(math.MinInt64) + maxInt = Integer(math.MaxInt64) + minInt = Integer(math.MinInt64) + oneFloat = NewFloatFromInt64(1) + minusOneFloat = NewFloatFromInt64(-1) ) var constants = map[Atom]Number{ - atomPi: Float(math.Pi), + atomPi: func() Number { + f, err := NewFloatFromString("3.14159265358979323846264338327950288419716939937510582097494459") + // should not occur + if err != nil { + panic(err) + } + return f + }(), } var unaryFunctors = map[Atom]func(Number) (Number, error){ @@ -25,17 +35,11 @@ var unaryFunctors = map[Atom]func(Number) (Number, error){ atomTruncate: truncate, atomRound: round, atomCeiling: ceiling, - atomSin: sin, - atomCos: cos, - atomAtan: atan, atomExp: exp, atomLog: log, atomSqrt: sqrt, atomBackSlash: bitwiseComplement, atomPlus: pos, - atomAsin: asin, - atomAcos: acos, - atomTan: tan, } var binaryFunctors = map[Atom]func(Number, Number) (Number, error){ @@ -55,7 +59,6 @@ var binaryFunctors = map[Atom]func(Number, Number) (Number, error){ atomMax: max, atomMin: min, atomCaret: integerPower, - atomAtan2: atan2, atomXor: xor, } @@ -472,7 +475,7 @@ func neg(x Number) (Number, error) { case Integer: return negI(x) case Float: - return negF(x), nil + return negF(x) default: return nil, exceptionalValueUndefined } @@ -506,7 +509,7 @@ func sign(x Number) (Number, error) { func floatIntegerPart(x Number) (Number, error) { switch x := x.(type) { case Float: - return intPartF(x), nil + return intPartF(x) default: return nil, typeError(validTypeFloat, x, nil) } @@ -516,7 +519,7 @@ func floatIntegerPart(x Number) (Number, error) { func floatFractionalPart(x Number) (Number, error) { switch x := x.(type) { case Float: - return fractPartF(x), nil + return fractPartF(x) default: return nil, typeError(validTypeFloat, x, nil) } @@ -576,147 +579,94 @@ func ceiling(x Number) (Number, error) { // power returns the base-x exponential of y. func power(x, y Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined + vx, err := numberToFloat(x) + if err != nil { + return nil, err } - var vy float64 - switch y := y.(type) { - case Integer: - vy = float64(y) - case Float: - vy = float64(y) - default: - return nil, exceptionalValueUndefined + vy, err := numberToFloat(y) + if err != nil { + return nil, err } // 9.3.1.3 d) special case - if vx == 0 && vy < 0 { - return nil, exceptionalValueUndefined - } - - r := Float(math.Pow(vx, vy)) - - switch { - case math.IsInf(float64(r), 0): - return nil, exceptionalValueFloatOverflow - case r == 0 && vx != 0: // Underflow: r can be 0 iff x = 0. - return nil, exceptionalValueUnderflow - case math.IsNaN(float64(r)): - return nil, exceptionalValueUndefined - default: - return r, nil - } -} - -// sin returns the sine of x. -func sin(x Number) (Number, error) { - switch x := x.(type) { - case Integer: - return Float(math.Sin(float64(x))), nil - case Float: - return Float(math.Sin(float64(x))), nil - default: + if vx.Zero() && vy.Negative() { return nil, exceptionalValueUndefined } -} -// cos returns the cosine of x. -func cos(x Number) (Number, error) { - switch x := x.(type) { - case Integer: - return Float(math.Cos(float64(x))), nil - case Float: - return Float(math.Cos(float64(x))), nil - default: - return nil, exceptionalValueUndefined + var dec apd.Decimal + c, err := decimal128Ctx.Pow(&dec, vx.dec, vy.dec) + if err != nil { + return nil, decimalConditionAsErr(c) } -} -// atan returns the arctangent of x. -func atan(x Number) (Number, error) { - switch x := x.(type) { - case Integer: - return Float(math.Atan(float64(x))), nil - case Float: - return Float(math.Atan(float64(x))), nil - default: - return nil, exceptionalValueUndefined - } + return Float{dec: &dec}, nil } // exp returns the base-e exponential of x. func exp(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined + f, err := numberToFloat(x) + if err != nil { + return nil, err } - // Positive overflow: - // e^x > max - // log(e^x) > log(max) - // x * log(e) > log(max) - // x > log(max) - if vx > math.Log(math.MaxFloat64) { - return nil, exceptionalValueFloatOverflow + var dec apd.Decimal + c, err := decimal128Ctx.Exp(&dec, f.dec) + if err != nil { + return nil, decimalConditionAsErr(c) } - r := Float(math.Exp(vx)) + return Float{dec: &dec}, nil - if r == 0 { // e^x != 0. - return nil, exceptionalValueUnderflow - } - - return r, nil + //dec := decimal.WithContext(decimal.Context128) + //r := Float{ + // dec: decimal.Context128.Exp(dec, f.dec), + //} + //if !dec.IsFinite() { + // return Float{}, exceptionalValueUnderflow + //} + // + //return r, r.Err() } // log returns the natural logarithm of x. func log(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined + f, err := numberToFloat(x) + if err != nil { + return nil, err } - if vx <= 0 { + if f.Negative() || f.Zero() { return nil, exceptionalValueUndefined } - return Float(math.Log(vx)), nil + var dec apd.Decimal + c, err := decimal128Ctx.Ln(&dec, f.dec) + if err != nil { + return nil, decimalConditionAsErr(c) + } + + return Float{dec: &dec}, nil } // sqrt returns the square root of x. func sqrt(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined + f, err := numberToFloat(x) + if err != nil { + return nil, err } - if vx < 0 { + if f.Negative() { return nil, exceptionalValueUndefined } - return Float(math.Sqrt(vx)), nil + var dec apd.Decimal + c, err := decimal128Ctx.Sqrt(&dec, f.dec) + if err != nil { + return nil, decimalConditionAsErr(c) + } + + return Float{dec: &dec}, nil } // bitwiseRightShift returns n bit-shifted by s to the right. @@ -827,7 +777,7 @@ func max(x, y Number) (Number, error) { } return x, nil case Float: - if floatItoF(x) < y { + if NewFloatFromInt64(int64(x)).Lt(y) { return y, nil } return x, nil @@ -837,12 +787,12 @@ func max(x, y Number) (Number, error) { case Float: switch y := y.(type) { case Integer: - if x < floatItoF(y) { + if x.Lt(NewFloatFromInt64(int64(y))) { return y, nil } return x, nil case Float: - if x < y { + if x.Lt(y) { return y, nil } return x, nil @@ -865,7 +815,7 @@ func min(x, y Number) (Number, error) { } return x, nil case Float: - if floatItoF(x) > y { + if NewFloatFromInt64(int64(x)).Gt(y) { return y, nil } return x, nil @@ -875,12 +825,12 @@ func min(x, y Number) (Number, error) { case Float: switch y := y.(type) { case Integer: - if x > floatItoF(y) { + if x.Gt(NewFloatFromInt64(int64(y))) { return y, nil } return x, nil case Float: - if x > y { + if x.Gt(y) { return y, nil } return x, nil @@ -950,88 +900,6 @@ func intPow(a, b Integer) (Integer, error) { return r, nil } -// asin returns the arc sine of x. -func asin(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined - } - - if vx > 1 || vx < -1 { - return nil, exceptionalValueUndefined - } - - return Float(math.Asin(vx)), nil -} - -// acos returns the arc cosine of x. -func acos(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined - } - - if vx > 1 || vx < -1 { - return nil, exceptionalValueUndefined - } - - return Float(math.Acos(vx)), nil -} - -// atan2 returns the arc tangent of y/x. -func atan2(y, x Number) (Number, error) { - var vy float64 - switch y := y.(type) { - case Integer: - vy = float64(y) - case Float: - vy = float64(y) - default: - return nil, exceptionalValueUndefined - } - - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined - } - - if vx == 0 && vy == 0 { - return nil, exceptionalValueUndefined - } - - return Float(math.Atan2(vy, vx)), nil -} - -// tan returns the tangent of x. -func tan(x Number) (Number, error) { - var vx float64 - switch x := x.(type) { - case Integer: - vx = float64(x) - case Float: - vx = float64(x) - default: - return nil, exceptionalValueUndefined - } - - return Float(math.Tan(vx)), nil -} - // xor returns the bitwise exclusive or of x and y. func xor(x, y Number) (Number, error) { vx, ok := x.(Integer) @@ -1050,7 +918,7 @@ func xor(x, y Number) (Number, error) { // Comparison func eqF(x, y Float) bool { - return x == y + return x.Eq(y) } func eqI(m, n Integer) bool { @@ -1067,7 +935,7 @@ func eqIF(n Integer, y Float) bool { } func neqF(x, y Float) bool { - return x != y + return !x.Eq(y) } func neqI(m, n Integer) bool { @@ -1084,7 +952,7 @@ func neqIF(n Integer, y Float) bool { } func lssF(x, y Float) bool { - return x < y + return x.Lt(y) } func lssI(m, n Integer) bool { @@ -1101,7 +969,7 @@ func lssIF(n Integer, y Float) bool { } func leqF(x, y Float) bool { - return x <= y + return x.Lte(y) } func leqI(m, n Integer) bool { @@ -1118,7 +986,7 @@ func leqIF(n Integer, y Float) bool { } func gtrF(x, y Float) bool { - return x > y + return x.Gt(y) } func gtrI(m, n Integer) bool { @@ -1135,7 +1003,7 @@ func gtrIF(n Integer, y Float) bool { } func geqF(x, y Float) bool { - return x >= y + return x.Gte(y) } func geqI(m, n Integer) bool { @@ -1154,7 +1022,7 @@ func geqIF(n Integer, y Float) bool { // Type conversion operations func floatItoF(n Integer) Float { - return Float(n) + return NewFloatFromInt64(int64(n)) } func floatFtoF(x Float) Float { @@ -1162,35 +1030,55 @@ func floatFtoF(x Float) Float { } func floorFtoI(x Float) (Integer, error) { - f := math.Floor(float64(x)) - if f > float64(maxInt) || f < float64(minInt) { + var dec apd.Decimal + c, err := decimal128Ctx.Floor(&dec, x.dec) + if err != nil { + return 0, decimalConditionAsErr(c) + } + + i64, err := dec.Int64() + if err != nil { return 0, exceptionalValueIntOverflow } - return Integer(f), nil + + return Integer(i64), nil } func truncateFtoI(x Float) (Integer, error) { - t := math.Trunc(float64(x)) - if t > float64(maxInt) || t < float64(minInt) { - return 0, exceptionalValueIntOverflow + if x.Negative() { + return ceilingFtoI(x) } - return Integer(t), nil + return floorFtoI(x) } func roundFtoI(x Float) (Integer, error) { - r := math.Round(float64(x)) - if r > float64(maxInt) || r < float64(minInt) { + var dec apd.Decimal + c, err := decimal128Ctx.RoundToIntegralExact(&dec, x.dec) + if err != nil { + return 0, decimalConditionAsErr(c) + } + + i64, err := dec.Int64() + if err != nil { return 0, exceptionalValueIntOverflow } - return Integer(r), nil + + return Integer(i64), nil } func ceilingFtoI(x Float) (Integer, error) { - c := math.Ceil(float64(x)) - if c > float64(maxInt) || c < float64(minInt) { + var dec apd.Decimal + c, err := decimal128Ctx.Ceil(&dec, x.dec) + if err != nil { + return 0, decimalConditionAsErr(c) + } + + i64, err := dec.Int64() + if err != nil { return 0, exceptionalValueIntOverflow } - return Integer(c), nil + + return Integer(i64), nil } // Integer operations @@ -1308,85 +1196,81 @@ func intFloorDivI(x, y Integer) (Integer, error) { // Float operations func addF(x, y Float) (Float, error) { - switch { - case y > 0 && x > math.MaxFloat64-y: - return 0, exceptionalValueFloatOverflow - case y < 0 && x < -math.MaxFloat64-y: - return 0, exceptionalValueFloatOverflow + var dec apd.Decimal + c, err := decimal128Ctx.Add(&dec, x.dec, y.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) } - return x + y, nil + return Float{dec: &dec}, nil } func subF(x, y Float) (Float, error) { - return addF(x, -y) -} - -func mulF(x, y Float) (Float, error) { - switch { - case y != 0 && x > math.MaxFloat64/y: - return 0, exceptionalValueFloatOverflow - case y != 0 && x < -math.MaxFloat64/y: - return 0, exceptionalValueFloatOverflow + var dec apd.Decimal + c, err := decimal128Ctx.Sub(&dec, x.dec, y.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) } - r := x * y + return Float{dec: &dec}, nil +} - // Underflow: x*y = 0 iff x = 0 or y = 0. - if r == 0 && x != 0 && y != 0 { - return 0, exceptionalValueUnderflow +func mulF(x, y Float) (Float, error) { + var dec apd.Decimal + c, err := decimal128Ctx.Mul(&dec, x.dec, y.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) } - return r, nil + return Float{dec: &dec}, nil } func divF(x, y Float) (Float, error) { - switch { - case y == 0: - return 0, exceptionalValueZeroDivisor - case x > math.MaxFloat64*y: - return 0, exceptionalValueFloatOverflow - case x < -math.MaxFloat64*y: - return 0, exceptionalValueFloatOverflow - } - - r := x / y - - // Underflow: x/y = 0 iff x = 0 and y != 0. - if r == 0 && x != 0 { - return 0, exceptionalValueUnderflow + var dec apd.Decimal + c, err := decimal128Ctx.Quo(&dec, x.dec, y.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) } - return r, nil + return Float{dec: &dec}, nil } -func negF(x Float) Float { - return -x +func negF(x Float) (Float, error) { + return mulF(x, minusOneFloat) } func absF(x Float) Float { - return Float(math.Abs(float64(x))) + var dec apd.Decimal + return Float{dec: dec.Abs(x.dec)} } func signF(x Float) Float { - switch { - case x > 0: - return 1 - case x < 0: - return -1 - default: - return 0 - } + return NewFloatFromInt64(int64(x.dec.Sign())) } -func intPartF(x Float) Float { - s := signF(x) - return s * Float(math.Floor(math.Abs(float64(x)))) +func intPartF(x Float) (Float, error) { + var dec apd.Decimal + c, err := decimal128Ctx.Floor(&dec, x.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) + } + + return Float{dec: &dec}, nil } -func fractPartF(x Float) Float { - i := intPartF(x) - return x - i +func fractPartF(x Float) (Float, error) { + i, err := intPartF(x) + if err != nil { + return Float{}, err + } + + var dec apd.Decimal + c, err := decimal128Ctx.Sub(&dec, x.dec, i.dec) + if err != nil { + return Float{}, decimalConditionAsErr(c) + } + + return Float{dec: &dec}, nil } func posF(x Float) (Float, error) { @@ -1396,37 +1280,49 @@ func posF(x Float) (Float, error) { // Mixed mode operations func addFI(x Float, n Integer) (Float, error) { - return addF(x, Float(n)) + return addF(x, NewFloatFromInt64(int64(n))) } func addIF(n Integer, x Float) (Float, error) { - return addF(Float(n), x) + return addF(NewFloatFromInt64(int64(n)), x) } func subFI(x Float, n Integer) (Float, error) { - return subF(x, Float(n)) + return subF(x, NewFloatFromInt64(int64(n))) } func subIF(n Integer, x Float) (Float, error) { - return subF(Float(n), x) + return subF(NewFloatFromInt64(int64(n)), x) } func mulFI(x Float, n Integer) (Float, error) { - return mulF(x, Float(n)) + return mulF(x, NewFloatFromInt64(int64(n))) } func mulIF(n Integer, x Float) (Float, error) { - return mulF(Float(n), x) + return mulF(NewFloatFromInt64(int64(n)), x) } func divFI(x Float, n Integer) (Float, error) { - return divF(x, Float(n)) + return divF(x, NewFloatFromInt64(int64(n))) } func divIF(n Integer, x Float) (Float, error) { - return divF(Float(n), x) + return divF(NewFloatFromInt64(int64(n)), x) } func divII(n, m Integer) (Float, error) { - return divF(Float(n), Float(m)) + return divF(NewFloatFromInt64(int64(n)), NewFloatFromInt64(int64(m))) +} + +// Utility function to cast a Number to a Float +func numberToFloat(x Number) (Float, error) { + switch x := x.(type) { + case Integer: + return NewFloatFromInt64(int64(x)), nil + case Float: + return x, nil + default: + return Float{}, exceptionalValueUndefined + } } From f4bc73ca691a8e1a38b961a44c58fa94fd4db0bd Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Wed, 3 Jul 2024 01:07:48 +0200 Subject: [PATCH 7/9] test: update all tests related to floats --- engine/atom_test.go | 2 +- engine/builtin_test.go | 50 +++---- engine/compound_test.go | 2 +- engine/float_test.go | 26 ++-- engine/integer_test.go | 2 +- engine/number_test.go | 302 +++++++++++++++++++--------------------- engine/parser_test.go | 52 ++++--- engine/stream_test.go | 2 +- engine/term_test.go | 2 +- engine/variable_test.go | 2 +- interpreter_test.go | 29 +--- solutions_test.go | 26 ++-- 12 files changed, 236 insertions(+), 261 deletions(-) diff --git a/engine/atom_test.go b/engine/atom_test.go index aaf83f8..89819c4 100644 --- a/engine/atom_test.go +++ b/engine/atom_test.go @@ -51,7 +51,7 @@ func TestAtom_Compare(t *testing.T) { o int }{ {title: `a > X`, a: NewAtom("a"), t: x, o: 1}, - {title: `a > 1.0`, a: NewAtom("a"), t: Float(1), o: 1}, + {title: `a > 1.0`, a: NewAtom("a"), t: NewFloatFromInt64(1), o: 1}, {title: `a > 1`, a: NewAtom("a"), t: Integer(1), o: 1}, {title: `a > 'Z'`, a: NewAtom("a"), t: NewAtom("Z"), o: 1}, {title: `a = a`, a: NewAtom("a"), t: NewAtom("a"), o: 0}, diff --git a/engine/builtin_test.go b/engine/builtin_test.go index d3ffee1..970d02a 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -399,7 +399,7 @@ func TestUnify(t *testing.T) { y: NewAtom("def"), }}, {title: `'='(1, 2).`, x: Integer(1), y: Integer(2), ok: false}, - {title: `'='(1, 1.0).`, x: Integer(1), y: Float(1), ok: false}, + {title: `'='(1, 1.0).`, x: Integer(1), y: NewFloatFromInt64(1), ok: false}, {title: `'='(g(X), f(f(X))).`, x: NewAtom("g").Apply(x), y: NewAtom("f").Apply(NewAtom("f").Apply(x)), ok: false}, {title: `'='(f(X, 1), f(a(X))).`, x: NewAtom("f").Apply(x, Integer(1)), y: NewAtom("f").Apply(NewAtom("a").Apply(x)), ok: false}, {title: `'='(f(X, Y, X), f(a(X), a(Y), Y, 2)).`, x: NewAtom("f").Apply(x, y, x), y: NewAtom("f").Apply(NewAtom("a").Apply(x), NewAtom("a").Apply(y), y, Integer(2)), ok: false}, @@ -453,7 +453,7 @@ func TestUnifyWithOccursCheck(t *testing.T) { y: NewAtom("def"), }}, {title: `unify_with_occurs_check(1, 2).`, x: Integer(1), y: Integer(2), ok: false}, - {title: `unify_with_occurs_check(1, 1.0).`, x: Integer(1), y: Float(1), ok: false}, + {title: `unify_with_occurs_check(1, 1.0).`, x: Integer(1), y: NewFloatFromInt64(1), ok: false}, {title: `unify_with_occurs_check(g(X), f(f(X))).`, x: NewAtom("g").Apply(x), y: NewAtom("f").Apply(NewAtom("f").Apply(x)), ok: false}, {title: `unify_with_occurs_check(f(X, 1), f(a(X))).`, x: NewAtom("f").Apply(x, Integer(1)), y: NewAtom("f").Apply(NewAtom("a").Apply(x)), ok: false}, {title: `unify_with_occurs_check(f(X, Y, X), f(a(X), a(Y), Y, 2)).`, x: NewAtom("f").Apply(x, y, x), y: NewAtom("f").Apply(NewAtom("a").Apply(x), NewAtom("a").Apply(y), y, Integer(2)), ok: false}, @@ -515,7 +515,7 @@ func TestTypeVar(t *testing.T) { func TestTypeFloat(t *testing.T) { t.Run("float", func(t *testing.T) { - ok, err := TypeFloat(nil, Float(1.0), Success, nil).Force(context.Background()) + ok, err := TypeFloat(nil, newFloatFromFloat64Must(1.0), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) @@ -632,15 +632,15 @@ func TestFunctor(t *testing.T) { x: Integer(1), y: Integer(0), }}, - {title: `functor(X, 1.1, 0).`, term: x, name: Float(1.1), arity: Integer(0), ok: true, env: map[Variable]Term{ - x: Float(1.1), + {title: `functor(X, 1.1, 0).`, term: x, name: newFloatFromFloat64Must(1.1), arity: Integer(0), ok: true, env: map[Variable]Term{ + x: newFloatFromFloat64Must(1.1), }}, {title: `functor([_|_], '.', 2).`, term: Cons(NewVariable(), NewVariable()), name: atomDot, arity: Integer(2), ok: true}, {title: `functor([], [], 0).`, term: atomEmptyList, name: atomEmptyList, arity: Integer(0), ok: true}, {title: `functor(X, Y, 3).`, term: x, name: y, arity: Integer(3), err: InstantiationError(nil)}, {title: `functor(X, foo, N).`, term: x, name: NewAtom("foo"), arity: n, err: InstantiationError(nil)}, {title: `functor(X, foo, a).`, term: x, name: NewAtom("foo"), arity: NewAtom("a"), err: typeError(validTypeInteger, NewAtom("a"), nil)}, - {title: `functor(F, 1.5, 1).`, term: f, name: Float(1.5), arity: Integer(1), err: typeError(validTypeAtom, Float(1.5), nil)}, + {title: `functor(F, 1.5, 1).`, term: f, name: newFloatFromFloat64Must(1.5), arity: Integer(1), err: typeError(validTypeAtom, newFloatFromFloat64Must(1.5), nil)}, {title: `functor(F, foo(a), 1).`, term: f, name: NewAtom("foo").Apply(NewAtom("a")), arity: Integer(1), err: typeError(validTypeAtomic, NewAtom("foo").Apply(NewAtom("a")), nil)}, // {title: `current_prolog_flag(max_arity, A), X is A + 1, functor(T, foo, X).`} {title: `Minus_1 is 0 - 1, functor(F, foo, Minus_1).`, term: f, name: NewAtom("foo"), arity: Integer(-1), err: domainError(validDomainNotLessThanZero, Integer(-1), nil)}, @@ -782,7 +782,7 @@ func TestUniv(t *testing.T) { {title: "9", term: x, list: PartialList(NewAtom("bar"), NewAtom("foo")), err: typeError(validTypeList, PartialList(NewAtom("bar"), NewAtom("foo")), nil)}, {title: "10", term: x, list: List(foo, NewAtom("bar")), err: InstantiationError(nil)}, {title: "11", term: x, list: List(Integer(3), Integer(1)), err: typeError(validTypeAtom, Integer(3), nil)}, - {title: "12", term: x, list: List(Float(1.1), NewAtom("foo")), err: typeError(validTypeAtom, Float(1.1), nil)}, + {title: "12", term: x, list: List(newFloatFromFloat64Must(1.1), NewAtom("foo")), err: typeError(validTypeAtom, newFloatFromFloat64Must(1.1), nil)}, {title: "13", term: x, list: List(NewAtom("a").Apply(NewAtom("b")), Integer(1)), err: typeError(validTypeAtom, NewAtom("a").Apply(NewAtom("b")), nil)}, {title: "14", term: x, list: Integer(4), err: typeError(validTypeList, Integer(4), nil)}, {title: "15", term: NewAtom("f").Apply(x), list: List(NewAtom("f"), NewAtom("u").Apply(x)), ok: true, env: map[Variable]Term{ @@ -2104,8 +2104,8 @@ func TestCompare(t *testing.T) { order: atomLessThan, }}, {title: `compare(<, <, <).`, order: atomLessThan, x: atomLessThan, y: atomLessThan, ok: false}, - {title: `compare(1+2, 3, 3.0).`, order: atomPlus.Apply(Integer(1), Integer(2)), x: Integer(3), y: Float(3.0), ok: false, err: typeError(validTypeAtom, atomPlus.Apply(Integer(1), Integer(2)), nil)}, - {title: `compare(>=, 3, 3.0).`, order: NewAtom(">="), x: Integer(3), y: Float(3.0), ok: false, err: domainError(validDomainOrder, NewAtom(">="), nil)}, + {title: `compare(1+2, 3, 3.0).`, order: atomPlus.Apply(Integer(1), Integer(2)), x: Integer(3), y: newFloatFromFloat64Must(3.0), ok: false, err: typeError(validTypeAtom, atomPlus.Apply(Integer(1), Integer(2)), nil)}, + {title: `compare(>=, 3, 3.0).`, order: NewAtom(">="), x: Integer(3), y: newFloatFromFloat64Must(3.0), ok: false, err: domainError(validDomainOrder, NewAtom(">="), nil)}, {title: `missing case for >`, order: atomGreaterThan, x: Integer(2), y: Integer(1), ok: true}, } @@ -5523,7 +5523,7 @@ func TestAtomLength(t *testing.T) { }}, {title: "atom_length('scarlet', 5).", atom: NewAtom("scarlet"), length: Integer(5), ok: false}, {title: "atom_length(Atom, 4).", atom: NewVariable(), length: Integer(4), err: InstantiationError(nil)}, - {title: "atom_length(1.23, 4).", atom: Float(1.23), length: Integer(4), err: typeError(validTypeAtom, Float(1.23), nil)}, + {title: "atom_length(1.23, 4).", atom: newFloatFromFloat64Must(1.23), length: Integer(4), err: typeError(validTypeAtom, newFloatFromFloat64Must(1.23), nil)}, {title: "atom_length(atom, '4').", atom: NewAtom("atom"), length: NewAtom("4"), err: typeError(validTypeInteger, NewAtom("4"), nil)}, // 8.16.1.3 Errors @@ -5868,7 +5868,7 @@ func TestNumberChars(t *testing.T) { t.Run("chars is a partial list", func(t *testing.T) { chars := NewVariable() - ok, err := NumberChars(nil, Float(23.4), chars, func(env *Env) *Promise { + ok, err := NumberChars(nil, newFloatFromFloat64Must(23.4), chars, func(env *Env) *Promise { assert.Equal(t, List(NewAtom("2"), NewAtom("3"), atomDot, NewAtom("4")), env.Resolve(chars)) return Bool(true) }, nil).Force(context.Background()) @@ -5879,7 +5879,7 @@ func TestNumberChars(t *testing.T) { t.Run("chars is a list with variables", func(t *testing.T) { char := NewVariable() - ok, err := NumberChars(nil, Float(23.4), List(char, NewAtom("3"), atomDot, NewAtom("4")), func(env *Env) *Promise { + ok, err := NumberChars(nil, newFloatFromFloat64Must(23.4), List(char, NewAtom("3"), atomDot, NewAtom("4")), func(env *Env) *Promise { assert.Equal(t, NewAtom("2"), env.Resolve(char)) return Bool(true) }, nil).Force(context.Background()) @@ -5892,7 +5892,7 @@ func TestNumberChars(t *testing.T) { num := NewVariable() ok, err := NumberChars(nil, num, List(NewAtom("2"), NewAtom("3"), atomDot, NewAtom("4")), func(env *Env) *Promise { - assert.Equal(t, Float(23.4), env.Resolve(num)) + assert.Equal(t, newFloatFromFloat64Must(23.4), env.Resolve(num)) return Bool(true) }, nil).Force(context.Background()) assert.NoError(t, err) @@ -5901,13 +5901,13 @@ func TestNumberChars(t *testing.T) { t.Run("both provided", func(t *testing.T) { t.Run("3.3", func(t *testing.T) { - ok, err := NumberChars(nil, Float(3.3), List(NewAtom("3"), atomDot, NewAtom("3")), Success, nil).Force(context.Background()) + ok, err := NumberChars(nil, newFloatFromFloat64Must(3.3), List(NewAtom("3"), atomDot, NewAtom("3")), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) t.Run("3.3E+0", func(t *testing.T) { - ok, err := NumberChars(nil, Float(3.3), List(NewAtom("3"), atomDot, NewAtom("3"), NewAtom("E"), atomPlus, NewAtom("0")), Success, nil).Force(context.Background()) + ok, err := NumberChars(nil, newFloatFromFloat64Must(3.3), List(NewAtom("3"), atomDot, NewAtom("3"), NewAtom("E"), atomPlus, NewAtom("0")), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) @@ -6017,10 +6017,10 @@ func TestNumberCodes(t *testing.T) { l: List(Integer('3'), Integer('3')), }}, {title: "number_codes(33, [0'3, 0'3]).", number: Integer(33), list: List(Integer('3'), Integer('3')), ok: true}, - {title: "number_codes(33.0, L).", number: Float(33.0), list: l, ok: true, env: map[Variable]Term{ + {title: "number_codes(33.0, L).", number: newFloatFromFloat64Must(33.0), list: l, ok: true, env: map[Variable]Term{ l: List(Integer('3'), Integer('3'), Integer('.'), Integer('0')), }}, - {title: "number_codes(33.0, [0'3, 0'., 0'3, 0'E, 0'+, 0'0, 0'1]).", number: Float(33.0), list: List(Integer('3'), Integer('.'), Integer('3'), Integer('E'), Integer('+'), Integer('0'), Integer('1')), ok: true}, + {title: "number_codes(33.0, [0'3, 0'., 0'3, 0'E, 0'+, 0'0, 0'1]).", number: newFloatFromFloat64Must(33.0), list: List(Integer('3'), Integer('.'), Integer('3'), Integer('E'), Integer('+'), Integer('0'), Integer('1')), ok: true}, {title: "number_codes(A, [0'-, 0'2, 0'5]).", number: a, list: List(Integer('-'), Integer('2'), Integer('5')), ok: true, env: map[Variable]Term{ a: Integer(-25), }}, @@ -6034,10 +6034,10 @@ func TestNumberCodes(t *testing.T) { a: Integer('a'), }}, {title: "number_codes(A, [0'4, 0'., 0'2]).", number: a, list: List(Integer('4'), Integer('.'), Integer('2')), ok: true, env: map[Variable]Term{ - a: Float(4.2), + a: newFloatFromFloat64Must(4.2), }}, {title: "number_codes(A, [0'4, 0'2, 0'., 0'0, 0'e, 0'-, 0'1]).", number: a, list: List(Integer('4'), Integer('2'), Integer('.'), Integer('0'), Integer('e'), Integer('-'), Integer('1')), ok: true, env: map[Variable]Term{ - a: Float(4.2), + a: newFloatFromFloat64Must(4.2), }}, // 8.16.8.3 Errors @@ -7162,8 +7162,8 @@ func TestSucc(t *testing.T) { }) t.Run("s is neither a variable nor an integer", func(t *testing.T) { - _, err := Succ(nil, NewVariable(), Float(1), Success, nil).Force(context.Background()) - assert.Equal(t, typeError(validTypeInteger, Float(1), nil), err) + _, err := Succ(nil, NewVariable(), NewFloatFromInt64(1), Success, nil).Force(context.Background()) + assert.Equal(t, typeError(validTypeInteger, NewFloatFromInt64(1), nil), err) }) }) @@ -7185,8 +7185,8 @@ func TestSucc(t *testing.T) { }) t.Run("s is neither a variable nor an integer", func(t *testing.T) { - _, err := Succ(nil, Integer(0), Float(1), Success, nil).Force(context.Background()) - assert.Equal(t, typeError(validTypeInteger, Float(1), nil), err) + _, err := Succ(nil, Integer(0), NewFloatFromInt64(1), Success, nil).Force(context.Background()) + assert.Equal(t, typeError(validTypeInteger, NewFloatFromInt64(1), nil), err) }) t.Run("x is negative", func(t *testing.T) { @@ -7206,8 +7206,8 @@ func TestSucc(t *testing.T) { }) t.Run("x is neither a variable nor an integer", func(t *testing.T) { - _, err := Succ(nil, Float(0), NewVariable(), Success, nil).Force(context.Background()) - assert.Equal(t, typeError(validTypeInteger, Float(0), nil), err) + _, err := Succ(nil, newFloatFromFloat64Must(0), NewVariable(), Success, nil).Force(context.Background()) + assert.Equal(t, typeError(validTypeInteger, newFloatFromFloat64Must(0), nil), err) }) } diff --git a/engine/compound_test.go b/engine/compound_test.go index a8c57ee..d883104 100644 --- a/engine/compound_test.go +++ b/engine/compound_test.go @@ -73,7 +73,7 @@ func TestCompareCompound(t *testing.T) { o int }{ {title: `f(a) > X`, x: NewAtom("f").Apply(NewAtom("a")), y: x, o: 1}, - {title: `f(a) > 1.0`, x: NewAtom("f").Apply(NewAtom("a")), y: Float(1), o: 1}, + {title: `f(a) > 1.0`, x: NewAtom("f").Apply(NewAtom("a")), y: NewFloatFromInt64(1), o: 1}, {title: `f(a) > 1`, x: NewAtom("f").Apply(NewAtom("a")), y: Integer(1), o: 1}, {title: `f(a) > a`, x: NewAtom("f").Apply(NewAtom("a")), y: NewAtom("a"), o: 1}, {title: `f(a) > f('Z')`, x: NewAtom("f").Apply(NewAtom("a")), y: NewAtom("f").Apply(NewAtom("Z")), o: 1}, {title: `f(a) > e(a)`, x: NewAtom("f").Apply(NewAtom("a")), y: NewAtom("e").Apply(NewAtom("a")), o: 1}, diff --git a/engine/float_test.go b/engine/float_test.go index ada2c18..08a7ad6 100644 --- a/engine/float_test.go +++ b/engine/float_test.go @@ -7,7 +7,7 @@ import ( ) func TestFloatNumber(t *testing.T) { - assert.Implements(t, (*Number)(nil), Float(0)) + assert.Implements(t, (*Number)(nil), NewFloatFromInt64(0)) } func TestFloat_WriteTerm(t *testing.T) { @@ -17,11 +17,11 @@ func TestFloat_WriteTerm(t *testing.T) { opts WriteOptions output string }{ - {title: "positive", f: 33.0, output: `33.0`}, - {title: "with e", f: 3.0e+100, output: `3.0e+100`}, - {title: "positive following unary minus", f: 33.0, opts: WriteOptions{left: operator{specifier: operatorSpecifierFX, name: atomMinus}}, output: ` (33.0)`}, - {title: "negative", f: -33.0, output: `-33.0`}, - {title: "ambiguous e", f: 33.0, opts: WriteOptions{right: operator{name: NewAtom(`e`)}}, output: `33.0 `}, // So that it won't be 33.0e. + {title: "positive", f: newFloatFromFloat64Must(33.0), output: `33.0`}, + {title: "with e", f: newFloatFromFloat64Must(3.0e+100), output: `3.0e+100`}, + {title: "positive following unary minus", f: newFloatFromFloat64Must(33.0), opts: WriteOptions{left: operator{specifier: operatorSpecifierFX, name: atomMinus}}, output: ` (33.0)`}, + {title: "negative", f: newFloatFromFloat64Must(-33.0), output: `-33.0`}, + {title: "ambiguous e", f: newFloatFromFloat64Must(33.0), opts: WriteOptions{right: operator{name: NewAtom(`e`)}}, output: `33.0 `}, // So that it won't be 33.0e. } var buf bytes.Buffer @@ -43,13 +43,13 @@ func TestFloat_Compare(t *testing.T) { t Term o int }{ - {title: `1.0 > X`, f: Float(1), t: x, o: 1}, - {title: `1.0 > 0.0`, f: Float(1), t: Float(0), o: 1}, - {title: `1.0 = 1.0`, f: Float(1), t: Float(1), o: 0}, - {title: `1.0 < 2.0`, f: Float(1), t: Float(2), o: -1}, - {title: `1.0 < 1`, f: Float(1), t: Integer(1), o: -1}, - {title: `1.0 < a`, f: Float(1), t: NewAtom("a"), o: -1}, - {title: `1.0 < f(a)`, f: Float(1), t: NewAtom("f").Apply(NewAtom("a")), o: -1}, + {title: `1.0 > X`, f: NewFloatFromInt64(1), t: x, o: 1}, + {title: `1.0 > 0.0`, f: NewFloatFromInt64(1), t: NewFloatFromInt64(0), o: 1}, + {title: `1.0 = 1.0`, f: NewFloatFromInt64(1), t: NewFloatFromInt64(1), o: 0}, + {title: `1.0 < 2.0`, f: NewFloatFromInt64(1), t: NewFloatFromInt64(2), o: -1}, + {title: `1.0 < 1`, f: NewFloatFromInt64(1), t: Integer(1), o: -1}, + {title: `1.0 < a`, f: NewFloatFromInt64(1), t: NewAtom("a"), o: -1}, + {title: `1.0 < f(a)`, f: NewFloatFromInt64(1), t: NewAtom("f").Apply(NewAtom("a")), o: -1}, } for _, tt := range tests { diff --git a/engine/integer_test.go b/engine/integer_test.go index 3ce0c31..97dd621 100644 --- a/engine/integer_test.go +++ b/engine/integer_test.go @@ -46,7 +46,7 @@ func TestInteger_Compare(t *testing.T) { o int }{ {title: `1 > X`, i: 1, t: x, o: 1}, - {title: `1 > 1.0`, i: 1, t: Float(1), o: 1}, + {title: `1 > 1.0`, i: 1, t: NewFloatFromInt64(1), o: 1}, {title: `1 > 0`, i: 1, t: Integer(0), o: 1}, {title: `1 = 1`, i: 1, t: Integer(1), o: 0}, {title: `1 < 2`, i: 1, t: Integer(2), o: -1}, diff --git a/engine/number_test.go b/engine/number_test.go index 248c0ea..46f025d 100644 --- a/engine/number_test.go +++ b/engine/number_test.go @@ -2,6 +2,7 @@ package engine import ( "context" + "github.com/cockroachdb/apd" "io" "math" "testing" @@ -10,9 +11,37 @@ import ( "github.com/stretchr/testify/mock" ) +func newFloatFromFloat64Must(f float64) Float { + var dec apd.Decimal + _, err := dec.SetFloat64(f) + if err != nil { + panic(err) + } + + return Float{dec: &dec} +} + +func newFloatFromStringMust(s string) Float { + f, err := NewFloatFromString(s) + if err != nil { + panic(err) + } + + return f +} + +func (f Float) mulMust(other Float) Float { + r, err := mulF(f, other) + if err != nil { + panic(err) + } + + return r +} + func TestIs(t *testing.T) { foo := NewAtom("foo") - + pi := newFloatFromStringMust("3.14159265358979323846264338327950288419716939937510582097494459") tests := []struct { title string result, expression Term @@ -20,25 +49,23 @@ func TestIs(t *testing.T) { err error }{ {title: "integer", result: Integer(1), expression: Integer(1), ok: true}, - {title: "float", result: Float(1), expression: Float(1), ok: true}, + {title: "float", result: NewFloatFromInt64(1), expression: NewFloatFromInt64(1), ok: true}, - {title: "pi", result: Float(math.Pi), expression: atomPi, ok: true}, + {title: "pi", result: pi, expression: atomPi, ok: true}, {title: "1 + 1", result: Integer(2), expression: atomPlus.Apply(Integer(1), Integer(1)), ok: true}, {title: "maxInt + 1", expression: atomPlus.Apply(Integer(math.MaxInt64), Integer(1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "minInt - 1", expression: atomPlus.Apply(Integer(math.MinInt64), Integer(-1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1 + 1.0", result: Float(2), expression: atomPlus.Apply(Integer(1), Float(1)), ok: true}, - {title: "1.0 + 1", result: Float(2), expression: atomPlus.Apply(Float(1), Integer(1)), ok: true}, - {title: "1.0 + maxFloat", expression: atomPlus.Apply(Float(1), Float(math.MaxFloat64)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "-1.0 + -maxFloat", expression: atomPlus.Apply(Float(-1), Float(-math.MaxFloat64)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "1 + 1.0", result: NewFloatFromInt64(2), expression: atomPlus.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, + {title: "1.0 + 1", result: NewFloatFromInt64(2), expression: atomPlus.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, {title: "mock + mock", expression: atomPlus.Apply(&mockNumber{}, &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "1 - 1", result: Integer(0), expression: atomMinus.Apply(Integer(1), Integer(1)), ok: true}, {title: "maxInt - -1", expression: atomMinus.Apply(Integer(math.MaxInt64), Integer(-1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "minInt - 1", expression: atomMinus.Apply(Integer(math.MinInt64), Integer(1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1 - 1.0", result: Float(0), expression: atomMinus.Apply(Integer(1), Float(1)), ok: true}, - {title: "1.0 - 1", result: Float(0), expression: atomMinus.Apply(Float(1), Integer(1)), ok: true}, - {title: "1.0 - 1.0", result: Float(0), expression: atomMinus.Apply(Float(1), Float(1)), ok: true}, + {title: "1 - 1.0", result: NewFloatFromInt64(0), expression: atomMinus.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, + {title: "1.0 - 1", result: NewFloatFromInt64(0), expression: atomMinus.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "1.0 - 1.0", result: NewFloatFromInt64(0), expression: atomMinus.Apply(NewFloatFromInt64(1), NewFloatFromInt64(1)), ok: true}, {title: "mock - mock", expression: atomMinus.Apply(&mockNumber{}, &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "1 * 1", result: Integer(1), expression: atomAsterisk.Apply(Integer(1), Integer(1)), ok: true}, @@ -46,96 +73,96 @@ func TestIs(t *testing.T) { {title: "1 * 0", result: Integer(0), expression: atomAsterisk.Apply(Integer(1), Integer(0)), ok: true}, {title: "-1 * minInt", expression: atomAsterisk.Apply(Integer(-1), Integer(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "minInt * -1", expression: atomAsterisk.Apply(Integer(math.MinInt64), Integer(-1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1 * 1.0", result: Float(1), expression: atomAsterisk.Apply(Integer(1), Float(1)), ok: true}, - {title: "1.0 * 1", result: Float(1), expression: atomAsterisk.Apply(Float(1), Integer(1)), ok: true}, - {title: "0.5 * ε", expression: atomAsterisk.Apply(Float(0.5), Float(math.SmallestNonzeroFloat64)), err: evaluationError(exceptionalValueUnderflow, nil)}, - {title: "maxFloat * 2", expression: atomAsterisk.Apply(Float(math.MaxFloat64), Integer(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "-maxFloat * 2", expression: atomAsterisk.Apply(Float(-math.MaxFloat64), Integer(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "1 * 1.0", result: NewFloatFromInt64(1), expression: atomAsterisk.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, + {title: "1.0 * 1", result: NewFloatFromInt64(1), expression: atomAsterisk.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "0.5 * ε", expression: atomAsterisk.Apply(newFloatFromFloat64Must(0.5), newFloatFromStringMust("1e-6143")), err: evaluationError(exceptionalValueUnderflow, nil)}, + {title: "maxFloat * 2", expression: atomAsterisk.Apply(newFloatFromStringMust("9e+6144"), Integer(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "-maxFloat * 2", expression: atomAsterisk.Apply(newFloatFromStringMust("-9e+6144"), Integer(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, {title: "mock * mock", expression: atomAsterisk.Apply(&mockNumber{}, &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "1 // 1", result: Integer(1), expression: atomSlashSlash.Apply(Integer(1), Integer(1)), ok: true}, {title: "1 // 0", expression: atomSlashSlash.Apply(Integer(1), Integer(0)), err: evaluationError(exceptionalValueZeroDivisor, nil)}, {title: "minInt // -1", expression: atomSlashSlash.Apply(Integer(math.MinInt64), Integer(-1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1.0 // 1", expression: atomSlashSlash.Apply(Float(1), Integer(1)), err: typeError(validTypeInteger, Float(1), nil)}, - {title: "1 // 1.0", expression: atomSlashSlash.Apply(Integer(1), Float(1)), err: typeError(validTypeInteger, Float(1), nil)}, + {title: "1.0 // 1", expression: atomSlashSlash.Apply(NewFloatFromInt64(1), Integer(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, + {title: "1 // 1.0", expression: atomSlashSlash.Apply(Integer(1), NewFloatFromInt64(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, - {title: "1 / 1", result: Float(1), expression: atomSlash.Apply(Integer(1), Integer(1)), ok: true}, - {title: "1.0 / 1", result: Float(1), expression: atomSlash.Apply(Float(1), Integer(1)), ok: true}, - {title: "1 / 1.0", result: Float(1), expression: atomSlash.Apply(Integer(1), Float(1)), ok: true}, + {title: "1 / 1", result: NewFloatFromInt64(1), expression: atomSlash.Apply(Integer(1), Integer(1)), ok: true}, + {title: "1.0 / 1", result: NewFloatFromInt64(1), expression: atomSlash.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "1 / 1.0", result: NewFloatFromInt64(1), expression: atomSlash.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, {title: "1 / 0", expression: atomSlash.Apply(Integer(1), Integer(0)), err: evaluationError(exceptionalValueZeroDivisor, nil)}, - {title: "maxFloat / 0.5", expression: atomSlash.Apply(Float(math.MaxFloat64), Float(0.5)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "-maxFloat / 0.5", expression: atomSlash.Apply(Float(-math.MaxFloat64), Float(0.5)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "ε / 2.0", expression: atomSlash.Apply(Float(math.SmallestNonzeroFloat64), Float(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, + {title: "maxFloat / 0.5", expression: atomSlash.Apply(newFloatFromStringMust("9e+6144"), newFloatFromFloat64Must(0.5)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "-maxFloat / 0.5", expression: atomSlash.Apply(newFloatFromStringMust("-9e+6144"), newFloatFromFloat64Must(0.5)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "ε / 2.0", expression: atomSlash.Apply(newFloatFromStringMust("1e-6143"), newFloatFromFloat64Must(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, {title: "1 div mock", expression: atomSlash.Apply(Integer(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "mock div 1", expression: atomSlash.Apply(&mockNumber{}, Integer(1)), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "1 rem 1", result: Integer(0), expression: atomRem.Apply(Integer(1), Integer(1)), ok: true}, {title: "1 rem 0", expression: atomRem.Apply(Integer(1), Integer(0)), err: evaluationError(exceptionalValueZeroDivisor, nil)}, - {title: "1.0 rem 1", expression: atomRem.Apply(Float(1), Integer(1)), err: typeError(validTypeInteger, Float(1), nil)}, - {title: "1 rem 1.0", expression: atomRem.Apply(Integer(1), Float(1)), err: typeError(validTypeInteger, Float(1), nil)}, + {title: "1.0 rem 1", expression: atomRem.Apply(NewFloatFromInt64(1), Integer(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, + {title: "1 rem 1.0", expression: atomRem.Apply(Integer(1), NewFloatFromInt64(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, {title: "1 mod 1", result: Integer(0), expression: atomMod.Apply(Integer(1), Integer(1)), ok: true}, {title: "1 mod 0", expression: atomMod.Apply(Integer(1), Integer(0)), err: evaluationError(exceptionalValueZeroDivisor, nil)}, - {title: "1.0 mod 1", expression: atomMod.Apply(Float(1), Integer(1)), err: typeError(validTypeInteger, Float(1), nil)}, - {title: "1 mod 1.0", expression: atomMod.Apply(Integer(1), Float(1)), err: typeError(validTypeInteger, Float(1), nil)}, + {title: "1.0 mod 1", expression: atomMod.Apply(NewFloatFromInt64(1), Integer(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, + {title: "1 mod 1.0", expression: atomMod.Apply(Integer(1), NewFloatFromInt64(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, {title: "- 1", result: Integer(-1), expression: atomMinus.Apply(Integer(1)), ok: true}, - {title: "- 1.0", result: Float(-1), expression: atomMinus.Apply(Float(1)), ok: true}, + {title: "- 1.0", result: NewFloatFromInt64(-1), expression: atomMinus.Apply(NewFloatFromInt64(1)), ok: true}, {title: "- minInt", expression: atomMinus.Apply(Integer(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "- mock", expression: atomMinus.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "abs(1)", result: Integer(1), expression: atomAbs.Apply(Integer(1)), ok: true}, {title: "abs(-1)", result: Integer(1), expression: atomAbs.Apply(Integer(-1)), ok: true}, - {title: "abs(-1.0)", result: Float(1), expression: atomAbs.Apply(Float(-1)), ok: true}, + {title: "abs(-1.0)", result: NewFloatFromInt64(1), expression: atomAbs.Apply(NewFloatFromInt64(-1)), ok: true}, {title: "abs(minInt)", expression: atomAbs.Apply(Integer(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "abs(mock)", expression: atomAbs.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "sign(5)", result: Integer(1), expression: atomSign.Apply(Integer(5)), ok: true}, {title: "sign(0)", result: Integer(0), expression: atomSign.Apply(Integer(0)), ok: true}, {title: "sign(-5)", result: Integer(-1), expression: atomSign.Apply(Integer(-5)), ok: true}, - {title: "sign(5.0)", result: Float(1), expression: atomSign.Apply(Float(5)), ok: true}, - {title: "sign(0.0)", result: Float(0), expression: atomSign.Apply(Float(0)), ok: true}, - {title: "sign(-5.0)", result: Float(-1), expression: atomSign.Apply(Float(-5)), ok: true}, + {title: "sign(5.0)", result: NewFloatFromInt64(1), expression: atomSign.Apply(NewFloatFromInt64(5)), ok: true}, + {title: "sign(0.0)", result: NewFloatFromInt64(0), expression: atomSign.Apply(NewFloatFromInt64(0)), ok: true}, + {title: "sign(-5.0)", result: NewFloatFromInt64(-1), expression: atomSign.Apply(NewFloatFromInt64(-5)), ok: true}, {title: "sign(mock)", expression: atomSign.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "float_integer_part(1.23)", result: Float(1), expression: atomFloatIntegerPart.Apply(Float(1.23)), ok: true}, + {title: "float_integer_part(1.23)", result: NewFloatFromInt64(1), expression: atomFloatIntegerPart.Apply(newFloatFromFloat64Must(1.23)), ok: true}, {title: "float_integer_part(1)", expression: atomFloatIntegerPart.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, - {title: "float_fractional_part(1.23)", result: Float(0.22999999999999998), expression: atomFloatFractionalPart.Apply(Float(1.23)), ok: true}, + {title: "float_fractional_part(1.23)", result: newFloatFromFloat64Must(0.23), expression: atomFloatFractionalPart.Apply(newFloatFromFloat64Must(1.23)), ok: true}, {title: "float_fractional_part(1)", expression: atomFloatFractionalPart.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, - {title: "float(1)", result: Float(1), expression: atomFloat.Apply(Integer(1)), ok: true}, - {title: "float(1.0)", result: Float(1), expression: atomFloat.Apply(Float(1)), ok: true}, + {title: "float(1)", result: NewFloatFromInt64(1), expression: atomFloat.Apply(Integer(1)), ok: true}, + {title: "float(1.0)", result: NewFloatFromInt64(1), expression: atomFloat.Apply(NewFloatFromInt64(1)), ok: true}, {title: "float(mock)", expression: atomFloat.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "floor(1.9)", result: Integer(1), expression: atomFloor.Apply(Float(1.9)), ok: true}, - {title: "floor(2.0 * maxInt)", expression: atomFloor.Apply(2 * Float(math.MaxInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "floor(2.0 * minInt)", expression: atomFloor.Apply(2 * Float(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "floor(1.9)", result: Integer(1), expression: atomFloor.Apply(newFloatFromFloat64Must(1.9)), ok: true}, + {title: "floor(2.0 * maxInt)", expression: atomFloor.Apply(NewFloatFromInt64(math.MaxInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "floor(2.0 * minInt)", expression: atomFloor.Apply(NewFloatFromInt64(math.MinInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "floor(1)", expression: atomFloor.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, - {title: "truncate(1.9)", result: Integer(1), expression: atomTruncate.Apply(Float(1.9)), ok: true}, - {title: "truncate(2.0 * maxInt)", expression: atomTruncate.Apply(2 * Float(math.MaxInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "truncate(2.0 * minInt)", expression: atomTruncate.Apply(2 * Float(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "truncate(1.9)", result: Integer(1), expression: atomTruncate.Apply(newFloatFromFloat64Must(1.9)), ok: true}, + {title: "truncate(2.0 * maxInt)", expression: atomTruncate.Apply(NewFloatFromInt64(math.MaxInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "truncate(2.0 * minInt)", expression: atomTruncate.Apply(NewFloatFromInt64(math.MinInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "truncate(1)", expression: atomTruncate.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, - {title: "round(1.9)", result: Integer(2), expression: atomRound.Apply(Float(1.9)), ok: true}, - {title: "round(2.0 * maxInt)", expression: atomRound.Apply(2 * Float(math.MaxInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "round(2.0 * minInt)", expression: atomRound.Apply(2 * Float(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "round(1.9)", result: Integer(2), expression: atomRound.Apply(newFloatFromFloat64Must(1.9)), ok: true}, + {title: "round(2.0 * maxInt)", expression: atomRound.Apply(NewFloatFromInt64(math.MaxInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "round(2.0 * minInt)", expression: atomRound.Apply(NewFloatFromInt64(math.MinInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "round(1)", expression: atomRound.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, - {title: "ceiling(1.9)", result: Integer(2), expression: atomCeiling.Apply(Float(1.9)), ok: true}, - {title: "ceiling(2.0 * maxInt)", expression: atomCeiling.Apply(2 * Float(math.MaxInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "ceiling(2.0 * minInt)", expression: atomCeiling.Apply(2 * Float(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "ceiling(1.9)", result: Integer(2), expression: atomCeiling.Apply(newFloatFromFloat64Must(1.9)), ok: true}, + {title: "ceiling(2.0 * maxInt)", expression: atomCeiling.Apply(NewFloatFromInt64(math.MaxInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, + {title: "ceiling(2.0 * minInt)", expression: atomCeiling.Apply(NewFloatFromInt64(math.MinInt64).mulMust(NewFloatFromInt64(2))), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "ceiling(1)", expression: atomCeiling.Apply(Integer(1)), err: typeError(validTypeFloat, Integer(1), nil)}, {title: "1 div 1", result: Integer(1), expression: atomDiv.Apply(Integer(1), Integer(1)), ok: true}, {title: "1 div 0", expression: atomDiv.Apply(Integer(1), Integer(0)), err: evaluationError(exceptionalValueZeroDivisor, nil)}, {title: "minInt div -1", expression: atomDiv.Apply(Integer(math.MinInt64), Integer(-1)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1.0 div 1", expression: atomDiv.Apply(Float(1), Integer(1)), err: typeError(validTypeInteger, Float(1), nil)}, - {title: "1 div 1.0", expression: atomDiv.Apply(Integer(1), Float(1)), err: typeError(validTypeInteger, Float(1), nil)}, + {title: "1.0 div 1", expression: atomDiv.Apply(NewFloatFromInt64(1), Integer(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, + {title: "1 div 1.0", expression: atomDiv.Apply(Integer(1), NewFloatFromInt64(1)), err: typeError(validTypeInteger, NewFloatFromInt64(1), nil)}, {title: "+ 1", result: Integer(1), expression: atomPlus.Apply(Integer(1)), ok: true}, - {title: "+ 1.0", result: Float(1), expression: atomPlus.Apply(Float(1)), ok: true}, + {title: "+ 1.0", result: NewFloatFromInt64(1), expression: atomPlus.Apply(NewFloatFromInt64(1)), ok: true}, {title: "+ mock", expression: atomPlus.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "invalid unary argument", expression: atomMinus.Apply(&mockTerm{}), err: typeError(validTypeEvaluable, atomSlash.Apply(&mockTerm{}, Integer(0)), nil)}, @@ -149,66 +176,54 @@ func TestIs(t *testing.T) { // 8.6.1.3 Errors {title: "a", result: NewVariable(), expression: NewVariable(), err: InstantiationError(nil)}, - {title: "1 ** 1", result: Float(1), expression: atomAsteriskAsterisk.Apply(Integer(1), Integer(1)), ok: true}, - {title: "1 ** 1.0", result: Float(1), expression: atomAsteriskAsterisk.Apply(Integer(1), Float(1)), ok: true}, - {title: "1.0 ** 1", result: Float(1), expression: atomAsteriskAsterisk.Apply(Float(1), Integer(1)), ok: true}, + {title: "1 ** 1", result: NewFloatFromInt64(1), expression: atomAsteriskAsterisk.Apply(Integer(1), Integer(1)), ok: true}, + {title: "1 ** 1.0", result: NewFloatFromInt64(1), expression: atomAsteriskAsterisk.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, + {title: "1.0 ** 1", result: NewFloatFromInt64(1), expression: atomAsteriskAsterisk.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, {title: "1 ** mock", expression: atomAsteriskAsterisk.Apply(Integer(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "mock ** 1", expression: atomAsteriskAsterisk.Apply(&mockNumber{}, Integer(1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "maxFloat ** 2.0", expression: atomAsteriskAsterisk.Apply(Float(math.MaxFloat64), Float(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "ε ** 2.0", expression: atomAsteriskAsterisk.Apply(Float(math.SmallestNonzeroFloat64), Float(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, - {title: "-1 ** 1.1", expression: atomAsteriskAsterisk.Apply(Integer(-1), Float(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "0 ** -1", expression: atomAsteriskAsterisk.Apply(Integer(0), Float(-1)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "maxFloat ** 2.0", expression: atomAsteriskAsterisk.Apply(newFloatFromStringMust("9e+6144"), NewFloatFromInt64(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "ε ** 2.0", expression: atomAsteriskAsterisk.Apply(newFloatFromStringMust("1e-6143"), NewFloatFromInt64(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, + {title: "-1 ** 1.1", expression: atomAsteriskAsterisk.Apply(Integer(-1), newFloatFromFloat64Must(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "0 ** -1", expression: atomAsteriskAsterisk.Apply(Integer(0), NewFloatFromInt64(-1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "sin(0)", result: Float(0), expression: atomSin.Apply(Integer(0)), ok: true}, - {title: "sin(0.0)", result: Float(0), expression: atomSin.Apply(Float(0)), ok: true}, - {title: "sin(mock)", expression: atomSin.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "cos(0)", result: Float(1), expression: atomCos.Apply(Integer(0)), ok: true}, - {title: "cos(0.0)", result: Float(1), expression: atomCos.Apply(Float(0)), ok: true}, - {title: "cos(mock)", expression: atomCos.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "atan(0)", result: Float(0), expression: atomAtan.Apply(Integer(0)), ok: true}, - {title: "atan(0.0)", result: Float(0), expression: atomAtan.Apply(Float(0)), ok: true}, - {title: "atan(mock)", expression: atomAtan.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "exp(0)", result: Float(1), expression: atomExp.Apply(Integer(0)), ok: true}, - {title: "exp(0.0)", result: Float(1), expression: atomExp.Apply(Float(0)), ok: true}, + {title: "exp(0)", result: NewFloatFromInt64(1), expression: atomExp.Apply(Integer(0)), ok: true}, + {title: "exp(0.0)", result: NewFloatFromInt64(1), expression: atomExp.Apply(NewFloatFromInt64(0)), ok: true}, {title: "exp(mock)", expression: atomExp.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "exp(maxFloat)", expression: atomExp.Apply(Float(math.MaxFloat64)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "exp(-maxFloat)", expression: atomExp.Apply(Float(-math.MaxFloat64)), err: evaluationError(exceptionalValueUnderflow, nil)}, + {title: "exp(maxFloat)", expression: atomExp.Apply(newFloatFromStringMust("9e+6144")), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "exp(-maxFloat)", expression: atomExp.Apply(newFloatFromStringMust("-9e+6144")), err: evaluationError(exceptionalValueUnderflow, nil)}, - {title: "log(1)", result: Float(0), expression: atomLog.Apply(Integer(1)), ok: true}, - {title: "log(1.0)", result: Float(0), expression: atomLog.Apply(Float(1)), ok: true}, + {title: "log(1)", result: NewFloatFromInt64(0), expression: atomLog.Apply(Integer(1)), ok: true}, + {title: "log(1.0)", result: NewFloatFromInt64(0), expression: atomLog.Apply(NewFloatFromInt64(1)), ok: true}, {title: "log(mock)", expression: atomLog.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "log(0.0)", expression: atomLog.Apply(Float(0)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "log(0.0)", expression: atomLog.Apply(NewFloatFromInt64(0)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "sqrt(0)", result: Float(0), expression: atomSqrt.Apply(Integer(0)), ok: true}, - {title: "sqrt(0.0)", result: Float(0), expression: atomSqrt.Apply(Float(0)), ok: true}, + {title: "sqrt(0)", result: NewFloatFromInt64(0), expression: atomSqrt.Apply(Integer(0)), ok: true}, + {title: "sqrt(0.0)", result: NewFloatFromInt64(0), expression: atomSqrt.Apply(NewFloatFromInt64(0)), ok: true}, {title: "sqrt(mock)", expression: atomSqrt.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "sqrt(-1.0)", result: Float(0), expression: atomSqrt.Apply(Float(-1)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "sqrt(-1.0)", result: NewFloatFromInt64(0), expression: atomSqrt.Apply(NewFloatFromInt64(-1)), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "max(1, 2)", result: Integer(2), expression: atomMax.Apply(Integer(1), Integer(2)), ok: true}, {title: "max(1, 1)", result: Integer(1), expression: atomMax.Apply(Integer(1), Integer(1)), ok: true}, - {title: "max(1, 2.0)", result: Float(2), expression: atomMax.Apply(Integer(1), Float(2)), ok: true}, - {title: "max(1, 1.0)", result: Integer(1), expression: atomMax.Apply(Integer(1), Float(1)), ok: true}, + {title: "max(1, 2.0)", result: NewFloatFromInt64(2), expression: atomMax.Apply(Integer(1), NewFloatFromInt64(2)), ok: true}, + {title: "max(1, 1.0)", result: Integer(1), expression: atomMax.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, {title: "max(1, mock)", expression: atomMax.Apply(Integer(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "max(1.0, 2)", result: Integer(2), expression: atomMax.Apply(Float(1), Integer(2)), ok: true}, - {title: "max(1.0, 1)", result: Float(1), expression: atomMax.Apply(Float(1), Integer(1)), ok: true}, - {title: "max(1.0, 2.0)", result: Float(2), expression: atomMax.Apply(Float(1), Float(2)), ok: true}, - {title: "max(1.0, 1.0)", result: Float(1), expression: atomMax.Apply(Float(1), Float(1)), ok: true}, - {title: "max(1.0, mock)", expression: atomMax.Apply(Float(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "max(1.0, 2)", result: Integer(2), expression: atomMax.Apply(NewFloatFromInt64(1), Integer(2)), ok: true}, + {title: "max(1.0, 1)", result: NewFloatFromInt64(1), expression: atomMax.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "max(1.0, 2.0)", result: NewFloatFromInt64(2), expression: atomMax.Apply(NewFloatFromInt64(1), NewFloatFromInt64(2)), ok: true}, + {title: "max(1.0, 1.0)", result: NewFloatFromInt64(1), expression: atomMax.Apply(NewFloatFromInt64(1), NewFloatFromInt64(1)), ok: true}, + {title: "max(1.0, mock)", expression: atomMax.Apply(NewFloatFromInt64(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "max(mock, 1)", expression: atomMax.Apply(&mockNumber{}, Integer(1)), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "min(2, 1)", result: Integer(1), expression: atomMin.Apply(Integer(2), Integer(1)), ok: true}, {title: "min(1, 1)", result: Integer(1), expression: atomMin.Apply(Integer(1), Integer(1)), ok: true}, - {title: "min(2, 1.0)", result: Float(1), expression: atomMin.Apply(Integer(2), Float(1)), ok: true}, - {title: "min(1, 1.0)", result: Integer(1), expression: atomMin.Apply(Integer(1), Float(1)), ok: true}, + {title: "min(2, 1.0)", result: NewFloatFromInt64(1), expression: atomMin.Apply(Integer(2), NewFloatFromInt64(1)), ok: true}, + {title: "min(1, 1.0)", result: Integer(1), expression: atomMin.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, {title: "min(1, mock)", expression: atomMin.Apply(Integer(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "min(2.0, 1)", result: Integer(1), expression: atomMin.Apply(Float(2), Integer(1)), ok: true}, - {title: "min(1.0, 1)", result: Float(1), expression: atomMin.Apply(Float(1), Integer(1)), ok: true}, - {title: "min(2.0, 1.0)", result: Float(1), expression: atomMin.Apply(Float(2), Float(1)), ok: true}, - {title: "min(1.0, 1.0)", result: Float(1), expression: atomMin.Apply(Float(1), Float(1)), ok: true}, - {title: "min(1.0, mock)", expression: atomMin.Apply(Float(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "min(2.0, 1)", result: Integer(1), expression: atomMin.Apply(NewFloatFromInt64(2), Integer(1)), ok: true}, + {title: "min(1.0, 1)", result: NewFloatFromInt64(1), expression: atomMin.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "min(2.0, 1.0)", result: NewFloatFromInt64(1), expression: atomMin.Apply(NewFloatFromInt64(2), NewFloatFromInt64(1)), ok: true}, + {title: "min(1.0, 1.0)", result: NewFloatFromInt64(1), expression: atomMin.Apply(NewFloatFromInt64(1), NewFloatFromInt64(1)), ok: true}, + {title: "min(1.0, mock)", expression: atomMin.Apply(NewFloatFromInt64(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "min(mock, 1)", expression: atomMin.Apply(&mockNumber{}, Integer(1)), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "1 ^ 1", result: Integer(1), expression: atomCaret.Apply(Integer(1), Integer(1)), ok: true}, @@ -217,66 +232,41 @@ func TestIs(t *testing.T) { {title: "-1 ^ -1", result: Integer(-1), expression: atomCaret.Apply(Integer(-1), Integer(-1)), ok: true}, {title: "-1 ^ minInt", expression: atomCaret.Apply(Integer(-1), Integer(math.MinInt64)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "2 ^ -2", expression: atomCaret.Apply(Integer(2), Integer(-2)), err: typeError(validTypeFloat, Integer(2), nil)}, - {title: "1 ^ 1.0", result: Float(1), expression: atomCaret.Apply(Integer(1), Float(1)), ok: true}, + {title: "1 ^ 1.0", result: NewFloatFromInt64(1), expression: atomCaret.Apply(Integer(1), NewFloatFromInt64(1)), ok: true}, {title: "1 ^ mock", expression: atomCaret.Apply(Integer(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "maxInt ^ 2", expression: atomCaret.Apply(Integer(math.MaxInt64), Integer(2)), err: evaluationError(exceptionalValueIntOverflow, nil)}, {title: "2 ^ 63", expression: atomCaret.Apply(Integer(2), Integer(63)), err: evaluationError(exceptionalValueIntOverflow, nil)}, - {title: "1.0 ^ 1", result: Float(1), expression: atomCaret.Apply(Float(1), Integer(1)), ok: true}, - {title: "1.0 ^ 1.0", result: Float(1), expression: atomCaret.Apply(Float(1), Float(1)), ok: true}, - {title: "1.0 ^ mock", expression: atomCaret.Apply(Float(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "mock ^ 1.0", expression: atomCaret.Apply(&mockNumber{}, Float(1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "maxFloat ^ 2.0", expression: atomCaret.Apply(Float(math.MaxFloat64), Float(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, - {title: "ε ^ 2.0", expression: atomCaret.Apply(Float(math.SmallestNonzeroFloat64), Float(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, - {title: "-1 ^ 1.1", expression: atomCaret.Apply(Integer(-1), Float(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "1.0 ^ 1", result: NewFloatFromInt64(1), expression: atomCaret.Apply(NewFloatFromInt64(1), Integer(1)), ok: true}, + {title: "1.0 ^ 1.0", result: NewFloatFromInt64(1), expression: atomCaret.Apply(NewFloatFromInt64(1), NewFloatFromInt64(1)), ok: true}, + {title: "1.0 ^ mock", expression: atomCaret.Apply(NewFloatFromInt64(1), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "mock ^ 1.0", expression: atomCaret.Apply(&mockNumber{}, NewFloatFromInt64(1)), err: evaluationError(exceptionalValueUndefined, nil)}, + {title: "maxFloat ^ 2.0", expression: atomCaret.Apply(newFloatFromStringMust("9e+6144"), NewFloatFromInt64(2)), err: evaluationError(exceptionalValueFloatOverflow, nil)}, + {title: "ε ^ 2.0", expression: atomCaret.Apply(newFloatFromStringMust("1e-6143"), NewFloatFromInt64(2)), err: evaluationError(exceptionalValueUnderflow, nil)}, + {title: "-1 ^ 1.1", expression: atomCaret.Apply(Integer(-1), newFloatFromFloat64Must(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, {title: "0 ^ -1", expression: atomCaret.Apply(Integer(0), Integer(-1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "asin(0)", result: Float(0), expression: atomAsin.Apply(Integer(0)), ok: true}, - {title: "asin(0.0)", result: Float(0), expression: atomAsin.Apply(Float(0)), ok: true}, - {title: "asin(mock)", expression: atomAsin.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "asin(1.1)", expression: atomAsin.Apply(Float(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "asin(-1.1)", expression: atomAsin.Apply(Float(-1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "acos(1)", result: Float(0), expression: atomAcos.Apply(Integer(1)), ok: true}, - {title: "acos(0.0)", result: Float(0), expression: atomAcos.Apply(Float(1)), ok: true}, - {title: "acos(mock)", expression: atomAcos.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "acos(1.1)", expression: atomAcos.Apply(Float(1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "acos(-1.1)", expression: atomAcos.Apply(Float(-1.1)), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "atan2(0, 1)", result: Float(0), expression: atomAtan2.Apply(Integer(0), Integer(1)), ok: true}, - {title: "atan2(0, 1.0)", result: Float(0), expression: atomAtan2.Apply(Integer(0), Float(1)), ok: true}, - {title: "atan2(0, mock)", expression: atomAtan2.Apply(Integer(0), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "atan2(0.0, 1)", result: Float(0), expression: atomAtan2.Apply(Float(0), Integer(1)), ok: true}, - {title: "atan2(0.0, 1.0)", result: Float(0), expression: atomAtan2.Apply(Float(0), Float(1)), ok: true}, - {title: "atan2(0.0, mock)", expression: atomAtan2.Apply(Float(0), &mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "atan2(mock, 1)", expression: atomAtan2.Apply(&mockNumber{}, Integer(1)), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "atan2(0, 0)", expression: atomAtan2.Apply(Integer(0), Integer(0)), err: evaluationError(exceptionalValueUndefined, nil)}, - - {title: "tan(0)", result: Float(0), expression: atomTan.Apply(Integer(0)), ok: true}, - {title: "tan(0.0)", result: Float(0), expression: atomTan.Apply(Float(0)), ok: true}, - {title: "tan(mock)", expression: atomTan.Apply(&mockNumber{}), err: evaluationError(exceptionalValueUndefined, nil)}, - {title: "16 >> 2", result: Integer(4), expression: atomBitwiseRightShift.Apply(Integer(16), Integer(2)), ok: true}, - {title: "16 >> 2.0", expression: atomBitwiseRightShift.Apply(Integer(16), Float(2)), err: typeError(validTypeInteger, Float(2), nil)}, - {title: "16.0 >> 2", expression: atomBitwiseRightShift.Apply(Float(16), Integer(2)), err: typeError(validTypeInteger, Float(16), nil)}, + {title: "16 >> 2.0", expression: atomBitwiseRightShift.Apply(Integer(16), NewFloatFromInt64(2)), err: typeError(validTypeInteger, NewFloatFromInt64(2), nil)}, + {title: "16.0 >> 2", expression: atomBitwiseRightShift.Apply(NewFloatFromInt64(16), Integer(2)), err: typeError(validTypeInteger, NewFloatFromInt64(16), nil)}, {title: "16 << 2", result: Integer(64), expression: atomBitwiseLeftShift.Apply(Integer(16), Integer(2)), ok: true}, - {title: "16 << 2.0", expression: atomBitwiseLeftShift.Apply(Integer(16), Float(2)), err: typeError(validTypeInteger, Float(2), nil)}, - {title: "16.0 << 2", expression: atomBitwiseLeftShift.Apply(Float(16), Integer(2)), err: typeError(validTypeInteger, Float(16), nil)}, + {title: "16 << 2.0", expression: atomBitwiseLeftShift.Apply(Integer(16), NewFloatFromInt64(2)), err: typeError(validTypeInteger, NewFloatFromInt64(2), nil)}, + {title: "16.0 << 2", expression: atomBitwiseLeftShift.Apply(NewFloatFromInt64(16), Integer(2)), err: typeError(validTypeInteger, NewFloatFromInt64(16), nil)}, {title: `10 /\ 12`, result: Integer(8), expression: atomBitwiseAnd.Apply(Integer(10), Integer(12)), ok: true}, - {title: `10 /\ 12.0`, expression: atomBitwiseAnd.Apply(Integer(10), Float(12)), err: typeError(validTypeInteger, Float(12), nil)}, - {title: `10.0 /\ 12`, expression: atomBitwiseAnd.Apply(Float(10), Integer(12)), err: typeError(validTypeInteger, Float(10), nil)}, + {title: `10 /\ 12.0`, expression: atomBitwiseAnd.Apply(Integer(10), NewFloatFromInt64(12)), err: typeError(validTypeInteger, NewFloatFromInt64(12), nil)}, + {title: `10.0 /\ 12`, expression: atomBitwiseAnd.Apply(NewFloatFromInt64(10), Integer(12)), err: typeError(validTypeInteger, NewFloatFromInt64(10), nil)}, {title: `10 \/ 12`, result: Integer(14), expression: atomBitwiseOr.Apply(Integer(10), Integer(12)), ok: true}, - {title: `10 \/ 12.0`, expression: atomBitwiseOr.Apply(Integer(10), Float(12)), err: typeError(validTypeInteger, Float(12), nil)}, - {title: `10.0 \/ 12`, expression: atomBitwiseOr.Apply(Float(10), Integer(12)), err: typeError(validTypeInteger, Float(10), nil)}, + {title: `10 \/ 12.0`, expression: atomBitwiseOr.Apply(Integer(10), NewFloatFromInt64(12)), err: typeError(validTypeInteger, NewFloatFromInt64(12), nil)}, + {title: `10.0 \/ 12`, expression: atomBitwiseOr.Apply(NewFloatFromInt64(10), Integer(12)), err: typeError(validTypeInteger, NewFloatFromInt64(10), nil)}, {title: `\ \ 10`, result: Integer(10), expression: atomBackSlash.Apply(atomBackSlash.Apply(Integer(10))), ok: true}, - {title: `\ \ 10.0`, expression: atomBackSlash.Apply(atomBackSlash.Apply(Float(10))), err: typeError(validTypeInteger, Float(10), nil)}, + {title: `\ \ 10.0`, expression: atomBackSlash.Apply(atomBackSlash.Apply(NewFloatFromInt64(10))), err: typeError(validTypeInteger, NewFloatFromInt64(10), nil)}, {title: "xor(10, 12)", result: Integer(6), expression: atomXor.Apply(Integer(10), Integer(12)), ok: true}, - {title: "xor(10, 12.0)", expression: atomXor.Apply(Integer(10), Float(12)), err: typeError(validTypeInteger, Float(12), nil)}, - {title: "xor(10.0, 12)", expression: atomXor.Apply(Float(10), Integer(12)), err: typeError(validTypeInteger, Float(10), nil)}, + {title: "xor(10, 12.0)", expression: atomXor.Apply(Integer(10), NewFloatFromInt64(12)), err: typeError(validTypeInteger, NewFloatFromInt64(12), nil)}, + {title: "xor(10.0, 12)", expression: atomXor.Apply(NewFloatFromInt64(10), Integer(12)), err: typeError(validTypeInteger, NewFloatFromInt64(10), nil)}, } for _, tt := range tests { @@ -299,7 +289,7 @@ func TestEqual(t *testing.T) { }) t.Run("float", func(t *testing.T) { - ok, err := Equal(&vm, Integer(1), Float(1), Success, nil).Force(context.Background()) + ok, err := Equal(&vm, Integer(1), NewFloatFromInt64(1), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) @@ -307,13 +297,13 @@ func TestEqual(t *testing.T) { t.Run("float", func(t *testing.T) { t.Run("integer", func(t *testing.T) { - ok, err := Equal(&vm, Float(1), Integer(1), Success, nil).Force(context.Background()) + ok, err := Equal(&vm, NewFloatFromInt64(1), Integer(1), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) t.Run("float", func(t *testing.T) { - ok, err := Equal(&vm, Float(1), Float(1), Success, nil).Force(context.Background()) + ok, err := Equal(&vm, NewFloatFromInt64(1), NewFloatFromInt64(1), Success, nil).Force(context.Background()) assert.NoError(t, err) assert.True(t, ok) }) @@ -346,9 +336,9 @@ func TestNotEqual(t *testing.T) { err error }{ {title: `1 =\= 2`, e1: Integer(1), e2: Integer(2), ok: true}, - {title: `1 =\= 2.0`, e1: Integer(1), e2: Float(2), ok: true}, - {title: `1.0 =\= 2`, e1: Float(1), e2: Integer(2), ok: true}, - {title: `1.0 =\= 2.0`, e1: Float(1), e2: Float(2), ok: true}, + {title: `1 =\= 2.0`, e1: Integer(1), e2: NewFloatFromInt64(2), ok: true}, + {title: `1.0 =\= 2`, e1: NewFloatFromInt64(1), e2: Integer(2), ok: true}, + {title: `1.0 =\= 2.0`, e1: NewFloatFromInt64(1), e2: NewFloatFromInt64(2), ok: true}, {title: `X =\= 1`, e1: x, e2: Integer(1), err: InstantiationError(nil)}, {title: `1 =\= X`, e1: Integer(1), e2: x, err: InstantiationError(nil)}, {title: `1 =\= 1`, e1: Integer(1), e2: Integer(1), ok: false}, @@ -373,9 +363,9 @@ func TestLessThan(t *testing.T) { err error }{ {title: `1 < 2`, e1: Integer(1), e2: Integer(2), ok: true}, - {title: `1 < 2.0`, e1: Integer(1), e2: Float(2), ok: true}, - {title: `1.0 < 2`, e1: Float(1), e2: Integer(2), ok: true}, - {title: `1.0 < 2.0`, e1: Float(1), e2: Float(2), ok: true}, + {title: `1 < 2.0`, e1: Integer(1), e2: NewFloatFromInt64(2), ok: true}, + {title: `1.0 < 2`, e1: NewFloatFromInt64(1), e2: Integer(2), ok: true}, + {title: `1.0 < 2.0`, e1: NewFloatFromInt64(1), e2: NewFloatFromInt64(2), ok: true}, {title: `X < 1`, e1: x, e2: Integer(1), err: InstantiationError(nil)}, {title: `1 < X`, e1: Integer(1), e2: x, err: InstantiationError(nil)}, {title: `1 < 1`, e1: Integer(1), e2: Integer(1), ok: false}, @@ -400,9 +390,9 @@ func TestGreaterThan(t *testing.T) { err error }{ {title: `2 > 1`, e1: Integer(2), e2: Integer(1), ok: true}, - {title: `2 > 1.0`, e1: Integer(2), e2: Float(1), ok: true}, - {title: `2.0 > 1`, e1: Float(2), e2: Integer(1), ok: true}, - {title: `2.0 > 1.0`, e1: Float(2), e2: Float(1), ok: true}, + {title: `2 > 1.0`, e1: Integer(2), e2: NewFloatFromInt64(1), ok: true}, + {title: `2.0 > 1`, e1: NewFloatFromInt64(2), e2: Integer(1), ok: true}, + {title: `2.0 > 1.0`, e1: NewFloatFromInt64(2), e2: NewFloatFromInt64(1), ok: true}, {title: `X > 1`, e1: x, e2: Integer(1), err: InstantiationError(nil)}, {title: `1 > X`, e1: Integer(1), e2: x, err: InstantiationError(nil)}, {title: `1 > 1`, e1: Integer(1), e2: Integer(1), ok: false}, @@ -427,9 +417,9 @@ func TestLessThanOrEqual(t *testing.T) { err error }{ {title: `1 =< 1`, e1: Integer(1), e2: Integer(1), ok: true}, - {title: `1 =< 1.0`, e1: Integer(1), e2: Float(1), ok: true}, - {title: `1.0 =< 1`, e1: Float(1), e2: Integer(1), ok: true}, - {title: `1.0 =< 1.0`, e1: Float(1), e2: Float(1), ok: true}, + {title: `1 =< 1.0`, e1: Integer(1), e2: NewFloatFromInt64(1), ok: true}, + {title: `1.0 =< 1`, e1: NewFloatFromInt64(1), e2: Integer(1), ok: true}, + {title: `1.0 =< 1.0`, e1: NewFloatFromInt64(1), e2: NewFloatFromInt64(1), ok: true}, {title: `X =< 1`, e1: x, e2: Integer(1), err: InstantiationError(nil)}, {title: `1 =< X`, e1: Integer(1), e2: x, err: InstantiationError(nil)}, {title: `2 =< 1`, e1: Integer(2), e2: Integer(1), ok: false}, @@ -454,9 +444,9 @@ func TestGreaterThanOrEqual(t *testing.T) { err error }{ {title: `1 >= 1`, e1: Integer(1), e2: Integer(1), ok: true}, - {title: `1 >= 1.0`, e1: Integer(1), e2: Float(1), ok: true}, - {title: `1.0 >= 1`, e1: Float(1), e2: Integer(1), ok: true}, - {title: `1.0 >= 1.0`, e1: Float(1), e2: Float(1), ok: true}, + {title: `1 >= 1.0`, e1: Integer(1), e2: NewFloatFromInt64(1), ok: true}, + {title: `1.0 >= 1`, e1: NewFloatFromInt64(1), e2: Integer(1), ok: true}, + {title: `1.0 >= 1.0`, e1: NewFloatFromInt64(1), e2: NewFloatFromInt64(1), ok: true}, {title: `X >= 1`, e1: x, e2: Integer(1), err: InstantiationError(nil)}, {title: `1 >= X`, e1: Integer(1), e2: x, err: InstantiationError(nil)}, {title: `1 >= 2`, e1: Integer(1), e2: Integer(2), ok: false}, diff --git a/engine/parser_test.go b/engine/parser_test.go index 6b9006c..a69fa37 100644 --- a/engine/parser_test.go +++ b/engine/parser_test.go @@ -9,6 +9,18 @@ import ( "github.com/stretchr/testify/assert" ) +func assertEqualFloatAware(t *testing.T, expected interface{}, actual interface{}) { + if x, ok := expected.(Float); ok { + if y, ok := actual.(Float); ok { + if x.dec.Cmp(y.dec) == 0 { + return + } + } + assert.Fail(t, "Not equal", expected, actual) + } + assert.Equal(t, expected, actual) +} + func TestParser_Term(t *testing.T) { ops := operators{} ops.define(1000, operatorSpecifierXFY, NewAtom(`,`)) @@ -69,10 +81,10 @@ func TestParser_Term(t *testing.T) { {input: `-`, err: io.EOF}, {input: `- -`, err: io.EOF}, - {input: `1.0.`, term: Float(1)}, - {input: `-1.0.`, term: Float(-1)}, - {input: `- 1.0.`, term: Float(-1)}, - {input: `'-'1.0.`, term: Float(-1)}, + {input: `1.0.`, term: NewFloatFromInt64(1)}, + {input: `-1.0.`, term: NewFloatFromInt64(-1)}, + {input: `- 1.0.`, term: NewFloatFromInt64(-1)}, + {input: `'-'1.0.`, term: NewFloatFromInt64(-1)}, {input: `_.`, termLazy: func() Term { return lastVariable() @@ -167,16 +179,16 @@ func TestParser_Term(t *testing.T) { doubleQuotes: tc.doubleQuotes, } term, err := p.Term() - assert.Equal(t, tc.err, err) + assertEqualFloatAware(t, tc.err, err) if tc.termLazy == nil { - assert.Equal(t, tc.term, term) + assertEqualFloatAware(t, tc.term, term) } else { - assert.Equal(t, tc.termLazy(), term) + assertEqualFloatAware(t, tc.termLazy(), term) } if tc.vars == nil { assert.Empty(t, p.Vars) } else { - assert.Equal(t, tc.vars(), p.Vars) + assertEqualFloatAware(t, tc.vars(), p.Vars) } }) } @@ -195,22 +207,22 @@ func TestParser_Replace(t *testing.T) { title: "chars", doubleQuotes: doubleQuotesChars, input: `[?, ?, ?, ?].`, - args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}}, - term: List(Float(1.0), Integer(2), CharList("foo"), List(CharList("a"), CharList("b"), CharList("c"))), + args: []interface{}{1, 2, "foo", []string{"a", "b", "c"}}, + term: List(Integer(1), Integer(2), CharList("foo"), List(CharList("a"), CharList("b"), CharList("c"))), }, { title: "codes", doubleQuotes: doubleQuotesCodes, input: `[?, ?, ?, ?].`, - args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}}, - term: List(Float(1.0), Integer(2), CodeList("foo"), List(CodeList("a"), CodeList("b"), CodeList("c"))), + args: []interface{}{1, 2, "foo", []string{"a", "b", "c"}}, + term: List(Integer(1), Integer(2), CodeList("foo"), List(CodeList("a"), CodeList("b"), CodeList("c"))), }, { title: "atom", doubleQuotes: doubleQuotesAtom, input: `[?, ?, ?, ?].`, - args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}}, - term: List(Float(1.0), Integer(2), NewAtom("foo"), List(NewAtom("a"), NewAtom("b"), NewAtom("c"))), + args: []interface{}{1, 2, "foo", []string{"a", "b", "c"}}, + term: List(Integer(1), Integer(2), NewAtom("foo"), List(NewAtom("a"), NewAtom("b"), NewAtom("c"))), }, { title: "invalid argument", @@ -221,13 +233,13 @@ func TestParser_Replace(t *testing.T) { { title: "too few arguments", input: `[?, ?, ?, ?, ?].`, - args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}}, + args: []interface{}{1, 2, "foo", []string{"a", "b", "c"}}, termErr: errors.New("not enough arguments for placeholders"), }, { title: "too many arguments", input: `[?, ?, ?, ?].`, - args: []interface{}{1.0, 2, "foo", []string{"a", "b", "c"}, "extra"}, + args: []interface{}{1, 2, "foo", []string{"a", "b", "c"}, "extra"}, termErr: errors.New("too many arguments for placeholders: [extra]"), }, } @@ -277,10 +289,10 @@ func TestParser_Number(t *testing.T) { {input: `0o1`, number: Integer(1)}, {input: `0x1`, number: Integer(1)}, - {input: `3.3`, number: Float(3.3)}, - {input: `-3.3`, number: Float(-3.3)}, - {input: `- 3.3`, number: Float(-3.3)}, - {input: `'-'3.3`, number: Float(-3.3)}, + {input: `3.3`, number: newFloatFromFloat64Must(3.3)}, + {input: `-3.3`, number: newFloatFromFloat64Must(-3.3)}, + {input: `- 3.3`, number: newFloatFromFloat64Must(-3.3)}, + {input: `'-'3.3`, number: newFloatFromFloat64Must(-3.3)}, {input: ``, err: io.EOF}, {input: `X`, err: errNotANumber}, diff --git a/engine/stream_test.go b/engine/stream_test.go index d8e698b..0503609 100644 --- a/engine/stream_test.go +++ b/engine/stream_test.go @@ -79,7 +79,7 @@ func TestStream_Compare(t *testing.T) { o int }{ {title: `s > X`, s: &ss[1], t: x, o: 1}, - {title: `s > 1.0`, s: &ss[1], t: Float(1), o: 1}, + {title: `s > 1.0`, s: &ss[1], t: NewFloatFromInt64(1), o: 1}, {title: `s > 1`, s: &ss[1], t: Integer(2), o: 1}, {title: `s > a`, s: &ss[1], t: NewAtom("a"), o: 1}, {title: `s > s`, s: &ss[1], t: &ss[0], o: 1}, diff --git a/engine/term_test.go b/engine/term_test.go index 8b11a6c..7eea5db 100644 --- a/engine/term_test.go +++ b/engine/term_test.go @@ -46,7 +46,7 @@ func TestCompareAtomic(t *testing.T) { o int }{ {a: &y{}, t: NewVariable(), o: 1}, - {a: &y{}, t: Float(0), o: 1}, + {a: &y{}, t: NewFloatFromInt64(0), o: 1}, {a: &y{}, t: Integer(0), o: 1}, {a: &y{}, t: Atom(0), o: 1}, {a: &y{}, t: &x{}, o: 1}, diff --git a/engine/variable_test.go b/engine/variable_test.go index 90ca997..efe4b8e 100644 --- a/engine/variable_test.go +++ b/engine/variable_test.go @@ -47,7 +47,7 @@ func TestVariable_Compare(t *testing.T) { {title: `X > W`, v: x, t: w, o: 1}, {title: `X = X`, v: x, t: x, o: 0}, {title: `X < Y`, v: x, t: y, o: -1}, - {title: `X < 0.0`, v: x, t: Float(0), o: -1}, + {title: `X < 0.0`, v: x, t: NewFloatFromInt64(0), o: -1}, {title: `X < 0`, v: x, t: Integer(0), o: -1}, {title: `X < a`, v: x, t: NewAtom("a"), o: -1}, {title: `X < f(a)`, v: x, t: NewAtom("f").Apply(NewAtom("a")), o: -1}, diff --git a/interpreter_test.go b/interpreter_test.go index a820e58..a6b31f2 100644 --- a/interpreter_test.go +++ b/interpreter_test.go @@ -724,7 +724,7 @@ func TestInterpreter_Exec(t *testing.T) { {query: `0.`, err: true}, {query: `append(cons(X, L1), L2, cons(X, L3)) :- append(L1, L2, L3).`}, - {query: `foo(?, ?, ?, ?).`, args: []interface{}{"a", 1, 2.0, []string{"abc", "def"}}}, + {query: `foo(?, ?, ?, ?).`, args: []interface{}{"a", 1, 2, []string{"abc", "def"}}}, {query: `foo(?).`, args: []interface{}{nil}, err: true}, {query: `#!/usr/bin/env 1pl @@ -769,7 +769,7 @@ func TestInterpreter_Query(t *testing.T) { type result struct { A string B int - C float64 + C string List []string `prolog:"D"` } @@ -788,12 +788,11 @@ func TestInterpreter_Query(t *testing.T) { {query: `append([a, b], [c], X).`, scan: map[string]TermString{}, result: map[string]TermString{ "X": "[a,b,c]", }}, - {query: `foo(?, ?, ?, ?).`, args: []interface{}{"a", 1, 2.0, []string{"abc", "def"}}, scan: map[string]interface{}{}, result: map[string]interface{}{}}, - {query: `foo(?, ?, ?, ?).`, args: []interface{}{nil, 1, 2.0, []string{"abc", "def"}}, queryErr: true, result: nil}, + {query: `foo(?, ?, ?, ?).`, args: []interface{}{nil, 1, "2.0", []string{"abc", "def"}}, queryErr: true, result: nil}, {query: `foo(A, B, C, D).`, scan: &result{}, result: &result{ A: "a", B: 1, - C: 2.0, + C: "2.0", List: []string{"abc", "def"}, }}, } @@ -1256,11 +1255,6 @@ func ExampleInterpreter_Exec_placeholders() { sols.Next() _ = sols.Close() - _ = p.Exec(`my_float(?, ?).`, float32(1), float64(1)) - sols, _ = p.Query(`my_float(F, F), float(F), write(F), nl.`) - sols.Next() - _ = sols.Close() - _ = p.Exec(`my_atom_list(?).`, []string{"foo", "bar", "baz"}) sols, _ = p.Query(`my_atom_list(As), maplist(maplist(atom), As), write(As), nl.`) sols.Next() @@ -1271,18 +1265,11 @@ func ExampleInterpreter_Exec_placeholders() { sols.Next() _ = sols.Close() - _ = p.Exec(`my_float_list(?).`, []float64{1, 2, 3}) - sols, _ = p.Query(`my_float_list(Fs), maplist(float, Fs), write(Fs), nl.`) - sols.Next() - _ = sols.Close() - // Output: // [f,o,o] // 1 - // 1.0 // [[f,o,o],[b,a,r],[b,a,z]] // [1,2,3] - // [1.0,2.0,3.0] } func ExampleInterpreter_Query_placeholders() { @@ -1293,26 +1280,18 @@ func ExampleInterpreter_Query_placeholders() { sols, _ = p.Query(`(I, I, I, I, I) = (?, ?, ?, ?, ?), integer(I), write(I), nl.`, int8(1), int16(1), int32(1), int64(1), 1) sols.Next() _ = sols.Close() - sols, _ = p.Query(`(F, F) = (?, ?), float(F), write(F), nl.`, float32(1), float64(1)) - sols.Next() - _ = sols.Close() sols, _ = p.Query(`L = ?, maplist(maplist(atom), L), write(L), nl.`, []string{"foo", "bar", "baz"}) sols.Next() _ = sols.Close() sols, _ = p.Query(`L = ?, maplist(integer, L), write(L), nl.`, []int{1, 2, 3}) sols.Next() _ = sols.Close() - sols, _ = p.Query(`L = ?, maplist(float, L), write(L), nl.`, []float64{1, 2, 3}) - sols.Next() - _ = sols.Close() // Output: // [f,o,o] // 1 - // 1.0 // [[f,o,o],[b,a,r],[b,a,z]] // [1,2,3] - // [1.0,2.0,3.0] } func ExampleNew_phrase() { diff --git a/solutions_test.go b/solutions_test.go index a680c4d..71dfc6a 100644 --- a/solutions_test.go +++ b/solutions_test.go @@ -82,9 +82,9 @@ func TestSolutions_Scan(t *testing.T) { X: 1, }}, {title: "struct: interface, float", sols: sols(map[string]engine.Term{ - "X": engine.Float(1), + "X": engine.NewFloatFromInt64(1), }), dest: &struct{ X interface{} }{}, result: &struct{ X interface{} }{ - X: 1.0, + X: "1", }}, {title: "struct: interface, list", sols: sols(map[string]engine.Term{ "X": engine.List(engine.Integer(1), engine.Integer(2), engine.Integer(3)), @@ -149,16 +149,10 @@ func TestSolutions_Scan(t *testing.T) { "X": engine.NewAtom("foo"), }), dest: &struct{ X int64 }{}, err: errConversion}, - {title: "struct: float32, float", sols: sols(map[string]engine.Term{ - "X": engine.Float(1), - }), dest: &struct{ X float32 }{}, result: &struct{ X float32 }{X: 1}}, {title: "struct: float32, non-float", sols: sols(map[string]engine.Term{ "X": engine.NewAtom("foo"), }), dest: &struct{ X float32 }{}, err: errConversion}, - {title: "struct: float64, float", sols: sols(map[string]engine.Term{ - "X": engine.Float(1), - }), dest: &struct{ X float64 }{}, result: &struct{ X float64 }{X: 1}}, {title: "struct: float64, non-float", sols: sols(map[string]engine.Term{ "X": engine.NewAtom("foo"), }), dest: &struct{ X float64 }{}, err: errConversion}, @@ -231,12 +225,12 @@ func ExampleSolutions_Scan() { var s struct { A string I int - F float64 + F string } _ = sols.Scan(&s) fmt.Printf("A = %s\n", s.A) fmt.Printf("I = %d\n", s.I) - fmt.Printf("F = %.2f\n", s.F) + fmt.Printf("F = %s\n", s.F) } // Output: @@ -250,14 +244,14 @@ func ExampleSolutions_Scan_tag() { sols, _ := p.Query(`A = foo, I = 42, F = 3.14.`) for sols.Next() { var s struct { - Atom string `prolog:"A"` - Integer int `prolog:"I"` - Float float64 `prolog:"F"` + Atom string `prolog:"A"` + Integer int `prolog:"I"` + Float string `prolog:"F"` } _ = sols.Scan(&s) fmt.Printf("Atom = %s\n", s.Atom) fmt.Printf("Integer = %d\n", s.Integer) - fmt.Printf("Float = %.2f\n", s.Float) + fmt.Printf("Float = %s\n", s.Float) } // Output: @@ -273,14 +267,14 @@ func ExampleSolutions_Scan_list() { var s struct { Atoms []string Integers []int64 - Floats []float64 + Floats []string Mixed []interface{} } _ = sols.Scan(&s) fmt.Printf("Atoms = %s\n", s.Atoms) fmt.Printf("Integers = %d\n", s.Integers) - fmt.Printf("Floats = %.1f\n", s.Floats) + fmt.Printf("Floats = %s\n", s.Floats) fmt.Printf("Mixed = %v\n", s.Mixed) } From dd60a95869a2c8ad64fb8c728617215b14461a1d Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:07:57 +0200 Subject: [PATCH 8/9] feat: secure integer arithmetic operations --- engine/number.go | 50 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/engine/number.go b/engine/number.go index 663dd8b..aa0aeff 100644 --- a/engine/number.go +++ b/engine/number.go @@ -617,16 +617,6 @@ func exp(x Number) (Number, error) { } return Float{dec: &dec}, nil - - //dec := decimal.WithContext(decimal.Context128) - //r := Float{ - // dec: decimal.Context128.Exp(dec, f.dec), - //} - //if !dec.IsFinite() { - // return Float{}, exceptionalValueUnderflow - //} - // - //return r, r.Err() } // log returns the natural logarithm of x. @@ -1130,7 +1120,15 @@ func intDivI(x, y Integer) (Integer, error) { // Two's complement special case return 0, exceptionalValueIntOverflow default: - return x / y, nil + fxdy, err := divII(x, y) + if err != nil { + return 0, err + } + xdy, err := roundFtoI(fxdy) + if err != nil { + return 0, err + } + return xdy, nil } } @@ -1138,14 +1136,34 @@ func remI(x, y Integer) (Integer, error) { if y == 0 { return 0, exceptionalValueZeroDivisor } - return x - ((x / y) * y), nil + + fxdy, err := divII(x, y) + if err != nil { + return 0, err + } + xdy, err := roundFtoI(fxdy) + if err != nil { + return 0, err + } + + return x - (xdy * y), nil } func modI(x, y Integer) (Integer, error) { if y == 0 { return 0, exceptionalValueZeroDivisor } - return x - (Integer(math.Floor(float64(x)/float64(y))) * y), nil + + fxdy, err := divII(x, y) + if err != nil { + return 0, err + } + xdy, err := floorFtoI(fxdy) + if err != nil { + return 0, err + } + + return x - (xdy * y), nil } func negI(x Integer) (Integer, error) { @@ -1189,7 +1207,11 @@ func intFloorDivI(x, y Integer) (Integer, error) { case y == 0: return 0, exceptionalValueZeroDivisor default: - return Integer(math.Floor(float64(x) / float64(y))), nil + fxdy, err := divII(x, y) + if err != nil { + return 0, err + } + return floorFtoI(fxdy) } } From bf4ef2a571732164ce3fd0f59f9b4594dd760454 Mon Sep 17 00:00:00 2001 From: Arnaud Mimart <33665250+amimart@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:29:45 +0200 Subject: [PATCH 9/9] refactor: remove unused var --- engine/number.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/number.go b/engine/number.go index aa0aeff..f123979 100644 --- a/engine/number.go +++ b/engine/number.go @@ -9,7 +9,6 @@ import ( var ( maxInt = Integer(math.MaxInt64) minInt = Integer(math.MinInt64) - oneFloat = NewFloatFromInt64(1) minusOneFloat = NewFloatFromInt64(-1) )