Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 23 additions & 30 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
#include "DOMURL.h"
#include "JSDOMURL.h"

#include <cmath>
#include <string_view>
#include <bun-uws/src/App.h>
#include <bun-uws/src/Http3Request.h>
Expand Down Expand Up @@ -1380,42 +1381,34 @@ std::optional<bool> specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark
if (vector == rightVector) [[unlikely]]
return true;

// For Float32Array and Float64Array, when not in strict mode, we need to
// handle +0 and -0 as equal, and NaN as not equal to itself.
// Float arrays are compared element-wise in non-strict mode. Jest's toEqual
// uses Object.is semantics (NaN equals NaN, +0 differs from -0), while node's
// loose deepEqual uses == (NaN differs from NaN, +0 equals -0).
if (!isStrict && (c1Type == Float16ArrayType || c1Type == Float32ArrayType || c1Type == Float64ArrayType)) {
if (c1Type == Float16ArrayType) {
auto* leftFloat = static_cast<const WTF::Float16*>(vector);
auto* rightFloat = static_cast<const WTF::Float16*>(rightVector);
size_t numElements = byteLength / sizeof(WTF::Float16);

for (size_t i = 0; i < numElements; i++) {
if (leftFloat[i] != rightFloat[i]) {
return false;
}
}
return true;
} else if (c1Type == Float32ArrayType) {
auto* leftFloat = static_cast<const float*>(vector);
auto* rightFloat = static_cast<const float*>(rightVector);
size_t numElements = byteLength / sizeof(float);

auto floatElementsEqual = [&](const auto* left, const auto* right) -> bool {
size_t numElements = byteLength / sizeof(*left);
for (size_t i = 0; i < numElements; i++) {
if (leftFloat[i] != rightFloat[i]) {
return false;
double l = left[i];
double r = right[i];
if constexpr (enableAsymmetricMatchers) {
if (l == r ? std::signbit(l) != std::signbit(r) : !(std::isnan(l) && std::isnan(r))) {
return false;
}
} else {
if (l != r) {
return false;
}
}
}
return true;
} else { // Float64Array
auto* leftDouble = static_cast<const double*>(vector);
auto* rightDouble = static_cast<const double*>(rightVector);
size_t numElements = byteLength / sizeof(double);
};

for (size_t i = 0; i < numElements; i++) {
if (leftDouble[i] != rightDouble[i]) {
return false;
}
}
return true;
if (c1Type == Float16ArrayType) {
return floatElementsEqual(static_cast<const WTF::Float16*>(vector), static_cast<const WTF::Float16*>(rightVector));
} else if (c1Type == Float32ArrayType) {
return floatElementsEqual(static_cast<const float*>(vector), static_cast<const float*>(rightVector));
} else {
return floatElementsEqual(static_cast<const double*>(vector), static_cast<const double*>(rightVector));
}
}

Expand Down
43 changes: 43 additions & 0 deletions test/regression/issue/34815.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test } from "bun:test";
import assert from "node:assert";

// https://github.com/oven-sh/bun/issues/34815
// Jest compares typed array elements with Object.is semantics:
// NaN equals NaN, and +0 is distinct from -0.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
test("toEqual treats NaN elements of float typed arrays as equal", () => {
expect(new Float16Array([NaN])).toEqual(new Float16Array([NaN]));
expect(new Float32Array([NaN])).toEqual(new Float32Array([NaN]));
expect(new Float64Array([NaN])).toEqual(new Float64Array([NaN]));
expect(new Float16Array([1, NaN, 2])).toEqual(new Float16Array([1, NaN, 2]));
expect(new Float32Array([1, NaN, 2])).toEqual(new Float32Array([1, NaN, 2]));
expect(new Float64Array([1, NaN, 2])).toEqual(new Float64Array([1, NaN, 2]));

expect(new Float16Array([NaN])).toStrictEqual(new Float16Array([NaN]));
expect(new Float32Array([NaN])).toStrictEqual(new Float32Array([NaN]));
expect(new Float64Array([NaN])).toStrictEqual(new Float64Array([NaN]));

expect(new Float64Array([NaN])).not.toEqual(new Float64Array([1]));
expect(new Float64Array([1])).not.toEqual(new Float64Array([NaN]));
expect(new Float32Array([1])).not.toEqual(new Float32Array([2]));
});

test("toEqual distinguishes +0 and -0 in float typed arrays", () => {
expect(new Float16Array([-0])).not.toEqual(new Float16Array([0]));
expect(new Float32Array([-0])).not.toEqual(new Float32Array([0]));
expect(new Float64Array([-0])).not.toEqual(new Float64Array([0]));
expect(new Float64Array([-0])).toEqual(new Float64Array([-0]));
expect(new Float64Array([0])).toEqual(new Float64Array([0]));
});

// node's loose deepEqual keeps == semantics for float typed arrays:
// NaN differs from NaN, +0 equals -0. Strict mode compares bytes.
test("node:assert float typed array semantics are unchanged", () => {
assert.deepEqual(new Float64Array([-0]), new Float64Array([0]));
assert.throws(() => assert.deepEqual(new Float64Array([NaN]), new Float64Array([NaN])));
assert.deepStrictEqual(new Float64Array([NaN]), new Float64Array([NaN]));
assert.throws(() => assert.deepStrictEqual(new Float64Array([-0]), new Float64Array([0])));

expect(Bun.deepEquals(new Float64Array([-0]), new Float64Array([0]), false)).toBe(true);
expect(Bun.deepEquals(new Float64Array([NaN]), new Float64Array([NaN]), false)).toBe(false);
expect(Bun.deepEquals(new Float64Array([NaN]), new Float64Array([NaN]), true)).toBe(true);
});
Loading