From 4487bfb8ba7ed7a3d91d79d4a1aa2205e7839558 Mon Sep 17 00:00:00 2001 From: Robert <766670+rben01@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:38:28 -0500 Subject: [PATCH] Added constant pi and several math functions (#1187) Add constant pi and several math functions Added: * pi (up to IEEE 64-bit binary float precision) * deg2rad, rad2deg * atan2 (builtin using C/C++ std library) * hypot (builtin using C/C++ std library) * log2, log10 --- core/desugarer.cpp | 4 ++- core/vm.cpp | 16 +++++++++ doc/_stdlib_gen/stdlib-content.jsonnet | 9 +++++ doc/ref/spec.html | 3 ++ doc/ref/stdlib.html | 5 +++ stdlib/std.jsonnet | 9 +++++ test_suite/stdlib.jsonnet | 48 +++++++++++++++++--------- 7 files changed, 77 insertions(+), 17 deletions(-) diff --git a/core/desugarer.cpp b/core/desugarer.cpp index 611c1545e..3ed2b453e 100644 --- a/core/desugarer.cpp +++ b/core/desugarer.cpp @@ -36,7 +36,7 @@ struct BuiltinDecl { std::vector params; }; -static unsigned long max_builtin = 38; +static unsigned long max_builtin = 40; BuiltinDecl jsonnet_builtin_decl(unsigned long builtin) { switch (builtin) { @@ -79,6 +79,8 @@ BuiltinDecl jsonnet_builtin_decl(unsigned long builtin) case 36: return {U"parseYaml", {U"str"}}; case 37: return {U"encodeUTF8", {U"str"}}; case 38: return {U"decodeUTF8", {U"arr"}}; + case 39: return {U"atan2", {U"y", U"x"}}; + case 40: return {U"hypot", {U"a", U"b"}}; default: std::cerr << "INTERNAL ERROR: Unrecognized builtin function: " << builtin << std::endl; std::abort(); diff --git a/core/vm.cpp b/core/vm.cpp index 7cc0a8655..27629fd67 100644 --- a/core/vm.cpp +++ b/core/vm.cpp @@ -937,6 +937,8 @@ class Interpreter { builtins["parseYaml"] = &Interpreter::builtinParseYaml; builtins["encodeUTF8"] = &Interpreter::builtinEncodeUTF8; builtins["decodeUTF8"] = &Interpreter::builtinDecodeUTF8; + builtins["atan2"] = &Interpreter::builtinAtan2; + builtins["hypot"] = &Interpreter::builtinHypot; DesugaredObject *stdlib = makeStdlibAST(alloc, "__internal__"); jsonnet_static_analysis(stdlib); @@ -1099,6 +1101,20 @@ class Interpreter { return nullptr; } + const AST *builtinAtan2(const LocationRange &loc, const std::vector &args) + { + validateBuiltinArgs(loc, "atan2", args, {Value::NUMBER, Value::NUMBER}); + scratch = makeNumberCheck(loc, std::atan2(args[0].v.d, args[1].v.d)); + return nullptr; + } + + const AST *builtinHypot(const LocationRange &loc, const std::vector &args) + { + validateBuiltinArgs(loc, "hypot", args, {Value::NUMBER, Value::NUMBER}); + scratch = makeNumberCheck(loc, std::hypot(args[0].v.d, args[1].v.d)); + return nullptr; + } + const AST *builtinType(const LocationRange &, const std::vector &args) { switch (args[0].t) { diff --git a/doc/_stdlib_gen/stdlib-content.jsonnet b/doc/_stdlib_gen/stdlib-content.jsonnet index c36e1c5a8..27b1d63e2 100644 --- a/doc/_stdlib_gen/stdlib-content.jsonnet +++ b/doc/_stdlib_gen/stdlib-content.jsonnet @@ -98,6 +98,8 @@ local html = import 'html.libsonnet';
    std.pow(x, n)
    std.exp(x)
    std.log(x)
+
    std.log2(x)
+
    std.log10(x)
    std.exponent(x)
    std.mantissa(x)
    std.floor(x)
@@ -109,12 +111,19 @@ local html = import 'html.libsonnet';
    std.asin(x)
    std.acos(x)
    std.atan(x)
+
    std.atan2(y, x)
+
    std.deg2rad(x)
+
    std.rad2deg(x)
+
    std.hypot(a, b)
    std.round(x)
    std.isEven(x)
    std.isOdd(x)
    std.isInteger(x)
    std.isDecimal(x)
+

+ The constant std.pi is also available. +

The function std.mod(a, b) is what the % operator is desugared to. It performs modulo arithmetic if the left hand side is a number, or if the left hand side is a string, diff --git a/doc/ref/spec.html b/doc/ref/spec.html index 9cfd6c01b..a9d7b852e 100644 --- a/doc/ref/spec.html +++ b/doc/ref/spec.html @@ -2825,7 +2825,10 @@

Execution

std.asin(x), std.acos(x), std.atan(x), + std.atan2(y, x), std.log(x), + std.log2(x), + std.log10(x), std.exp(x), std.mantissa(x), std.exponent(x) and diff --git a/doc/ref/stdlib.html b/doc/ref/stdlib.html index dc18e227d..79703bbbb 100644 --- a/doc/ref/stdlib.html +++ b/doc/ref/stdlib.html @@ -234,6 +234,8 @@

    std.pow(x, n)
    std.exp(x)
    std.log(x)
+
    std.log2(x)
+
    std.log10(x)
    std.exponent(x)
    std.mantissa(x)
    std.floor(x)
@@ -245,6 +247,9 @@

    std.asin(x)
    std.acos(x)
    std.atan(x)
+
    std.atan2(y, x)
+
    std.deg2rad(x)
+
    std.rad2deg(x)
    std.round(x)
    std.isEven(x)
    std.isOdd(x)
diff --git a/stdlib/std.jsonnet b/stdlib/std.jsonnet index fccbe26d5..ec7ab8f4d 100644 --- a/stdlib/std.jsonnet +++ b/stdlib/std.jsonnet @@ -261,6 +261,15 @@ limitations under the License. else error 'Operator % cannot be used on types ' + std.type(a) + ' and ' + std.type(b) + '.', + // this is the most precision that will fit in a f64 + pi:: 3.14159265358979311600, + + deg2rad(x):: x * std.pi / 180, + rad2deg(x):: x * 180 / std.pi, + + log2(x):: std.log(x) / std.log(2), + log10(x):: std.log(x) / std.log(10), + map(func, arr):: if !std.isFunction(func) then error ('std.map first param must be function, got ' + std.type(func)) diff --git a/test_suite/stdlib.jsonnet b/test_suite/stdlib.jsonnet index fc1161ed5..1ea325c68 100644 --- a/test_suite/stdlib.jsonnet +++ b/test_suite/stdlib.jsonnet @@ -56,31 +56,47 @@ std.assertEqual(std.abs(33), 33) && std.assertEqual(std.abs(-33), 33) && std.assertEqual(std.abs(0), 0) && -// Ordinary (non-test) code can define pi as 2*std.acos(0) -local pi = 3.14159265359; - -assertClose(std.sin(0.0 * pi), 0) && -assertClose(std.sin(0.5 * pi), 1) && -assertClose(std.sin(1.0 * pi), 0) && -assertClose(std.sin(1.5 * pi), -1) && -assertClose(std.sin(2.0 * pi), 0) && -assertClose(std.cos(0.0 * pi), 1) && -assertClose(std.cos(0.5 * pi), 0) && -assertClose(std.cos(1.0 * pi), -1) && -assertClose(std.cos(1.5 * pi), 0) && -assertClose(std.cos(2.0 * pi), 1) && +assertClose(std.sin(0.0 * std.pi), 0) && +assertClose(std.sin(0.5 * std.pi), 1) && +assertClose(std.sin(1.0 * std.pi), 0) && +assertClose(std.sin(1.5 * std.pi), -1) && +assertClose(std.sin(2.0 * std.pi), 0) && +assertClose(std.cos(0.0 * std.pi), 1) && +assertClose(std.cos(0.5 * std.pi), 0) && +assertClose(std.cos(1.0 * std.pi), -1) && +assertClose(std.cos(1.5 * std.pi), 0) && +assertClose(std.cos(2.0 * std.pi), 1) && assertClose(std.tan(0), 0) && -assertClose(std.tan(0.25 * pi), 1) && +assertClose(std.tan(0.25 * std.pi), 1) && assertClose(std.asin(0), 0) && assertClose(std.acos(1), 0) && -assertClose(std.asin(1), 0.5 * pi) && -assertClose(std.acos(0), 0.5 * pi) && +assertClose(std.asin(1), 0.5 * std.pi) && +assertClose(std.acos(0), 0.5 * std.pi) && assertClose(std.atan(0), 0) && +assertClose(std.atan2(1, 1), std.pi / 4) && +assertClose(std.atan2(-1, 1), -std.pi / 4) && +assertClose(std.atan2(1.2, -3.8), 2.835713782184941) && // arbitrary, done on a calculator +assertClose(std.deg2rad(0), 0) && +assertClose(std.deg2rad(45), std.pi / 4) && +assertClose(std.deg2rad(90), std.pi / 2) && +assertClose(std.deg2rad(172), 3.0019663134302466) && // arbitrary, done on a calculator +assertClose(std.rad2deg(std.pi / 4), 45) && +assertClose(std.rad2deg(std.pi / 2), 90) && +assertClose(std.rad2deg(3.0019663134302466), 172) && // arbitrary, done on a calculator +assertClose(std.hypot(3, 4), 5) && +assertClose(std.hypot(5, 12), 13) && +assertClose(std.hypot(1, 1), std.sqrt(2)) && assertClose(std.log(std.exp(5)), 5) && assertClose(std.mantissa(1), 0.5) && assertClose(std.exponent(1), 1) && assertClose(std.mantissa(128), 0.5) && assertClose(std.exponent(128), 8) && +assertClose(std.log2(std.pow(2, -5)), -5) && +assertClose(std.log2(std.pow(2, 0)), 0) && +assertClose(std.log2(std.pow(2, std.pi)), std.pi) && +assertClose(std.log10(std.pow(10, -5)), -5) && +assertClose(std.log10(std.pow(10, 0)), 0) && +assertClose(std.log10(std.pow(10, std.pi)), std.pi) && std.assertEqual(std.clamp(-3, 0, 5), 0) && std.assertEqual(std.clamp(4, 0, 5), 4) &&