Skip to content

fix: std.primitiveEquals(-0.0, 0.0) should return true#913

Merged
stephenamar-db merged 1 commit into
databricks:masterfrom
He-Pin:fix/primitive-equals-negative-zero-clean
Jun 11, 2026
Merged

fix: std.primitiveEquals(-0.0, 0.0) should return true#913
stephenamar-db merged 1 commit into
databricks:masterfrom
He-Pin:fix/primitive-equals-negative-zero-clean

Conversation

@He-Pin

@He-Pin He-Pin commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix std.primitiveEquals(-0.0, 0.0) to return true (IEEE 754 semantics), matching go-jsonnet and C++ jsonnet behavior.

Changes

  • sjsonnet/src/sjsonnet/stdlib/TypeModule.scala: Change primitiveEquals for numbers from ev.compare(x, y) == 0 to x.rawDouble == y.rawDouble
  • Add test case: test/resources/new_test_suite/primitive_equals_negative_zero.jsonnet

Implementation Comparison

Implementation Code -0.0 == 0.0 Performance
go-jsonnet left.value == right.value true Fast (direct ==)
jrsonnet (a.get() - b.get()).abs() <= f64::EPSILON true Slower (subtraction + abs)
sjsonnet (original) ev.compare(x, y) == 0 false Slow (method call + Double.compare)
sjsonnet (this PR) x.rawDouble == y.rawDouble true Fast (direct ==)

Performance Analysis

Current implementation (ev.compare(x, y) == 0)

// Evaluator.scala:2038
def compare(x: Val, y: Val): Int = (x, y) match {
  case (x: Val.Num, y: Val.Num) => java.lang.Double.compare(x.asDouble, y.asDouble)
  ...
}
  • Method call overhead
  • java.lang.Double.compare distinguishes -0.0 and 0.0 (returns -1)
  • asDouble has NaN check overhead

New implementation (x.rawDouble == y.rawDouble)

// TypeModule.scala
case (x: Val.Num, y: Val.Num) =>
  x.rawDouble == y.rawDouble
  • Direct field access
  • Direct == operator
  • No method call overhead
  • Consistent with go-jsonnet

jrsonnet implementation

(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,
  • Subtraction + abs + comparison
  • More expensive than direct ==

Why this change is correct

  1. IEEE 754 semantics: -0.0 == 0.0 is true per IEEE 754
  2. Consistency with references: go-jsonnet and C++ jsonnet both treat -0.0 and 0.0 as equal
  3. Performance: Direct == is faster than method call + Double.compare
  4. rawDouble is safe: NaN cannot arise from valid Jsonnet expressions (constructor guards against infinity, no standard operator produces NaN)

Test

// test/resources/new_test_suite/primitive_equals_negative_zero.jsonnet
std.assertEqual(std.primitiveEquals(-0.0, 0.0), true) &&
std.assertEqual(std.primitiveEquals(0.0, -0.0), true) &&
std.assertEqual(std.primitiveEquals(-0.0, -0.0), true) &&
std.assertEqual(std.primitiveEquals(0.0, 0.0), true) &&
std.assertEqual(std.primitiveEquals(-0.0, 1.0), false) &&
std.assertEqual(std.primitiveEquals(0.0, 1.0), false) &&
true

Motivation:
std.primitiveEquals(-0.0, 0.0) was returning false instead of true.
This is incorrect because IEEE 754 defines -0.0 == 0.0 as true.

Modification:
Changed TypeModule.scala to use rawDouble == rawDouble for number
comparison instead of ev.compare(x, y) == 0 which uses
java.lang.Double.compare that treats -0.0 and 0.0 as different.

Result:
std.primitiveEquals(-0.0, 0.0) now returns true, matching go-jsonnet
and C++ jsonnet behavior.

References:
- go-jsonnet builtins.go:264 uses left.value == right.value
- C++ Jsonnet vm.cpp:1436 uses args[0].v.d == args[1].v.d
@He-Pin He-Pin marked this pull request as draft June 10, 2026 07:48
@He-Pin He-Pin marked this pull request as ready for review June 10, 2026 07:52
@stephenamar-db stephenamar-db merged commit 6406572 into databricks:master Jun 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants