mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 15:07:13 +00:00
Normalize -0.0 in number hashes and test range ends
operator== compares numbers exactly since #5459, so equal numbers share one value and convert to the same number_float_t. Update the comment accordingly, map -0.0 to 0.0 before hashing (std::hash need not do that), and test -0.0 and the ends of the integer ranges. Show hash(0.0) in the docs example and note the change in the version history. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
6 files changed
+39
-14
No files matched your search
@@ -37,3 +37,5 @@ whether they are stored as `#!cpp 0`, `#!cpp 0U`, or `#!cpp 0.0`.
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Extended for arbitrary basic_json types in version 3.10.5.
|
||||
- Numbers that compare equal hash equally since version 3.13.0; before, `#!cpp 0`, `#!cpp 0U`, and `#!cpp 0.0` had
|
||||
different hash values.
|
||||
@@ -11,6 +11,7 @@ int main()
|
||||
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
||||
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
||||
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
||||
<< "hash(0.0) = " << std::hash<json> {}(json(0.0)) << '\n'
|
||||
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
||||
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
||||
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
||||
|
||||
@@ -2,6 +2,7 @@ hash(null) = 2654435769
|
||||
hash(false) = 2654436030
|
||||
hash(0) = 2654436221
|
||||
hash(0U) = 2654436221
|
||||
hash(0.0) = 2654436221
|
||||
hash("") = 11160318156688833227
|
||||
hash({}) = 2654435832
|
||||
hash([]) = 2654435899
|
||||
|
||||
@@ -116,14 +116,16 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
case BasicJsonType::value_t::number_unsigned:
|
||||
case BasicJsonType::value_t::number_float:
|
||||
{
|
||||
// operator== converts between number_integer, number_unsigned, and
|
||||
// number_float before comparing, so equal numbers of different
|
||||
// internal types (0, 0U, 0.0) must hash the same. Combining a
|
||||
// single shared type tag with the value converted to
|
||||
// number_float_t keeps the hash consistent with operator== for
|
||||
// every pair of numbers it considers equal.
|
||||
// operator== compares numbers by their mathematical value across
|
||||
// number_integer, number_unsigned, and number_float, so equal
|
||||
// numbers of different internal types (0, 0U, 0.0) must hash the
|
||||
// same. Two equal numbers have the same value, which converts to
|
||||
// the same number_float_t, so all numbers share one type tag and
|
||||
// hash that converted value. Adding zero turns -0.0 (equal to 0)
|
||||
// into 0.0, as std::hash need not map both to the same hash.
|
||||
const auto number_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
||||
const auto value = j.template get<number_float_t>() + static_cast<number_float_t>(0);
|
||||
const auto h = std::hash<number_float_t> {}(value);
|
||||
return combine(number_type, h);
|
||||
}
|
||||
|
||||
|
||||
@@ -7664,14 +7664,16 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
case BasicJsonType::value_t::number_unsigned:
|
||||
case BasicJsonType::value_t::number_float:
|
||||
{
|
||||
// operator== converts between number_integer, number_unsigned, and
|
||||
// number_float before comparing, so equal numbers of different
|
||||
// internal types (0, 0U, 0.0) must hash the same. Combining a
|
||||
// single shared type tag with the value converted to
|
||||
// number_float_t keeps the hash consistent with operator== for
|
||||
// every pair of numbers it considers equal.
|
||||
// operator== compares numbers by their mathematical value across
|
||||
// number_integer, number_unsigned, and number_float, so equal
|
||||
// numbers of different internal types (0, 0U, 0.0) must hash the
|
||||
// same. Two equal numbers have the same value, which converts to
|
||||
// the same number_float_t, so all numbers share one type tag and
|
||||
// hash that converted value. Adding zero turns -0.0 (equal to 0)
|
||||
// into 0.0, as std::hash need not map both to the same hash.
|
||||
const auto number_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
||||
const auto value = j.template get<number_float_t>() + static_cast<number_float_t>(0);
|
||||
const auto h = std::hash<number_float_t> {}(value);
|
||||
return combine(number_type, h);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using json = nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -147,6 +148,22 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
// a std::unordered_set relies on this same consistency between == and hash
|
||||
const std::unordered_set<json> numbers {json(0), json(static_cast<unsigned>(0)), json(0.0)};
|
||||
CHECK(numbers.size() == 1);
|
||||
|
||||
// -0.0 compares equal to 0 and 0.0
|
||||
CHECK(json(-0.0) == json(0));
|
||||
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0)));
|
||||
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0.0)));
|
||||
|
||||
// the ends of the integer ranges, which equal floats exactly
|
||||
const auto int_min = (std::numeric_limits<json::number_integer_t>::min)();
|
||||
const auto int_max = (std::numeric_limits<json::number_integer_t>::max)();
|
||||
const auto two_63 = json::number_unsigned_t(1) << 63U;
|
||||
CHECK(json(int_min) == json(-9223372036854775808.0));
|
||||
CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0)));
|
||||
CHECK(json(two_63) == json(9223372036854775808.0));
|
||||
CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0)));
|
||||
CHECK(json(json::number_unsigned_t(int_max)) == json(int_max));
|
||||
CHECK(std::hash<json> {}(json(json::number_unsigned_t(int_max))) == std::hash<json> {}(json(int_max)));
|
||||
}
|
||||
|
||||
TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
|
||||
Reference in new issue
Block a user