Repository: google/starlark-go (go)

Issue:
a math library
Starlark seem mighty terrific. I just have a minor query about doing some math.

In starlark-go, how would one raise n to the m-th power?

In Go, I would call math.Pow(n, m). In Python, math.pow(n, m).

Another way of asking: is there a way to get at the math library from Go perhaps?

Pull request description:
add math module
closes #78.

cc @dustmop, @adonovan, & @essobedo

Patch:
diff --git a/cmd/starlark/starlark.go b/cmd/starlark/starlark.go
index fb53555..65a8bfa 100644
--- a/cmd/starlark/starlark.go
+++ b/cmd/starlark/starlark.go
@@ -16,6 +16,7 @@ import (
 	"strings"
 
 	"go.starlark.net/internal/compile"
+	"go.starlark.net/lib/math"
 	"go.starlark.net/lib/time"
 	"go.starlark.net/repl"
 	"go.starlark.net/resolve"
@@ -93,6 +94,7 @@ func doMain() int {
 	// TODO(adonovan): plumb predeclared env through to the REPL.
 	starlark.Universe["json"] = starlarkjson.Module
 	starlark.Universe["time"] = time.Module
+	starlark.Universe["math"] = math.Module
 
 	switch {
 	case flag.NArg() == 1 || *execprog != "":
diff --git a/lib/math/math.go b/lib/math/math.go
new file mode 100644
index 0000000..d72eeef
--- /dev/null
+++ b/lib/math/math.go
@@ -0,0 +1,171 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package math provides basic constants and mathematical functions.
+package math // import "go.starlark.net/lib/math"
+
+import (
+	"errors"
+	"fmt"
+	"math"
+
+	"go.starlark.net/starlark"
+	"go.starlark.net/starlarkstruct"
+)
+
+// Module math is a Starlark module of math-related functions and constants.
+// The module defines the following functions:
+//
+//     abs(x) - Returns the absolute value of x.
+//     ceil(x) - Returns the ceiling of x, the smallest integer greater than or equal to x.
+//     copysign(x, y) - Returns a value with the magnitude of x and the sign of y.
+//     floor(x) - Returns the floor of x, the largest integer less than or equal to x.
+//     mod(x, y) - Returns the floating-point remainder of x/y. The magnitude of the result is less than y and its sign agrees with that of x.
+//     pow(x, y) - Returns x**y, the base-x exponential of y.
+//     remainder(x, y) - Returns the IEEE 754 floating-point remainder of x/y.
+//     round(x) - Returns the nearest integer, rounding half away from zero.
+//
+//     exp(x) - Returns e raised to the power x, where e = 2.718281… is the base of natural logarithms.
+//     sqrt(x) - Returns the square root of x.
+//
+//     acos(x) - Returns the arc cosine of x, in radians.
+//     asin(x) - Returns the arc sine of x, in radians.
+//     atan(x) - Returns the arc tangent of x, in radians.
+//     atan2(y, x) - Returns atan(y / x), in radians.
+//                   The result is between -pi and pi.
+//                   The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis.
+//                   The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct
+//                   quadrant for the angle.
+//                   For example, atan(1) and atan2(1, 1) are both pi/4, but atan2(-1, -1) is -3*pi/4.
+//     cos(x) - Returns the cosine of x, in radians.
+//     hypot(x, y) - Returns the Euclidean norm, sqrt(x*x + y*y). This is the length of the vector from the origin to point (x, y).
+//     sin(x) - Returns the sine of x, in radians.
+//     tan(x) - Returns the tangent of x, in radians.
+//
+//     degrees(x) - Converts angle x from radians to degrees.
+//     radians(x) - Converts angle x from degrees to radians.
+//
+//     acosh(x) - Returns the inverse hyperbolic cosine of x.
+//     asinh(x) - Returns the inverse hyperbolic sine of x.
+//     atanh(x) - Returns the inverse hyperbolic tangent of x.
+//     cosh(x) - Returns the hyperbolic cosine of x.
+//     sinh(x) - Returns the hyperbolic sine of x.
+//     tanh(x) - Returns the hyperbolic tangent of x.
+//
+//     log(x, base) - Returns the logarithm of x in the given base, or natural logarithm by default.
+//
+//     gamma(x) - Returns the Gamma function of x.
+//
+// All functions accept both int and float values as arguments.
+//
+// The module also defines approximations of the following constants:
+//
+//     e - The base of natural logarithms, approximately 2.71828.
+//     pi - The ratio of a circle's circumference to its diameter, approximately 3.14159.
+//
+var Module = &starlarkstruct.Module{
+	Name: "math",
+	Members: starlark.StringDict{
+		"abs":       newUnaryBuiltin("abs", math.Abs),
+		"ceil":      newUnaryBuiltin("ceil", math.Ceil),
+		"copysign":  newBinaryBuiltin("copysign", math.Copysign),
+		"floor":     newUnaryBuiltin("floor", math.Floor),
+		"mod":       newBinaryBuiltin("round", math.Mod),
+		"pow":       newBinaryBuiltin("pow", math.Pow),
+		"remainder": newBinaryBuiltin("remainder", math.Remainder),
+		"round":     newUnaryBuiltin("round", math.Round),
+
+		"exp":  newUnaryBuiltin("exp", math.Exp),
+		"sqrt": newUnaryBuiltin("sqrt", math.Sqrt),
+
+		"acos":  newUnaryBuiltin("acos", math.Acos),
+		"asin":  newUnaryBuiltin("asin", math.Asin),
+		"atan":  newUnaryBuiltin("atan", math.Atan),
+		"atan2": newBinaryBuiltin("atan2", math.Atan2),
+		"cos":   newUnaryBuiltin("cos", math.Cos),
+		"hypot": newBinaryBuiltin("hypot", math.Hypot),
+		"sin":   newUnaryBuiltin("sin", math.Sin),
+		"tan":   newUnaryBuiltin("tan", math.Tan),
+
+		"degrees": newUnaryBuiltin("degrees", degrees),
+		"radians": newUnaryBuiltin("radians", radians),
+
+		"acosh": newUnaryBuiltin("acosh", math.Acosh),
+		"asinh": newUnaryBuiltin("asinh", math.Asinh),
+		"atanh": newUnaryBuiltin("atanh", math.Atanh),
+		"cosh":  newUnaryBuiltin("cosh", math.Cosh),
+		"sinh":  newUnaryBuiltin("sinh", math.Sinh),
+		"tanh":  newUnaryBuiltin("tanh", math.Tanh),
+
+		"log": starlark.NewBuiltin("log", log),
+
+		"gamma": newUnaryBuiltin("gamma", math.Gamma),
+
+		"e":  starlark.Float(math.E),
+		"pi": starlark.Float(math.Pi),
+	},
+}
+
+// floatOrInt is an Unpacker that converts a Starlark int or float to Go's float64.
+type floatOrInt float64
+
+func (p *floatOrInt) Unpack(v starlark.Value) error {
+	switch v := v.(type) {
+	case starlark.Int:
+		*p = floatOrInt(v.Float())
+		return nil
+	case starlark.Float:
+		*p = floatOrInt(v)
+		return nil
+	}
+	return fmt.Errorf("got %s, want float or int", v.Type())
+}
+
+// newUnaryBuiltin wraps a unary floating-point Go function
+// as a Starlark built-in that accepts int or float arguments.
+func newUnaryBuiltin(name string, fn func(float64) float64) *starlark.Builtin {
+	return starlark.NewBuiltin(name, func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
+		var x floatOrInt
+		if err := starlark.UnpackPositionalArgs(name, args, kwargs, 1, &x); err != nil {
+			return nil, err
+		}
+		return starlark.Float(fn(float64(x))), nil
+	})
+}
+
+// newBinaryBuiltin wraps a binary floating-point Go function
+// as a Starlark built-in that accepts int or float arguments.
+func newBinaryBuiltin(name string, fn func(float64, float64) float64) *starlark.Builtin {
+	return starlark.NewBuiltin(name, func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
+		var x, y floatOrInt
+		if err := starlark.UnpackPositionalArgs(name, args, kwargs, 2, &x, &y); err != nil {
+			return nil, err
+		}
+		return starlark.Float(fn(float64(x), float64(y))), nil
+	})
+}
+
+//  log wraps the Log function
+// as a Starlark built-in that accepts int or float arguments.
+func log(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
+	var (
+		x    floatOrInt
+		base floatOrInt = math.E
+	)
+	if err := starlark.UnpackPositionalArgs("log", args, kwargs, 1, &x, &base); err != nil {
+		return nil, err
+	}
+	if base == 1 {
+		return nil, errors.New("division by zero")
+	}
+	return starlark.Float(math.Log(float64(x)) / math.Log(float64(base))), nil
+}
+
+func degrees(x float64) float64 {
+	return 360 * x / (2 * math.Pi)
+}
+
+func radians(x float64) float64 {
+	return 2 * math.Pi * x / 360
+}


Test patch:
diff --git a/starlark/eval_test.go b/starlark/eval_test.go
index 7fbba43..f0ff412 100644
--- a/starlark/eval_test.go
+++ b/starlark/eval_test.go
@@ -16,6 +16,7 @@ import (
 	"testing"
 
 	"go.starlark.net/internal/chunkedfile"
+	starlarkmath "go.starlark.net/lib/math"
 	"go.starlark.net/lib/time"
 	"go.starlark.net/resolve"
 	"go.starlark.net/starlark"
@@ -124,6 +125,7 @@ func TestExecFile(t *testing.T) {
 		"testdata/int.star",
 		"testdata/json.star",
 		"testdata/list.star",
+		"testdata/math.star",
 		"testdata/misc.star",
 		"testdata/set.star",
 		"testdata/string.star",
@@ -198,6 +200,9 @@ func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
 	if module == "time.star" {
 		return starlark.StringDict{"time": time.Module}, nil
 	}
+	if module == "math.star" {
+		return starlark.StringDict{"math": starlarkmath.Module}, nil
+	}
 
 	// TODO(adonovan): test load() using this execution path.
 	filename := filepath.Join(filepath.Dir(thread.CallFrame(0).Pos.Filename()), module)
diff --git a/starlark/testdata/math.star b/starlark/testdata/math.star
new file mode 100644
index 0000000..92a70e1
--- /dev/null
+++ b/starlark/testdata/math.star
@@ -0,0 +1,361 @@
+# Tests of math module.
+
+load('math.star', 'math')
+load('assert.star', 'assert')
+
+def near(got, want, threshold):
+  return math.abs(got-want) < threshold 
+
+inf, nan = float("inf"), float("nan")
+
+# ceil
+assert.eq(math.ceil(0.0), 0.0)
+assert.eq(math.ceil(0.4), 1.0)
+assert.eq(math.ceil(0.5), 1.0)
+assert.eq(math.ceil(1.0), 1.0)
+assert.eq(math.ceil(10.0), 10.0)
+assert.eq(math.ceil(0), 0.0)
+assert.eq(math.ceil(1), 1.0)
+assert.eq(math.ceil(10), 10.0)
+assert.eq(math.ceil(inf), inf)
+assert.eq(math.ceil(nan), nan)
+assert.eq(math.ceil(-0.0), 0.0)
+assert.eq(math.ceil(-0.4), 0.0)
+assert.eq(math.ceil(-0.5), 0.0)
+assert.eq(math.ceil(-1.0), -1.0)
+assert.eq(math.ceil(-10.0), -10.0)
+assert.eq(math.ceil(-1), -1.0)
+assert.eq(math.ceil(-10), -10.0)
+assert.eq(math.ceil(-inf), -inf)
+assert.fails(lambda: math.ceil("0"), "got string, want float or int")
+# abs
+assert.eq(math.abs(2.0), 2.0)
+assert.eq(math.abs(0.0), 0.0)
+assert.eq(math.abs(-2.0), 2.0)
+assert.eq(math.abs(2), 2)
+assert.eq(math.abs(0), 0)
+assert.eq(math.abs(-2), 2)
+assert.eq(math.abs(inf), inf)
+assert.eq(math.abs(-inf), inf)
+assert.eq(math.abs(nan), nan)
+assert.fails(lambda: math.abs("0"), "got string, want float or int")
+# floor
+assert.eq(math.floor(0.0), 0.0)
+assert.eq(math.floor(0.4), 0.0)
+assert.eq(math.floor(0.5), 0.0)
+assert.eq(math.floor(1.0), 1.0)
+assert.eq(math.floor(10.0), 10.0)
+assert.eq(math.floor(inf), inf)
+assert.eq(math.floor(nan), nan)
+assert.eq(math.floor(-0.0), 0.0)
+assert.eq(math.floor(-0.4), -1.0)
+assert.eq(math.floor(-0.5), -1.0)
+assert.eq(math.floor(-1.0), -1.0)
+assert.eq(math.floor(-10.0), -10.0)
+assert.eq(math.floor(-inf), -inf)
+assert.fails(lambda: math.floor("0"), "got string, want float or int")
+# mod
+assert.eq(math.mod(5, 3), 2)
+assert.eq(math.mod(inf, 1), nan)
+assert.eq(math.mod(-inf, 1.0), nan)
+assert.eq(math.mod(nan, 1.0), nan)
+assert.eq(math.mod(1.0, 0.0), nan)
+assert.eq(math.mod(1.0, inf), 1)
+assert.eq(math.mod(1.0, -inf), 1)
+assert.eq(math.mod(1.0, nan), nan)
+assert.fails(lambda: math.mod("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.mod(1.0, "0"), "got string, want float or int")
+# pow
+assert.eq(math.pow(5, 3), 125)
+assert.eq(math.pow(5, 0), 1)
+assert.eq(math.pow(5, 1), 5)
+assert.eq(math.pow(1, 5), 1)
+assert.eq(math.pow(inf, 1), inf)
+assert.eq(math.pow(-inf, 1.0), -inf)
+assert.eq(math.pow(nan, 1.0), nan)
+assert.eq(math.pow(1.1, inf), inf)
+assert.eq(math.pow(1.1, -inf), 0)
+assert.eq(math.pow(2.0, nan), nan)
+assert.fails(lambda: math.pow("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.pow(1.0, "0"), "got string, want float or int")
+# copysign
+assert.eq(math.copysign(3.2, -1), -3.2)
+assert.eq(math.copysign(inf, -1.0),-inf)
+assert.eq(math.copysign(-inf, -1), -inf)
+assert.eq(math.copysign(nan, -1), nan)
+assert.eq(math.copysign(-1, nan), 1)
+assert.fails(lambda: math.copysign("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.copysign(1.0, "0"), "got string, want float or int")
+# remainder
+assert.eq(math.remainder(3, 5), -2)
+assert.eq(math.remainder(1, 0), nan)
+assert.eq(math.remainder(2, inf), 2)
+assert.eq(math.remainder(2, -inf), 2)
+assert.eq(math.remainder(inf, -1.0), nan)
+assert.eq(math.remainder(-inf, -1), nan)
+assert.eq(math.remainder(nan, -1), nan)
+assert.eq(math.remainder(-1, nan), nan)
+assert.fails(lambda: math.remainder("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.remainder(1.0, "0"), "got string, want float or int")
+# round
+assert.eq(math.round(0.0), 0.0)
+assert.eq(math.round(0.4), 0.0)
+assert.eq(math.round(0.5), 1.0)
+assert.eq(math.round(0.6), 1.0)
+assert.eq(math.round(1.0), 1.0)
+assert.eq(math.round(10.0), 10.0)
+assert.eq(math.round(inf), inf)
+assert.eq(math.round(nan), nan)
+assert.eq(math.round(-0.4), 0.0)
+assert.eq(math.round(-0.5), -1.0)
+assert.eq(math.round(-0.6), -1.0)
+assert.eq(math.round(-1.0), -1.0)
+assert.eq(math.round(-10.0), -10.0)
+assert.eq(math.round(-inf), -inf)
+assert.fails(lambda: math.round("0"), "got string, want float or int")
+# exp
+assert.eq(math.exp(0.0), 1)
+assert.eq(math.exp(1.0), math.e)
+assert.true(near(math.exp(2.0), math.e * math.e, 0.00000000000001))
+assert.eq(math.exp(-1.0), 1 / math.e)
+assert.eq(math.exp(0), 1)
+assert.eq(math.exp(1), math.e)
+assert.true(near(math.exp(2), math.e * math.e, 0.00000000000001))
+assert.eq(math.exp(-1), 1 / math.e)
+assert.eq(math.exp(inf), inf)
+assert.eq(math.exp(-inf), 0)
+assert.eq(math.exp(nan), nan)
+assert.fails(lambda: math.exp("0"), "got string, want float or int")
+# sqrt
+assert.eq(math.sqrt(0.0), 0.0)
+assert.eq(math.sqrt(4.0), 2.0)
+assert.eq(math.sqrt(-4.0), nan)
+assert.eq(math.sqrt(0), 0)
+assert.eq(math.sqrt(4), 2)
+assert.eq(math.sqrt(-4), nan)
+assert.eq(math.sqrt(nan), nan)
+assert.eq(math.sqrt(inf), inf)
+assert.eq(math.sqrt(-inf), nan)
+assert.fails(lambda: math.sqrt("0"), "got string, want float or int")
+# acos
+assert.eq(math.acos(1.0), 0)
+assert.eq(math.acos(1), 0)
+assert.eq(math.acos(0.0), math.pi / 2)
+assert.eq(math.acos(0), math.pi / 2)
+assert.eq(math.acos(-1.0), math.pi)
+assert.eq(math.acos(-1), math.pi)
+assert.eq(math.acos(1.01), nan)
+assert.eq(math.acos(-1.01), nan)
+assert.eq(math.acos(inf), nan)
+assert.eq(math.acos(-inf), nan)
+assert.eq(math.acos(nan), nan)
+assert.fails(lambda: math.acos("0"), "got string, want float or int")
+# asin
+assert.eq(math.asin(0.0), 0)
+assert.eq(math.asin(1.0), math.pi / 2)
+assert.eq(math.asin(-1.0), -math.pi / 2)
+assert.eq(math.asin(0), 0)
+assert.eq(math.asin(1), math.pi / 2)
+assert.eq(math.asin(-1), -math.pi / 2)
+assert.eq(math.asin(1.01), nan)
+assert.eq(math.asin(-1.01), nan)
+assert.eq(math.asin(inf), nan)
+assert.eq(math.asin(-inf), nan)
+assert.eq(math.asin(nan), nan)
+assert.fails(lambda: math.asin("0"), "got string, want float or int")
+# atan
+assert.eq(math.atan(0.0), 0)
+assert.eq(math.atan(1.0), math.pi / 4)
+assert.eq(math.atan(-1.0), -math.pi / 4)
+assert.eq(math.atan(1), math.pi / 4)
+assert.eq(math.atan(-1), -math.pi / 4)
+assert.eq(math.atan(inf), math.pi / 2)
+assert.eq(math.atan(-inf), -math.pi / 2)
+assert.eq(math.atan(nan), nan)
+assert.fails(lambda: math.atan("0"), "got string, want float or int")
+# atan2
+assert.eq(math.atan2(1.0, 1.0), math.pi / 4)
+assert.eq(math.atan2(-1.0, 1.0), -math.pi / 4)
+assert.eq(math.atan2(0.0, 10.0), 0)
+assert.eq(math.atan2(0.0, -10.0), math.pi)
+assert.eq(math.atan2(-0.0, -10.0), -math.pi)
+assert.eq(math.atan2(10.0, 0.0), math.pi / 2)
+assert.eq(math.atan2(-10.0, 0.0), -math.pi / 2)
+assert.eq(math.atan2(1, 1), math.pi / 4)
+assert.eq(math.atan2(-1, 1), -math.pi / 4)
+assert.eq(math.atan2(0, 10.0), 0)
+assert.eq(math.atan2(0.0, -10), math.pi)
+assert.eq(math.atan2(-0.0, -10), -math.pi)
+assert.eq(math.atan2(10.0, 0), math.pi / 2)
+assert.eq(math.atan2(-10.0, 0), -math.pi / 2)
+assert.eq(math.atan2(1.0, nan), nan)
+assert.eq(math.atan2(nan, 1.0), nan)
+assert.eq(math.atan2(10.0, inf), 0)
+assert.eq(math.atan2(-10.0, inf), 0)
+assert.eq(math.atan2(10.0, -inf), math.pi)
+assert.eq(math.atan2(-10.0, -inf), -math.pi)
+assert.eq(math.atan2(inf, 10.0), math.pi / 2)
+assert.eq(math.atan2(inf, -10.0), math.pi / 2)
+assert.eq(math.atan2(-inf, 10.0), -math.pi / 2)
+assert.eq(math.atan2(-inf, -10.0), -math.pi / 2)
+assert.eq(math.atan2(inf, inf), math.pi / 4)
+assert.eq(math.atan2(-inf, inf), -math.pi / 4)
+assert.eq(math.atan2(inf, -inf), 3 * math.pi / 4)
+assert.eq(math.atan2(-inf, -inf), -3 * math.pi / 4)
+assert.fails(lambda: math.atan2("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.atan2(1.0, "0"), "got string, want float or int")
+# cos
+assert.eq(math.cos(0.0), 1)
+assert.true(near(math.cos(math.pi / 2), 0, 0.00000000000001))
+assert.eq(math.cos(math.pi), -1)
+assert.true(near(math.cos(-math.pi / 2), 0, 0.00000000000001))
+assert.eq(math.cos(-math.pi), -1)
+assert.eq(math.cos(inf), nan)
+assert.eq(math.cos(-inf), nan)
+assert.eq(math.cos(nan), nan)
+assert.fails(lambda: math.cos("0"), "got string, want float or int")
+# hypot
+assert.eq(math.hypot(4.0, 3.0), 5.0)
+assert.eq(math.hypot(4, 3), 5.0)
+assert.eq(math.hypot(inf, 3.0), inf)
+assert.eq(math.hypot(-inf, 3.0), inf)
+assert.eq(math.hypot(3.0, inf), inf)
+assert.eq(math.hypot(3.0, -inf), inf)
+assert.eq(math.hypot(nan, 3.0), nan)
+assert.eq(math.hypot(3.0, nan), nan)
+assert.fails(lambda: math.hypot("0", 1.0), "got string, want float or int")
+assert.fails(lambda: math.hypot(1.0, "0"), "got string, want float or int")
+# sin
+assert.eq(math.sin(0.0), 0)
+assert.eq(math.sin(0), 0)
+assert.eq(math.sin(math.pi / 2), 1)
+assert.eq(math.sin(-math.pi / 2), -1)
+assert.eq(math.sin(inf), nan)
+assert.eq(math.sin(-inf), nan)
+assert.eq(math.sin(nan), nan)
+assert.fails(lambda: math.sin("0"), "got string, want float or int")
+# tan
+assert.eq(math.tan(0.0), 0)
+assert.eq(math.tan(0), 0)
+assert.eq(math.tan(math.pi / 4), 1)
+assert.eq(math.tan(-math.pi / 4), -1)
+assert.eq(math.tan(inf), nan)
+assert.eq(math.tan(-inf), nan)
+assert.eq(math.tan(nan), nan)
+assert.fails(lambda: math.tan("0"), "got string, want float or int")
+# degrees
+oneDeg = 57.29577951308232
+assert.eq(math.degrees(1.0), oneDeg)
+assert.eq(math.degrees(1), oneDeg)
+assert.eq(math.degrees(-1.0), -oneDeg)
+assert.eq(math.degrees(-1), -oneDeg)
+assert.eq(math.degrees(inf), inf)
+assert.eq(math.degrees(-inf), -inf)
+assert.eq(math.degrees(nan), nan)
+assert.fails(lambda: math.degrees("0"), "got string, want float or int")
+# radians
+oneRad = 0.017453292519943295
+assert.eq(math.radians(1.0), oneRad)
+assert.eq(math.radians(-1.0), -oneRad)
+assert.eq(math.radians(1), oneRad)
+assert.eq(math.radians(-1), -oneRad)
+assert.eq(math.radians(inf), inf)
+assert.eq(math.radians(-inf), -inf)
+assert.eq(math.radians(nan), nan)
+assert.fails(lambda: math.radians("0"), "got string, want float or int")
+# acosh
+assert.eq(math.acosh(1.0), 0)
+assert.eq(math.acosh(1), 0)
+assert.eq(math.acosh(0.99), nan)
+assert.eq(math.acosh(0), nan)
+assert.eq(math.acosh(-0.99), nan)
+assert.eq(math.acosh(-inf), nan)
+assert.eq(math.acosh(inf), inf)
+assert.eq(math.acosh(nan), nan)
+assert.fails(lambda: math.acosh("0"), "got string, want float or int")
+# asinh
+asinhOne = 0.8813735870195432
+assert.eq(math.asinh(0.0), 0)
+assert.eq(math.asinh(0), 0)
+assert.true(near(math.asinh(1.0), asinhOne, 0.00000001))
+assert.true(near(math.asinh(1), asinhOne, 0.00000001))
+assert.true(near(math.asinh(-1.0), -asinhOne, 0.00000001))
+assert.true(near(math.asinh(-1), -asinhOne, 0.00000001))
+assert.eq(math.asinh(inf), inf)
+assert.eq(math.asinh(-inf), -inf)
+assert.eq(math.asinh(nan), nan)
+assert.fails(lambda: math.asinh("0"), "got string, want float or int")
+# atanh
+atanhHalf = 0.5493061443340548
+assert.eq(math.atanh(0.0), 0)
+assert.eq(math.atanh(0), 0)
+assert.eq(math.atanh(0.5), atanhHalf)
+assert.eq(math.atanh(-0.5), -atanhHalf)
+assert.eq(math.atanh(1), inf)
+assert.eq(math.atanh(-1), -inf)
+assert.eq(math.atanh(1.1), nan)
+assert.eq(math.atanh(-1.1), nan)
+assert.eq(math.atanh(inf), nan)
+assert.eq(math.atanh(-inf), nan)
+assert.eq(math.atanh(nan), nan)
+assert.fails(lambda: math.atanh("0"), "got string, want float or int")
+# cosh
+coshOne = 1.5430806348152437
+assert.eq(math.cosh(1.0), coshOne)
+assert.eq(math.cosh(1), coshOne)
+assert.eq(math.cosh(0.0), 1)
+assert.eq(math.cosh(0), 1)
+assert.eq(math.cosh(-inf), inf)
+assert.eq(math.cosh(inf), inf)
+assert.eq(math.cosh(nan), nan)
+assert.fails(lambda: math.cosh("0"), "got string, want float or int")
+# sinh
+sinhOne = 1.1752011936438014
+assert.eq(math.sinh(0.0), 0)
+assert.eq(math.sinh(0), 0)
+assert.eq(math.sinh(1.0), sinhOne)
+assert.eq(math.sinh(1), sinhOne)
+assert.eq(math.sinh(-1.0), -sinhOne)
+assert.eq(math.sinh(-1), -sinhOne)
+assert.eq(math.sinh(-inf), -inf)
+assert.eq(math.sinh(inf), inf)
+assert.eq(math.sinh(nan), nan)
+assert.fails(lambda: math.sinh("0"), "got string, want float or int")
+# tanh
+tanhOne = 0.7615941559557649
+assert.eq(math.tanh(0.0), 0)
+assert.eq(math.tanh(0), 0)
+assert.eq(math.tanh(1.0), tanhOne)
+assert.eq(math.tanh(1), tanhOne)
+assert.eq(math.tanh(-1.0), -tanhOne)
+assert.eq(math.tanh(-1), -tanhOne)
+assert.eq(math.tanh(-inf), -1)
+assert.eq(math.tanh(inf), 1)
+assert.eq(math.tanh(nan), nan)
+assert.fails(lambda: math.tanh("0"), "got string, want float or int")
+# log
+assert.eq(math.log(math.e), 1)
+assert.eq(math.log(10, 10), 1)
+assert.eq(math.log(10.0, 10.0), 1)
+assert.eq(math.log(2, 2.0), 1)
+assert.fails(lambda: math.log(2, 1), "division by zero")
+assert.fails(lambda: math.log(0.99, 1.0), "division by zero")
+assert.eq(math.log(0.0), -inf)
+assert.eq(math.log(0), -inf)
+assert.eq(math.log(-1.0), nan)
+assert.eq(math.log(-1), nan)
+assert.eq(math.log(nan), nan)
+assert.fails(lambda: math.log("0"), "got string, want float or int")
+assert.fails(lambda: math.log(10, "10"), "got string, want float or int")
+# gamma
+assert.eq(math.gamma(1.0), 1)
+assert.eq(math.gamma(1), 1)
+assert.eq(math.gamma(-1), nan)
+assert.eq(math.gamma(0), inf)
+assert.eq(math.gamma(-inf), nan)
+assert.eq(math.gamma(inf), inf)
+assert.eq(math.gamma(nan), nan)
+assert.fails(lambda: math.gamma("0"), "got string, want float or int")
+# Constants
+assert.eq(math.e, 2.7182818284590452)
+assert.eq(math.pi, 3.1415926535897932)