mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-20 21:31:32 +02:00
math: Add trigonometric functions and some angle helper functions
This commit adds these new template functions in the `math` namespace: math.Acos math.Asin math.Atan math.Atan2 math.Cos math.Pi math.Sin math.Tan math.ToDegrees math.ToRadians Co-authored-by: Joe Mooring <joe@mooring.com>
This commit is contained in:
@@ -38,6 +38,13 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Acos,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Acos 1 }}", "0"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Add,
|
||||
[]string{"add"},
|
||||
[][2]string{
|
||||
@@ -45,6 +52,27 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Asin,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Asin 1 }}", "1.5707963267948966"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Atan,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Atan 1 }}", "0.7853981633974483"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Atan2,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Atan2 1 2 }}", "0.4636476090008061"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Ceil,
|
||||
nil,
|
||||
[][2]string{
|
||||
@@ -52,6 +80,13 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Cos,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Cos 1 }}", "0.5403023058681398"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Div,
|
||||
[]string{"div"},
|
||||
[][2]string{
|
||||
@@ -108,6 +143,13 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Pi,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Pi }}", "3.141592653589793"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Pow,
|
||||
[]string{"pow"},
|
||||
[][2]string{
|
||||
@@ -129,6 +171,13 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Sin,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Sin 1 }}", "0.8414709848078965"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Sqrt,
|
||||
nil,
|
||||
[][2]string{
|
||||
@@ -143,6 +192,27 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Tan,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.Tan 1 }}", "1.557407724654902"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.ToDegrees,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.ToDegrees 1.5707963267948966 }}", "90"},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.ToRadians,
|
||||
nil,
|
||||
[][2]string{
|
||||
{"{{ math.ToRadians 90 }}", "1.5707963267948966"},
|
||||
},
|
||||
)
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
|
124
tpl/math/math.go
124
tpl/math/math.go
@@ -49,11 +49,51 @@ func (ns *Namespace) Abs(n any) (float64, error) {
|
||||
return math.Abs(af), nil
|
||||
}
|
||||
|
||||
// Acos returns the arccosine, in radians, of n.
|
||||
func (ns *Namespace) Acos(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Acos(af), nil
|
||||
}
|
||||
|
||||
// Add adds the multivalued addends n1 and n2 or more values.
|
||||
func (ns *Namespace) Add(inputs ...any) (any, error) {
|
||||
return ns.doArithmetic(inputs, '+')
|
||||
}
|
||||
|
||||
// Asin returns the arcsine, in radians, of n.
|
||||
func (ns *Namespace) Asin(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Asin(af), nil
|
||||
}
|
||||
|
||||
// Atan returns the arctangent, in radians, of n.
|
||||
func (ns *Namespace) Atan(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Atan(af), nil
|
||||
}
|
||||
|
||||
// Atan2 returns the arc tangent of n/m, using the signs of the two to determine the quadrant of the return value.
|
||||
func (ns *Namespace) Atan2(n, m any) (float64, error) {
|
||||
afx, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires numeric arguments")
|
||||
}
|
||||
afy, err := cast.ToFloat64E(m)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires numeric arguments")
|
||||
}
|
||||
return math.Atan2(afx, afy), nil
|
||||
}
|
||||
|
||||
// Ceil returns the least integer value greater than or equal to n.
|
||||
func (ns *Namespace) Ceil(n any) (float64, error) {
|
||||
xf, err := cast.ToFloat64E(n)
|
||||
@@ -64,6 +104,15 @@ func (ns *Namespace) Ceil(n any) (float64, error) {
|
||||
return math.Ceil(xf), nil
|
||||
}
|
||||
|
||||
// Cos returns the cosine of the radian argument n.
|
||||
func (ns *Namespace) Cos(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Cos(af), nil
|
||||
}
|
||||
|
||||
// Div divides n1 by n2.
|
||||
func (ns *Namespace) Div(inputs ...any) (any, error) {
|
||||
return ns.doArithmetic(inputs, '/')
|
||||
@@ -99,22 +148,6 @@ func (ns *Namespace) Min(inputs ...any) (minimum float64, err error) {
|
||||
return ns.applyOpToScalarsOrSlices("Min", math.Min, inputs...)
|
||||
}
|
||||
|
||||
// Sum returns the sum of all numbers in inputs. Any slices in inputs are flattened.
|
||||
func (ns *Namespace) Sum(inputs ...any) (sum float64, err error) {
|
||||
fn := func(x, y float64) float64 {
|
||||
return x + y
|
||||
}
|
||||
return ns.applyOpToScalarsOrSlices("Sum", fn, inputs...)
|
||||
}
|
||||
|
||||
// Product returns the product of all numbers in inputs. Any slices in inputs are flattened.
|
||||
func (ns *Namespace) Product(inputs ...any) (product float64, err error) {
|
||||
fn := func(x, y float64) float64 {
|
||||
return x * y
|
||||
}
|
||||
return ns.applyOpToScalarsOrSlices("Product", fn, inputs...)
|
||||
}
|
||||
|
||||
// Mod returns n1 % n2.
|
||||
func (ns *Namespace) Mod(n1, n2 any) (int64, error) {
|
||||
ai, erra := cast.ToInt64E(n1)
|
||||
@@ -146,6 +179,11 @@ func (ns *Namespace) Mul(inputs ...any) (any, error) {
|
||||
return ns.doArithmetic(inputs, '*')
|
||||
}
|
||||
|
||||
// Pi returns the mathematical constant pi.
|
||||
func (ns *Namespace) Pi() float64 {
|
||||
return math.Pi
|
||||
}
|
||||
|
||||
// Pow returns n1 raised to the power of n2.
|
||||
func (ns *Namespace) Pow(n1, n2 any) (float64, error) {
|
||||
af, erra := cast.ToFloat64E(n1)
|
||||
@@ -158,6 +196,14 @@ func (ns *Namespace) Pow(n1, n2 any) (float64, error) {
|
||||
return math.Pow(af, bf), nil
|
||||
}
|
||||
|
||||
// Product returns the product of all numbers in inputs. Any slices in inputs are flattened.
|
||||
func (ns *Namespace) Product(inputs ...any) (product float64, err error) {
|
||||
fn := func(x, y float64) float64 {
|
||||
return x * y
|
||||
}
|
||||
return ns.applyOpToScalarsOrSlices("Product", fn, inputs...)
|
||||
}
|
||||
|
||||
// Rand returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0).
|
||||
func (ns *Namespace) Rand() float64 {
|
||||
return rand.Float64()
|
||||
@@ -173,6 +219,15 @@ func (ns *Namespace) Round(n any) (float64, error) {
|
||||
return _round(xf), nil
|
||||
}
|
||||
|
||||
// Sin returns the sine of the radian argument n.
|
||||
func (ns *Namespace) Sin(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Sin(af), nil
|
||||
}
|
||||
|
||||
// Sqrt returns the square root of the number n.
|
||||
func (ns *Namespace) Sqrt(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
@@ -188,6 +243,43 @@ func (ns *Namespace) Sub(inputs ...any) (any, error) {
|
||||
return ns.doArithmetic(inputs, '-')
|
||||
}
|
||||
|
||||
// Sum returns the sum of all numbers in inputs. Any slices in inputs are flattened.
|
||||
func (ns *Namespace) Sum(inputs ...any) (sum float64, err error) {
|
||||
fn := func(x, y float64) float64 {
|
||||
return x + y
|
||||
}
|
||||
return ns.applyOpToScalarsOrSlices("Sum", fn, inputs...)
|
||||
}
|
||||
|
||||
// Tan returns the tangent of the radian argument n.
|
||||
func (ns *Namespace) Tan(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
return math.Tan(af), nil
|
||||
}
|
||||
|
||||
// ToDegrees converts radians into degrees.
|
||||
func (ns *Namespace) ToDegrees(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
|
||||
return af * 180 / math.Pi, nil
|
||||
}
|
||||
|
||||
// ToRadians converts degrees into radians.
|
||||
func (ns *Namespace) ToRadians(n any) (float64, error) {
|
||||
af, err := cast.ToFloat64E(n)
|
||||
if err != nil {
|
||||
return 0, errors.New("requires a numeric argument")
|
||||
}
|
||||
|
||||
return af * math.Pi / 180, nil
|
||||
}
|
||||
|
||||
func (ns *Namespace) applyOpToScalarsOrSlices(opName string, op func(x, y float64) float64, inputs ...any) (result float64, err error) {
|
||||
var i int
|
||||
var hasValue bool
|
||||
|
@@ -547,3 +547,335 @@ func TestProduct(t *testing.T) {
|
||||
_, err := ns.Product()
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
// Test trigonometric functions
|
||||
|
||||
func TestPi(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
ns := New()
|
||||
|
||||
expect := 3.1415
|
||||
result := ns.Pi()
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(result, qt.Equals, expect)
|
||||
}
|
||||
|
||||
func TestSin(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
a any
|
||||
expect any
|
||||
}{
|
||||
{0, 0.0},
|
||||
{1, 0.8414},
|
||||
{math.Pi / 2, 1.0},
|
||||
{math.Pi, 0.0},
|
||||
{-1.0, -0.8414},
|
||||
{"abc", false},
|
||||
} {
|
||||
|
||||
result, err := ns.Sin(test.a)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCos(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
a any
|
||||
expect any
|
||||
}{
|
||||
{0, 1.0},
|
||||
{1, 0.5403},
|
||||
{math.Pi / 2, 0.0},
|
||||
{math.Pi, -1.0},
|
||||
{-1.0, 0.5403},
|
||||
{"abc", false},
|
||||
} {
|
||||
|
||||
result, err := ns.Cos(test.a)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTan(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
a any
|
||||
expect any
|
||||
}{
|
||||
{0, 0.0},
|
||||
{1, 1.5574},
|
||||
// {math.Pi / 2, math.Inf(1)},
|
||||
{math.Pi, 0.0},
|
||||
{-1.0, -1.5574},
|
||||
{"abc", false},
|
||||
} {
|
||||
|
||||
result, err := ns.Tan(test.a)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
if result != math.Inf(1) {
|
||||
result = float64(int(result*10000)) / 10000
|
||||
}
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
|
||||
// Separate test for Tan(oo) -- returns NaN
|
||||
result, err := ns.Tan(math.Inf(1))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Satisfies, math.IsNaN)
|
||||
}
|
||||
|
||||
// Test inverse trigonometric functions
|
||||
|
||||
func TestAsin(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
expect any
|
||||
}{
|
||||
{0.0, 0.0},
|
||||
{1.0, 1.5707},
|
||||
{-1.0, -1.5707},
|
||||
{0.5, 0.5235},
|
||||
{"abc", false},
|
||||
} {
|
||||
result, err := ns.Asin(test.x)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
|
||||
// Separate test for Asin(2) -- returns NaN
|
||||
result, err := ns.Asin(2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Satisfies, math.IsNaN)
|
||||
}
|
||||
|
||||
func TestAcos(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
expect any
|
||||
}{
|
||||
{1.0, 0.0},
|
||||
{0.0, 1.5707},
|
||||
{-1.0, 3.1415},
|
||||
{0.5, 1.0471},
|
||||
{"abc", false},
|
||||
} {
|
||||
result, err := ns.Acos(test.x)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
|
||||
// Separate test for Acos(2) -- returns NaN
|
||||
result, err := ns.Acos(2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Satisfies, math.IsNaN)
|
||||
}
|
||||
|
||||
func TestAtan(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
expect any
|
||||
}{
|
||||
{0.0, 0.0},
|
||||
{1, 0.7853},
|
||||
{-1.0, -0.7853},
|
||||
{math.Inf(1), 1.5707},
|
||||
{"abc", false},
|
||||
} {
|
||||
result, err := ns.Atan(test.x)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtan2(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
y any
|
||||
expect any
|
||||
}{
|
||||
{1.0, 1.0, 0.7853},
|
||||
{-1.0, 1.0, -0.7853},
|
||||
{1.0, -1.0, 2.3561},
|
||||
{-1.0, -1.0, -2.3561},
|
||||
{1, 0, 1.5707},
|
||||
{-1, 0, -1.5707},
|
||||
{0, 1, 0.0},
|
||||
{0, -1, 3.1415},
|
||||
{0.0, 0.0, 0.0},
|
||||
{"abc", "def", false},
|
||||
} {
|
||||
result, err := ns.Atan2(test.x, test.y)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
// Test angle helper functions
|
||||
|
||||
func TestToDegrees(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
expect any
|
||||
}{
|
||||
{0.0, 0.0},
|
||||
{1, 57.2957},
|
||||
{math.Pi / 2, 90.0},
|
||||
{math.Pi, 180.0},
|
||||
{"abc", false},
|
||||
} {
|
||||
result, err := ns.ToDegrees(test.x)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToRadians(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
ns := New()
|
||||
|
||||
for _, test := range []struct {
|
||||
x any
|
||||
expect any
|
||||
}{
|
||||
{0, 0.0},
|
||||
{57.29577951308232, 1.0},
|
||||
{90, 1.5707},
|
||||
{180.0, 3.1415},
|
||||
{"abc", false},
|
||||
} {
|
||||
result, err := ns.ToRadians(test.x)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
continue
|
||||
}
|
||||
|
||||
// we compare only 4 digits behind point if its a real float
|
||||
// otherwise we usually get different float values on the last positions
|
||||
result = float64(int(result*10000)) / 10000
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(result, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user