Do not compare an edited NaN equal to itself in json_view (#6085)

Two views compared equal when the tokens of two floats were equal, and
the same node was equal to itself. An edit writes a NaN as the token
"nan", so a view of it (or of an array holding it) was equal to itself
and to another NaN, while json(NaN) == json(NaN) and view == json are
false. The token shortcut now excludes the edited "nan" token (found by
the missing digit layout), dispatched on the document mode by a tag: a
read-only document cannot hold a NaN, and its walk is unchanged
(instruction count and cycles on canada, citm and twitter within
noise). For editable documents the check costs about 2 % instructions
on canada (cycles within noise). The same-node shortcut is kept for
read-only documents and for editable ones that were never edited; a
document with edits is walked, so comparing it with itself is linear
instead of constant.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-11-11 22:36:54 +01:00
1 parent 9d35fe6d21
commit 02d57db31e
4 files changed
+87 -9

No files matched your search

+38
View File
@@ -206,6 +206,44 @@ TEST_CASE("json_view edits: views and values")
CHECK(d.root().materialize().dump() == json::parse(R"([1.5, 100.0, 0.1, null, null, 18446744073709551615, -9223372036854775808])").dump());
}
SECTION("a NaN written by an edit is not equal to itself")
{
// as for basic_json: NaN != NaN, so a view of it, of the array that holds it, and the same view twice agree
json_editable_document d = json_editable_document::parse("[0, 0, 1.5]");
d.set(d.root(), 0, std::numeric_limits<double>::quiet_NaN());
d.set(d.root(), 1, std::numeric_limits<double>::quiet_NaN());
json_editable_document other = json_editable_document::parse("[0, 0, 1.5]");
other.set(other.root(), 0, std::numeric_limits<double>::quiet_NaN());
other.set(other.root(), 1, std::numeric_limits<double>::quiet_NaN());
const json value = d.root().materialize();
CHECK_FALSE(value == value);
CHECK_FALSE(d.root()[0] == d.root()[0]);
CHECK_FALSE(d.root()[0] == d.root()[1]);
CHECK_FALSE(d.root() == d.root());
CHECK_FALSE(d.root() == other.root());
CHECK_FALSE(d.root()[0] == other.root()[0]);
CHECK(d.root() != d.root());
CHECK(d.root()[0] != d.root()[0]);
CHECK_FALSE(d.root() == value);
CHECK_FALSE(d.root()[0] == value[0]);
// the other elements are equal as before
CHECK(d.root()[2] == d.root()[2]);
CHECK(d.root()[2] == other.root()[2]);
// finite and infinite floats written by an edit stay equal to themselves
d.set(d.root(), 0, 0.1);
d.set(d.root(), 1, std::numeric_limits<double>::infinity());
other.set(other.root(), 0, 0.1);
other.set(other.root(), 1, std::numeric_limits<double>::infinity());
CHECK(d.root() == d.root());
CHECK(d.root() == other.root());
CHECK(d.root()[1] == other.root()[1]);
// a document that was never edited
const json_editable_document untouched = json_editable_document::parse("[0.5, [1, 2]]");
CHECK(untouched.root() == untouched.root());
CHECK(untouched.root()[1] == untouched.root()[1]);
}
SECTION("numbers of other float types")
{
// doubles have their own path to the output; other float types are