1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-03 15:02:32 +02:00

Feature/#7 numeric functions (#116)

* #7 Added ABS

* #7 Added ACOS

* #7 Added ASIN

* #7 Added ATAN

* #7 Added ATAN2

* #7 Added AVERAGE

* #7 Added CEIL

* #7 Added COS

* #7 Added DEGREES

* #7 Added EXP

* #7 Added EXP2

* #7 Added FLOOR

* #7 Added LOG

* #7 Added LOG2

* #7 Added LOG10

* #7 Added MAX

* #7 Added MEDIAN

* #7 Added MIN

* #7 Added PERCENTILE

* #7 Added PI

* #7 Added POW

* #7 Added RADIANS

* #7 Added RAND

* #7 Added RANGE

* #7 Added ROUND

* #7 Added SIN

* #7 Added SQRT

* #7 Added TAN

* #7 Added SUM

* #7 Added STDDEV_POPULATION

* #7 Added STDDEV_SAMPLE, VARIANCE_POPULATION, VARIANCE_SAMPLE
This commit is contained in:
Tim Voronov 2018-10-13 21:07:28 -04:00 committed by GitHub
parent 2417be3f9d
commit 5f94b77a39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 2466 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/json"
"hash/fnv"
"sort"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/pkg/errors"
@ -221,3 +222,17 @@ func (t *Array) Clone() core.Cloneable {
return cloned
}
func (t *Array) Sort() *Array {
c := make([]core.Value, len(t.value))
copy(c, t.value)
sort.SliceStable(c, func(i, j int) bool {
return c[i].Compare(c[j]) == 0
})
res := new(Array)
res.value = c
return res
}

View File

@ -63,6 +63,14 @@ func ParseFloatP(input interface{}) Float {
return res
}
func IsNaN(input Float) Boolean {
return NewBoolean(math.IsNaN(float64(input)))
}
func IsInf(input Float, sign Int) Boolean {
return NewBoolean(math.IsInf(float64(input), int(sign)))
}
func (t Float) MarshalJSON() ([]byte, error) {
return json.Marshal(float64(t))
}

View File

@ -5,6 +5,7 @@ import (
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
"github.com/MontFerret/ferret/pkg/stdlib/collections"
"github.com/MontFerret/ferret/pkg/stdlib/html"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"github.com/MontFerret/ferret/pkg/stdlib/objects"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
"github.com/MontFerret/ferret/pkg/stdlib/types"
@ -22,6 +23,7 @@ func NewLib() map[string]core.Function {
add(types.NewLib())
add(strings.NewLib())
add(math.NewLib())
add(collections.NewLib())
add(arrays.NewLib())
add(objects.NewLib())

29
pkg/stdlib/math/abs.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the absolute value of a given number.
* @param number (Int|Float) - Input number.
* @returns (Float) - The absolute value of a given number.
*/
func Abs(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Abs(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,38 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAbs(t *testing.T) {
Convey("Should return absolute value", t, func() {
Convey("When value is int", func() {
out, err := math.Abs(context.Background(), values.NewInt(-5))
So(err, ShouldBeNil)
So(out, ShouldEqual, 5)
out, err = math.Abs(context.Background(), values.NewInt(3))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
})
Convey("When value is float", func() {
out, err := math.Abs(context.Background(), values.NewFloat(-5))
So(err, ShouldBeNil)
So(out, ShouldEqual, 5)
out, err = math.Abs(context.Background(), values.NewFloat(5.1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 5.1)
})
})
}

29
pkg/stdlib/math/acos.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the arccosine, in radians, of a given number.
* @param number (Int|Float) - Input number.
* @returns (Float) - The arccosine, in radians, of a given number.
*/
func Acos(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Acos(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,24 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAcos(t *testing.T) {
Convey("Should return arccosine", t, func() {
out, err := math.Acos(context.Background(), values.NewInt(-1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3.141592653589793)
out, err = math.Acos(context.Background(), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1.5707963267948966)
})
}

29
pkg/stdlib/math/asin.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the arcsine, in radians, of a given number.
* @param number (Int|Float) - Input number.
* @returns (Float) - The arcsine, in radians, of a given number.
*/
func Asin(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Asin(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,34 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAsin(t *testing.T) {
Convey("Should return arcsine value", t, func() {
out, err := math.Asin(context.Background(), values.NewInt(1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1.5707963267948966)
out, err = math.Asin(context.Background(), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
out, err = math.Asin(context.Background(), values.NewInt(-1))
So(err, ShouldBeNil)
So(out, ShouldEqual, -1.5707963267948966)
out, err = math.Asin(context.Background(), values.NewInt(2))
So(err, ShouldBeNil)
So(values.IsNaN(out.(values.Float)), ShouldEqual, true)
})
}

29
pkg/stdlib/math/atan.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the arctangent, in radians, of a given number.
* @param number (Int|Float) - Input number.
* @returns (Float) - The arctangent, in radians, of a given number.
*/
func Atan(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Atan(toFloat(args[0]))), nil
}

39
pkg/stdlib/math/atan2.go Normal file
View File

@ -0,0 +1,39 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the arc tangent of y/x, using the signs of the two to determine the quadrant of the return value.
* @param number1 (Int|Float) - Input number.
* @param number2 (Int|Float) - Input number.
* @returns (Float) - The arc tangent of y/x, using the signs of the two to determine the quadrant of the return value.
*/
func Atan2(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
arg1 := toFloat(args[0])
arg2 := toFloat(args[1])
return values.NewFloat(math.Atan2(arg1, arg2)), nil
}

View File

@ -0,0 +1,34 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAtan2(t *testing.T) {
Convey("Should return tangent value", t, func() {
out, err := math.Atan2(context.Background(), values.NewInt(0), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
out, err = math.Atan2(context.Background(), values.NewInt(1), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1.5707963267948966)
out, err = math.Atan2(context.Background(), values.NewInt(1), values.NewInt(1))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 0.7853981633974483)
out, err = math.Atan2(context.Background(), values.NewInt(-10), values.NewInt(20))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -0.4636476090008061)
})
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAtan(t *testing.T) {
Convey("Should return arctangent value", t, func() {
out, err := math.Atan(context.Background(), values.NewInt(-1))
So(err, ShouldBeNil)
So(out, ShouldEqual, -0.7853981633974483)
out, err = math.Atan(context.Background(), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
out, err = math.Atan(context.Background(), values.NewInt(10))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 1.4711276743037345)
})
}

View File

@ -0,0 +1,55 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the average (arithmetic mean) of the values in array.
* @param array (Array) - Array of numbers.
* @returns (Float) - The average of the values in array.
*/
func Average(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.None, nil
}
var sum float64
arr.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.FloatType, core.IntType)
if err != nil {
return false
}
sum += toFloat(value)
return true
})
if err != nil {
return values.None, nil
}
count := arr.Length()
return values.Float(sum / float64(count)), nil
}

View File

@ -0,0 +1,47 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAverage(t *testing.T) {
Convey("Should return average value", t, func() {
out, err := math.Average(context.Background(), values.NewArrayWith(
values.NewInt(5),
values.NewInt(2),
values.NewInt(9),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 4.5)
out, err = math.Average(context.Background(), values.NewArrayWith(
values.NewInt(-3),
values.NewInt(-5),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, -2)
out, err = math.Average(context.Background(), values.NewArrayWith(
values.None,
values.NewInt(-5),
values.False,
))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
out, err = math.Average(context.Background(), values.NewArray(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

29
pkg/stdlib/math/ceil.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the least integer value greater than or equal to a given value.
* @param number (Int|Float) - Input number.
* @returns (Int) - The least integer value greater than or equal to a given value.
*/
func Ceil(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewInt(int(math.Ceil(toFloat(args[0])))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestCeil(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Ceil(context.Background(), values.NewFloat(2.49))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
out, err = math.Ceil(context.Background(), values.NewFloat(2.50))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
out, err = math.Ceil(context.Background(), values.NewFloat(-2.50))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -2)
})
}

29
pkg/stdlib/math/cos.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the cosine of a given number.
* @param number (Int|Float) - Input number.
* @returns (Float) - The cosine of a given number.
*/
func Cos(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Cos(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestCos(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Cos(context.Background(), values.NewFloat(1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0.5403023058681398)
out, err = math.Cos(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
out, err = math.Cos(context.Background(), values.NewFloat(-3.141592653589783))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -1)
})
}

View File

@ -0,0 +1,30 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the angle converted from radians to degrees.
* @param number (Float|Int) - The input number.
* @returns (Float) - The angle in degrees.
*/
func Degrees(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
r := toFloat(args[0])
return values.NewFloat(r * RadToDeg), nil
}

View File

@ -0,0 +1,24 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDegrees(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Degrees(context.Background(), values.NewFloat(0.7853981633974483))
So(err, ShouldBeNil)
So(out, ShouldEqual, 45)
out, err = math.Degrees(context.Background(), values.NewFloat(3.141592653589793))
So(err, ShouldBeNil)
So(out, ShouldEqual, 180)
})
}

29
pkg/stdlib/math/exp.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns Euler's constant (2.71828...) raised to the power of value.
* @param number (Int|Float) - Input number.
* @returns (Float) - Euler's constant raised to the power of value.
*/
func Exp(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Exp(toFloat(args[0]))), nil
}

29
pkg/stdlib/math/exp2.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns 2 raised to the power of value.
* @param number (Int|Float) - Input number.
* @returns (Float) - 2 raised to the power of value.
*/
func Exp2(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Exp2(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestExp2(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Exp2(context.Background(), values.NewFloat(16))
So(err, ShouldBeNil)
So(out, ShouldEqual, 65536)
out, err = math.Exp(context.Background(), values.NewFloat(1))
So(err, ShouldBeNil)
So(out.Compare(values.NewFloat(2)) == 1, ShouldBeTrue)
out, err = math.Exp(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
})
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestExp(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Exp(context.Background(), values.NewFloat(1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2.718281828459045)
out, err = math.Exp(context.Background(), values.NewFloat(10))
So(err, ShouldBeNil)
So(out.Compare(values.NewFloat(22026.46579480671)) == 1, ShouldBeTrue)
out, err = math.Exp(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
})
}

29
pkg/stdlib/math/floor.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the greatest integer value less than or equal to a given value.
* @param number (Int|Float) - Input number.
* @returns (Int) - The greatest integer value less than or equal to a given value.
*/
func Floor(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewInt(int(math.Floor(toFloat(args[0])))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestFloor(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Floor(context.Background(), values.NewFloat(2.49))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Floor(context.Background(), values.NewFloat(2.50))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Floor(context.Background(), values.NewFloat(-2.50))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -3)
})
}

60
pkg/stdlib/math/lib.go Normal file
View File

@ -0,0 +1,60 @@
package math
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
const (
RadToDeg = 180 / math.Pi
DegToRad = math.Pi / 180
RadToGrad = 200 / math.Pi
GradToDeg = math.Pi / 200
)
func NewLib() map[string]core.Function {
return map[string]core.Function{
"ABS": Abs,
"ACOS": Acos,
"ASIN": Asin,
"ATAN": Atan,
"ATAN2": Atan2,
"AVERAGE": Average,
"CEIL": Ceil,
"COS": Cos,
"DEGREES": Degrees,
"EXP": Exp,
"EXP2": Exp2,
"FLOOR": Floor,
"LOG": Log,
"LOG2": Log2,
"LOG10": Log10,
"MAX": Max,
"MEDIAN": Median,
"MIN": Min,
"PERCENTILE": Percentile,
"PI": Pi,
"POW": Pow,
"RADIANS": Radians,
"RAND": Rand,
"RANGE": Range,
"ROUND": Round,
"SIN": Sin,
"SQRT": Sqrt,
"STDDEV_POPULATION": StandardDeviationPopulation,
"STDDEV_SAMPLE": StandardDeviationSample,
"SUM": Sum,
"TAN": Tan,
"VARIANCE_POPULATION": PopulationVariance,
"VARIANCE_SAMPLE": SampleVariance,
}
}
func toFloat(arg core.Value) float64 {
if arg.Type() == core.IntType {
return float64(arg.(values.Int))
}
return float64(arg.(values.Float))
}

29
pkg/stdlib/math/log.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the natural logarithm of a given value.
* @param number (Int|Float) - Input number.
* @returns (Float) - The natural logarithm of a given value.
*/
func Log(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Log(toFloat(args[0]))), nil
}

29
pkg/stdlib/math/log10.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the decimal logarithm of a given value.
* @param number (Int|Float) - Input number.
* @returns (Float) - The decimal logarithm of a given value.
*/
func Log10(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Log10(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLog10(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Log10(context.Background(), values.NewFloat(10000))
So(err, ShouldBeNil)
So(out, ShouldEqual, 4)
out, err = math.Log10(context.Background(), values.NewFloat(10))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
out, err = math.Log10(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(values.IsInf(out.(values.Float), -1).Unwrap(), ShouldBeTrue)
})
}

29
pkg/stdlib/math/log2.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the binary logarithm of a given value.
* @param number (Int|Float) - Input number.
* @returns (Float) - The binary logarithm of a given value.
*/
func Log2(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Log2(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLog2(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Log2(context.Background(), values.NewFloat(1024))
So(err, ShouldBeNil)
So(out, ShouldEqual, 10)
out, err = math.Log2(context.Background(), values.NewFloat(8))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
out, err = math.Log2(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(values.IsInf(out.(values.Float), -1).Unwrap(), ShouldBeTrue)
})
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLog(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Log(context.Background(), values.NewFloat(2.718281828459045))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
out, err = math.Log(context.Background(), values.NewFloat(10))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2.302585092994046)
out, err = math.Log(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(values.IsInf(out.(values.Float), -1).Unwrap(), ShouldBeTrue)
})
}

57
pkg/stdlib/math/max.go Normal file
View File

@ -0,0 +1,57 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the greatest (arithmetic mean) of the values in array.
* @param array (Array) - Array of numbers.
* @returns (Float) - The greatest of the values in array.
*/
func Max(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.None, nil
}
var max float64
arr.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.FloatType, core.IntType)
if err != nil {
return false
}
fv := toFloat(value)
if fv > max {
max = fv
}
return true
})
if err != nil {
return values.None, nil
}
return values.NewFloat(max), nil
}

View File

@ -0,0 +1,47 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestMax(t *testing.T) {
Convey("Should return the largest value", t, func() {
out, err := math.Max(context.Background(), values.NewArrayWith(
values.NewInt(5),
values.NewInt(2),
values.NewInt(9),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 9)
out, err = math.Max(context.Background(), values.NewArrayWith(
values.NewInt(-3),
values.NewInt(-5),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Max(context.Background(), values.NewArrayWith(
values.None,
values.NewInt(-5),
values.False,
))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
out, err = math.Max(context.Background(), values.NewArray(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

34
pkg/stdlib/math/mean.go Normal file
View File

@ -0,0 +1,34 @@
package math
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
func mean(input *values.Array) (values.Float, error) {
if input.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
var err error
var sum float64
input.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.FloatType, core.IntType)
if err != nil {
return false
}
sum += toFloat(value)
return true
})
if err != nil {
return 0, err
}
return values.NewFloat(sum / float64(input.Length())), nil
}

53
pkg/stdlib/math/median.go Normal file
View File

@ -0,0 +1,53 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the median of the values in array.
* @param array (Array) - Array of numbers.
* @returns (Float) - The median of the values in array.
*/
func Median(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
sorted := arr.Sort()
l := sorted.Length()
var median core.Value
if l == 0 {
return values.NewFloat(math.NaN()), nil
} else if l%2 == 0 {
median, err = mean(sorted.Slice(l/2-1, l/2+1))
if err != nil {
return values.None, nil
}
} else {
median = sorted.Get(l / 2)
}
if err != nil {
return values.None, nil
}
return median, nil
}

View File

@ -0,0 +1,52 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestMedian(t *testing.T) {
Convey("Should return median value", t, func() {
out, err := math.Median(context.Background(), values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Average(context.Background(), values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2.5)
out, err = math.Average(context.Background(), values.NewArrayWith(
values.NewInt(2),
values.NewInt(1),
values.NewInt(4),
values.NewInt(3),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2.5)
out, err = math.Average(context.Background(), values.NewArrayWith(
values.None,
values.NewInt(-5),
values.False,
))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

57
pkg/stdlib/math/min.go Normal file
View File

@ -0,0 +1,57 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the smallest (arithmetic mean) of the values in array.
* @param array (Array) - Array of numbers.
* @returns (Float) - The smallest of the values in array.
*/
func Min(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.None, nil
}
var min float64
arr.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.FloatType, core.IntType)
if err != nil {
return false
}
fv := toFloat(value)
if min > fv || idx == 0 {
min = fv
}
return true
})
if err != nil {
return values.None, nil
}
return values.NewFloat(min), nil
}

View File

@ -0,0 +1,47 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestMin(t *testing.T) {
Convey("Should return the smallest value", t, func() {
out, err := math.Min(context.Background(), values.NewArrayWith(
values.NewInt(5),
values.NewInt(2),
values.NewInt(9),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Min(context.Background(), values.NewArrayWith(
values.NewInt(-3),
values.NewInt(-5),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, -5)
out, err = math.Min(context.Background(), values.NewArrayWith(
values.None,
values.NewInt(-5),
values.False,
))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
out, err = math.Min(context.Background(), values.NewArray(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, values.None)
})
}

View File

@ -0,0 +1,92 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/pkg/errors"
"math"
)
/*
* Returns the nth percentile of the values in a given array.
* @param array (Array) - Array of numbers.
* @param numb (Int) - A number which must be between 0 (excluded) and 100 (included).
* @param method (String, optional) - "rank" (default) or "interpolation".
* @returns (Float) - The nth percentile, or null if the array is empty or only null values are contained in it or the percentile cannot be calculated.
*/
func Percentile(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType)
if err != nil {
return values.None, err
}
// TODO: Implement different methods
//method := "rank"
//
//if len(args) > 2 {
// err = core.ValidateType(args[2], core.StringType)
//
// if err != nil {
// return values.None, err
// }
//
// if args[2].String() == "interpolation" {
// method = "interpolation"
// }
//}
arr := args[0].(*values.Array)
percent := values.Float(args[1].(values.Int))
if arr.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
if percent <= 0 || percent > 100 {
return values.NewFloat(math.NaN()), errors.New("input is outside of range")
}
sorted := arr.Sort()
// Multiply percent by length of input
l := values.Float(sorted.Length())
index := (percent / 100) * l
even := values.Float(values.Int(index))
var percentile core.Value
// Check if the index is a whole number
if index == even {
// Convert float to int
i := values.Int(index)
// Find the value at the index
percentile = sorted.Get(i - 1)
} else if index > 1 {
// Convert float to int via truncation
i := values.Int(index)
// Find the average of the index and following values
percentile, _ = mean(values.NewArrayWith(sorted.Get(i-1), sorted.Get(i)))
} else {
return values.NewFloat(math.NaN()), errors.New("input is outside of range")
}
return percentile, nil
}

View File

@ -0,0 +1,28 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestPrecentile(t *testing.T) {
Convey("Should return nth percentile value", t, func() {
out, err := math.Percentile(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
),
values.NewInt(50),
)
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
})
}

22
pkg/stdlib/math/pi.go Normal file
View File

@ -0,0 +1,22 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns Pi value.
* @returns (Float) - Pi value.
*/
func Pi(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 0, 0)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Pi), nil
}

View File

@ -0,0 +1,18 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/stdlib/math"
. "github.com/smartystreets/goconvey/convey"
m "math"
"testing"
)
func TestPi(t *testing.T) {
Convey("Should return Pi value", t, func() {
out, err := math.Pi(context.Background())
So(err, ShouldBeNil)
So(out, ShouldEqual, m.Pi)
})
}

36
pkg/stdlib/math/pow.go Normal file
View File

@ -0,0 +1,36 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the base to the exponent value.
* @param base (Int|Float) - The base value.
* @param exp (Int|Float) - The exponent value.
* @returns (Float) - The exponentiated value.
*/
func Pow(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Pow(toFloat(args[0]), toFloat(args[1]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestPow(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Pow(context.Background(), values.NewInt(2), values.NewInt(4))
So(err, ShouldBeNil)
So(out, ShouldEqual, 16)
out, err = math.Pow(context.Background(), values.NewInt(5), values.NewInt(-1))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0.2)
out, err = math.Pow(context.Background(), values.NewInt(5), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
})
}

View File

@ -0,0 +1,30 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the angle converted from degrees to radians.
* @param number (Float|Int) - The input number.
* @returns (Float) - The angle in radians.
*/
func Radians(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
r := toFloat(args[0])
return values.NewFloat(r * DegToRad), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestRadians(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Radians(context.Background(), values.NewInt(180))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3.141592653589793)
out, err = math.Radians(context.Background(), values.NewFloat(90))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1.5707963267948966)
out, err = math.Radians(context.Background(), values.NewFloat(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
})
}

22
pkg/stdlib/math/rand.go Normal file
View File

@ -0,0 +1,22 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math/rand"
)
/*
* Return a pseudo-random number between 0 and 1.
* @returns (Float) - A number greater than 0 and less than 1.
*/
func Rand(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 0, 0)
if err != nil {
return values.None, err
}
return values.NewFloat(rand.Float64()), nil
}

View File

@ -0,0 +1,17 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/stdlib/math"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestRand(t *testing.T) {
Convey("Should return pseudo-random value", t, func() {
out, err := math.Rand(context.Background())
So(err, ShouldBeNil)
So(out, ShouldBeLessThan, 1)
})
}

56
pkg/stdlib/math/range.go Normal file
View File

@ -0,0 +1,56 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns an array of numbers in the specified range, optionally with increments other than 1.
* @param start (Int|Float) - The value to start the range at (inclusive).
* @param end (Int|Float) - The value to end the range with (inclusive).
* @param step (Int|Float, optional) - How much to increment in every step, the default is 1.0.
*/
func Range(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
var step float64 = 1
if len(args) > 2 {
err = core.ValidateType(args[2], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
step = toFloat(args[2])
}
start := toFloat(args[0])
end := toFloat(args[1])
arr := values.NewArray(int(end))
for i := start; i <= end; i += step {
arr.Push(values.NewFloat(i))
}
return arr, nil
}

View File

@ -0,0 +1,62 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestRange(t *testing.T) {
Convey("Should return range of numbers", t, func() {
out, err := math.Range(context.Background(), values.NewInt(1), values.NewInt(4))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,2,3,4]")
out, err = math.Range(context.Background(),
values.NewInt(1),
values.NewInt(4),
values.NewInt(2))
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,3]")
out, err = math.Range(context.Background(),
values.NewInt(1),
values.NewInt(4),
values.NewInt(3),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1,4]")
out, err = math.Range(context.Background(),
values.NewFloat(1.5),
values.NewFloat(2.5),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1.5,2.5]")
out, err = math.Range(context.Background(),
values.NewFloat(1.5),
values.NewFloat(2.5),
values.NewFloat(0.5),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[1.5,2,2.5]")
out, err = math.Range(context.Background(),
values.NewFloat(-0.75),
values.NewFloat(1.1),
values.NewFloat(0.5),
)
So(err, ShouldBeNil)
So(out.String(), ShouldEqual, "[-0.75,-0.25,0.25,0.75]")
})
}

29
pkg/stdlib/math/round.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the nearest integer, rounding half away from zero.
* @param number (Int|Float) - Input number.
* @returns (Int) - The nearest integer, rounding half away from zero.
*/
func Round(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewInt(int(math.Round(toFloat(args[0])))), nil
}

View File

@ -0,0 +1,34 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestRound(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.Round(context.Background(), values.NewFloat(2.49))
So(err, ShouldBeNil)
So(out, ShouldEqual, 2)
out, err = math.Round(context.Background(), values.NewFloat(2.50))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
out, err = math.Ceil(context.Background(), values.NewFloat(-2.50))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -2)
out, err = math.Ceil(context.Background(), values.NewFloat(-2.49))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -2)
})
}

29
pkg/stdlib/math/sin.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the sine of the radian argument.
* @param number (Int|Float) - Input number.
* @returns (Float) - The sin, in radians, of a given number.
*/
func Sin(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Sin(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,36 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSin(t *testing.T) {
Convey("Should return sin value", t, func() {
out, err := math.Sin(context.Background(), values.NewFloat(3.141592653589783/2))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1)
out, err = math.Sin(context.Background(), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
out, err = math.Sin(context.Background(), values.NewFloat(-3.141592653589783/2))
So(err, ShouldBeNil)
So(out, ShouldEqual, -1)
v, _ := math.Radians(context.Background(), values.NewInt(270))
out, err = math.Sin(context.Background(), v)
So(err, ShouldBeNil)
So(out, ShouldEqual, -1)
})
}

29
pkg/stdlib/math/sqrt.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the square root of a given number.
* @param value (Int|Float) - A number.
* @returns (Float) - The square root.
*/
func Sqrt(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Sqrt(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,24 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSqrt(t *testing.T) {
Convey("Should return square value", t, func() {
out, err := math.Sqrt(context.Background(), values.NewFloat(9))
So(err, ShouldBeNil)
So(out, ShouldEqual, 3)
out, err = math.Sqrt(context.Background(), values.NewInt(2))
So(err, ShouldBeNil)
So(out, ShouldEqual, 1.4142135623730951)
})
}

View File

@ -0,0 +1,42 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the population standard deviation of the values in a given array.
* @params (Array) - Array of numbers.
* @returns (Float) - The population standard deviation.
*/
func StandardDeviationPopulation(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
vp, err := variance(arr, values.NewInt(0))
if err != nil {
return values.NewFloat(math.NaN()), err
}
return values.NewFloat(math.Pow(float64(vp), 0.5)), nil
}

View File

@ -0,0 +1,28 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStandardDeviationPopulation(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.StandardDeviationPopulation(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(3),
values.NewInt(6),
values.NewInt(5),
values.NewInt(2),
),
)
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 1.8547236990991407)
})
}

View File

@ -0,0 +1,42 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the sample standard deviation of the values in a given array.
* @params (Array) - Array of numbers.
* @returns (Float) - The sample standard deviation.
*/
func StandardDeviationSample(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
vp, err := variance(arr, values.NewInt(1))
if err != nil {
return values.NewFloat(math.NaN()), err
}
return values.NewFloat(math.Pow(float64(vp), 0.5)), nil
}

View File

@ -0,0 +1,28 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStandardDeviationSample(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.StandardDeviationSample(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(3),
values.NewInt(6),
values.NewInt(5),
values.NewInt(2),
),
)
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 2.073644135332772)
})
}

53
pkg/stdlib/math/sum.go Normal file
View File

@ -0,0 +1,53 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Returns the sum of the values in a given array.
* @param array (Array) - Array of numbers.
* @returns (Float) - The sum of the values.
*/
func Sum(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.None, nil
}
var sum float64
arr.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.FloatType, core.IntType)
if err != nil {
return false
}
sum += toFloat(value)
return true
})
if err != nil {
return values.None, nil
}
return values.NewFloat(sum), nil
}

View File

@ -0,0 +1,33 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSum(t *testing.T) {
Convey("Should return sum of values", t, func() {
out, err := math.Sum(context.Background(), values.NewArrayWith(
values.NewInt(5),
values.NewInt(2),
values.NewInt(9),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, 18)
out, err = math.Sum(context.Background(), values.NewArrayWith(
values.NewInt(-3),
values.NewInt(-5),
values.NewInt(2),
))
So(err, ShouldBeNil)
So(out, ShouldEqual, -6)
})
}

29
pkg/stdlib/math/tan.go Normal file
View File

@ -0,0 +1,29 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the tangent of a given number.
* @param value (Int|Float) - A number.
* @returns (Float) - The tangent.
*/
func Tan(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.IntType, core.FloatType)
if err != nil {
return values.None, err
}
return values.NewFloat(math.Tan(toFloat(args[0]))), nil
}

View File

@ -0,0 +1,29 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestTan(t *testing.T) {
Convey("Should return tan value", t, func() {
out, err := math.Tan(context.Background(), values.NewFloat(10))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 0.6483608274590867)
out, err = math.Tan(context.Background(), values.NewInt(5))
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, -3.3805150062465854)
out, err = math.Tan(context.Background(), values.NewInt(0))
So(err, ShouldBeNil)
So(out, ShouldEqual, 0)
})
}

View File

@ -0,0 +1,39 @@
package math
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
func variance(input *values.Array, sample values.Int) (values.Float, error) {
if input.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
m, _ := mean(input)
var err error
var variance values.Float
input.ForEach(func(value core.Value, idx int) bool {
err = core.ValidateType(value, core.IntType, core.FloatType)
if err != nil {
return false
}
n := values.Float(toFloat(value))
variance += (n - m) * (n - m)
return true
})
// When getting the mean of the squared differences
// "sample" will allow us to know if it's a sample
// or population and wether to subtract by one or not
l := values.Float(input.Length() - (1 * sample))
return variance / l, nil
}

View File

@ -0,0 +1,36 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the population variance of the values in a given array.
* @params (Array) - Array of numbers.
* @returns (Float) - The population variance.
*/
func PopulationVariance(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
return variance(arr, values.NewInt(0))
}

View File

@ -0,0 +1,28 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestPopulationVariance(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.PopulationVariance(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(3),
values.NewInt(6),
values.NewInt(5),
values.NewInt(2),
),
)
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 3.44)
})
}

View File

@ -0,0 +1,36 @@
package math
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"math"
)
/*
* Returns the sample variance of the values in a given array.
* @params (Array) - Array of numbers.
* @returns (Float) - The sample variance.
*/
func SampleVariance(_ context.Context, args ...core.Value) (core.Value, error) {
var err error
err = core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], core.ArrayType)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
if arr.Length() == 0 {
return values.NewFloat(math.NaN()), nil
}
return variance(arr, values.NewInt(1))
}

View File

@ -0,0 +1,28 @@
package math_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSampleVariance(t *testing.T) {
Convey("Should return a value", t, func() {
out, err := math.SampleVariance(
context.Background(),
values.NewArrayWith(
values.NewInt(1),
values.NewInt(3),
values.NewInt(6),
values.NewInt(5),
values.NewInt(2),
),
)
So(err, ShouldBeNil)
So(out.Unwrap(), ShouldEqual, 4.3)
})
}

View File

@ -0,0 +1,26 @@
package types
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
/*
* Checks whether value is NaN.
* @param value (Value) - Input value of arbitrary type.
* @returns (Boolean) - Returns true if value is NaN, otherwise false.
*/
func IsNaN(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
if args[0].Type() != core.FloatType {
return values.False, nil
}
return values.IsNaN(args[0].(values.Float)), nil
}

View File

@ -24,6 +24,7 @@ func NewLib() map[string]core.Function {
"IS_HTML_ELEMENT": IsHTMLElement,
"IS_HTML_DOCUMENT": IsHTMLDocument,
"IS_BINARY": IsBinary,
"IS_NAN": IsNaN,
"TYPENAME": TypeName,
}
}