diff --git a/docs/mkdocs/docs/api/basic_json/std_hash.md b/docs/mkdocs/docs/api/basic_json/std_hash.md index cb1243b6d..7be7772cc 100644 --- a/docs/mkdocs/docs/api/basic_json/std_hash.md +++ b/docs/mkdocs/docs/api/basic_json/std_hash.md @@ -7,9 +7,14 @@ namespace std { ``` Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the -type of the JSON value is taken into account, so `#!json null`, `#!cpp false`, and numbers all hash differently from +type of the JSON value is taken into account, so `#!json null`, `#!cpp false`, and numbers may hash differently from each other. Numbers that compare equal under [`operator==`](operator_eq.md) always hash equally, regardless of -whether they are stored as `#!cpp 0`, `#!cpp 0U`, or `#!cpp 0.0`. +whether they are stored as signed integer, unsigned integer, or floating-point number. + +Numbers are hashed by their value converted to `number_float_t`. Converting an integer to `number_float_t` therefore +keeps its hash, but converting a floating-point number to an integer type is lossy and can change it: `#!cpp 0.5` +converts to `#!cpp 0`, which need not have the same hash. Unequal numbers can also share a hash value, for example two +large integers that convert to the same `number_float_t`. ## Examples @@ -27,7 +32,8 @@ whether they are stored as `#!cpp 0`, `#!cpp 0U`, or `#!cpp 0.0`. --8<-- "examples/std_hash.output" ``` - Note the output is platform-dependent. + The hash values shown are examples only. They depend on the platform, the compiler, and the compiler version, and + they can change between versions of this library. Do not persist them or rely on specific values. ## See also diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp index aa91f8894..fd1326b5d 100644 --- a/include/nlohmann/detail/hash.hpp +++ b/include/nlohmann/detail/hash.hpp @@ -35,7 +35,7 @@ std::size_t hash_iteratively(const BasicJsonType& j); @brief hash a JSON value The hash function tries to rely on std::hash where possible. Furthermore, the -type of the JSON value is taken into account, so null, false, and numbers all +type of the JSON value is taken into account, so null, false, and numbers may hash differently from each other, but any two numbers that compare equal under operator== hash equally regardless of which of number_integer, number_unsigned, or number_float actually holds the value. @@ -123,6 +123,10 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0) // 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. + // The converse does not hold: converting a number_float_t value + // to an integer type is lossy, so the result can hash + // differently, and unequal numbers that convert to the same + // number_float_t (e.g., 2^53 and 2^53 + 1) share a hash. const auto number_type = static_cast(BasicJsonType::value_t::number_float); const auto value = j.template get() + static_cast(0); const auto h = std::hash {}(value); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 95acdfd78..77635787e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -7583,7 +7583,7 @@ std::size_t hash_iteratively(const BasicJsonType& j); @brief hash a JSON value The hash function tries to rely on std::hash where possible. Furthermore, the -type of the JSON value is taken into account, so null, false, and numbers all +type of the JSON value is taken into account, so null, false, and numbers may hash differently from each other, but any two numbers that compare equal under operator== hash equally regardless of which of number_integer, number_unsigned, or number_float actually holds the value. @@ -7671,6 +7671,10 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0) // 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. + // The converse does not hold: converting a number_float_t value + // to an integer type is lossy, so the result can hash + // differently, and unequal numbers that convert to the same + // number_float_t (e.g., 2^53 and 2^53 + 1) share a hash. const auto number_type = static_cast(BasicJsonType::value_t::number_float); const auto value = j.template get() + static_cast(0); const auto h = std::hash {}(value);