Repository navigation
fix: preserve non-canonical NaN payloads and distinguish -0.0 in v3 metadata - #4486
barlowa124 wants to merge 9 commits into
Conversation
…etadata float_from_json_v3 decoded hex strings through Python float64, which cannot carry a float16 payload and risks narrowing float32 payloads; decode into a same-width NumPy scalar via np.frombuffer instead. float_to_json_v3 flattened every NaN to "NaN" on write; emit the hex bit pattern for NaNs that are not the canonical positive NaN so the document round-trips the payload it parsed. ArrayV3Metadata.__eq__ compared to_dict() dictionaries, under which -0.0 == 0.0 and (before this change) any NaN payload == "NaN" even though the documents serialize differently. Compare the canonical serialized JSON text instead, which is also the exact form __hash__ already hashes, making the two consistent by construction. Partial fix for zarr-developers#4453 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The strict bits==canonical check serialized float32 0xffc00000 as hex, but the canonical NaN designation covers the payload, not the sign; mask the sign bit so 0xffc00000 writes as "NaN" like the canonical form. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
thanks for this, I will have a look in the next few days! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4486 +/- ##
==========================================
+ Coverage 94.69% 94.71% +0.01%
==========================================
Files 94 94
Lines 13619 13666 +47
==========================================
+ Hits 12897 12944 +47
Misses 722 722
🚀 New features to boost your workflow:
|
d-v-b
left a comment
There was a problem hiding this comment.
- Compare all of a NaN's bits, sign included, before writing
"NaN". Any other NaN, such as0xffc00000, should be written as hex, as zarrs and tensorstore do. At the moment0xffc00000writes"NaN"and reads back as0x7fc00000. - Make
ArrayV3Metadata.__eq__work withoutjson.dumps(..., sort_keys=True). It raisesTypeErrorwhen attribute keys mixintandstr, and that reachesArray.__eq__too. - Get the canonical NaN bits from numpy (
np.asarray(np.nan, dtype=...)viewed as the matching uint) instead of hardcoded tables. - Use parametrize in
test_noncanonical_nan_serializes_as_hex, and add tests for the0xffc00000round trip and the complex path. - note in the changelog that equality now compares the JSON text, so
1and1.0in attributes no longer compare equal.
|
Pushed 8a2714a.
|
|
d238278 fixes the CI fallout: |
Summary
Third item of #4453: float fill values holding non-canonical NaN
payloads did not round-trip, and
ArrayV3Metadata.__eq__conflateddocuments whose
fill_values serialize differently.Parse to serialize dropped NaN payloads.
"0x7fc00001"parsed fine,but
float_to_json_v3flattened every NaN to"NaN"on write. It nowemits the hex bit pattern for any NaN differing from the canonical
positive one, which is the case the spec's hex encoding names. The old
comment claiming NumPy cannot hold distinct NaNs was wrong. The
payload bits were being discarded on write, not absent. The canonical
check compares all NaN bits including sign, matching zarrs and
tensorstore. In the same place,
float_from_json_v3decoded hex intoPython's 64-bit
floatand narrowed float16/float32 payloads. Hexnow decodes through
np.frombufferinto a same-width scalar, so allthree widths round-trip.
Equality conflated distinct documents.
__eq__comparedto_dict()dictionaries, where negative zero equals positive zero and every NaN
equaled
"NaN".__eq__now comparesjson.dumps(to_dict(), sort_keys=True). That is the string__hash__already hashes, soequal implies equal-hash by construction.
For reviewers
float_from_json_v3returnsnp.floatingfor hex input rather thanfloat. Both call sites consume it through a cast orcomplex(), socallers see no change.
"NaN"still denotes the canonical positiveNaN per width. v2 serialization is untouched. Complex fills route
through the same helpers per component, so a complex64 real part with
a non-canonical payload now round-trips as hex as well. That path
was exercised but has no dedicated test. Payload bits survive only when the hex
width matches the dtype width, which the spec requires anyway.
Author attestation
TODO
Done:
changes/Outstanding:
docs/user-guide/*.md(no user-facing behavior beyond spec compliance)