diff --git a/sjsonnet/src/sjsonnet/stdlib/MathModule.scala b/sjsonnet/src/sjsonnet/stdlib/MathModule.scala index a021074d..9af39e2f 100644 --- a/sjsonnet/src/sjsonnet/stdlib/MathModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/MathModule.scala @@ -314,7 +314,12 @@ object MathModule extends AbstractFunctionModule { * The official docs list std.round(x) as a mathematical function. */ builtin("round", "x") { (pos, ev, x: Double) => - if (x >= 0) math.floor(x + 0.5) else math.ceil(x - 0.5) + // For |x| >= 2^52 the double is already an exact integer (ULP >= 1.0); + // adding 0.5 would invoke IEEE 754 round-to-even and produce the wrong + // result for odd inputs (e.g. 2^53 - 1). Short-circuit to avoid it. + if (x != x || math.abs(x) >= 4503599627370496.0) x + else if (x >= 0) math.floor(x + 0.5) + else math.ceil(x - 0.5) }, /** * [[https://jsonnet.org/ref/stdlib.html#math std.ceil(x)]]. diff --git a/sjsonnet/test/resources/new_test_suite/math_round_isEven_isOdd_isInteger_isDecimal.jsonnet b/sjsonnet/test/resources/new_test_suite/math_round_isEven_isOdd_isInteger_isDecimal.jsonnet index 5af7a288..aad08a57 100644 --- a/sjsonnet/test/resources/new_test_suite/math_round_isEven_isOdd_isInteger_isDecimal.jsonnet +++ b/sjsonnet/test/resources/new_test_suite/math_round_isEven_isOdd_isInteger_isDecimal.jsonnet @@ -12,6 +12,14 @@ assert std.round(-1.8) == -2 : 'round(-1.8)'; assert std.round(0) == 0 : 'round(0)'; assert std.round(1) == 1 : 'round(1)'; assert std.round(-1) == -1 : 'round(-1)'; +// Large-integer regression: for |x| >= 2^52 the double is already an exact +// integer (ULP >= 1.0), so std.round must be the identity. go-jsonnet and +// jrsonnet agree on all four values below. +assert std.round(9007199254740991) == 9007199254740991 : 'round(2^53 - 1)'; +assert std.round(9007199254740990) == 9007199254740990 : 'round(2^53 - 2)'; +assert std.round(4503599627370497) == 4503599627370497 : 'round(2^52 + 1)'; +assert std.round(-9007199254740991) == -9007199254740991 : 'round(-(2^53 - 1))'; +assert std.round(1e20) == 1e20 : 'round(1e20)'; // === std.isEven: truncation of integral part (go-jsonnet + jrsonnet agree) === // Integer inputs