m0g3r opened a new pull request, #3808:
URL: https://github.com/apache/iceberg-python/pull/3808

   # Rationale for this change
   
   `CythonBinaryDecoder.read_double` is declared `cpdef float 
read_double(self)`. In Cython, `float` is the C single-precision type, so the 
correctly decoded 64-bit value returned by `STRUCT_DOUBLE.unpack` is narrowed 
to 32 bits on the way out. Every Avro `double` read through the fast decoder is 
silently rounded, and values outside the single-precision range collapse 
entirely.
   
   ```python
   >>> import struct
   >>> from pyiceberg.avro.decoder import StreamingBinaryDecoder
   >>> from pyiceberg.avro.decoder_fast import CythonBinaryDecoder
   >>> b = struct.pack("<d", 3.141592653589793)
   >>> StreamingBinaryDecoder(b).read_double()
   3.141592653589793
   >>> CythonBinaryDecoder(b).read_double()      # before this PR
   3.1415927410125732
   >>> CythonBinaryDecoder(struct.pack("<d", 1e308)).read_double()
   inf
   >>> CythonBinaryDecoder(struct.pack("<d", 5e-324)).read_double()
   0.0
   ```
   
   `new_decoder` returns `CythonBinaryDecoder` whenever the extension is built, 
so this is the default read path; the pure-Python `StreamingBinaryDecoder` 
fallback was always correct.
   
   The user-visible effect is on any `double` read out of a manifest, most 
directly an identity partition value on a `float`/`double` column. Writing a 
manifest with a partition value of `429496729622.314` and reading it back 
through `ManifestFile.fetch_manifest_entry` returns `429496729600.0` on `main`, 
and the exact value with this change.
   
   `read_float` is left as `cpdef float`: a value decoded from four bytes is 
already exactly representable as a C float, so no precision is lost there.
   
   The bug dates back to the original Cython decoder (#8134, 2023). It was not 
caught by `tests/avro/test_decoder.py::test_read_double` because `19.25` is 
exactly representable in single precision. 
`tests/avro/test_file.py::test_all_primitive_types` does round-trip a `double` 
that is not, but its assertion loop iterates 
`enumerate(all_primitives_schema.as_struct())` — iterating the pydantic model 
yields its two model fields (`type`, `fields`), not the 13 schema fields — so 
only positions 0 and 1 were ever compared. I kept that out of this PR to keep 
it to one concern, and am happy to send the test fix as a follow-up (or fold it 
in here if you would rather).
   
   ## Are these changes tested?
   
   Yes. `tests/avro/test_decoder.py` gains 
`test_read_double_keeps_full_precision`, parametrized over both decoder 
implementations and six doubles that are not representable in single precision, 
including the 1e308 overflow and 5e-324 underflow cases.
   
   Verified red/green by rebuilding the extension against the pre-change 
`decoder_fast.pyx`:
   
   - before: `6 failed, 8 passed` — every failure is the `CythonBinaryDecoder` 
parametrization, e.g. `assert 0.0 == 5e-324`
   - after: `56 passed` in `tests/avro/test_decoder.py`
   
   Full local run on macOS/arm64, Python 3.13: `make lint` all 12 hooks pass, 
`make test` gives `3957 passed, 3 skipped, 1570 deselected`.
   
   Integration tests (Spark/Docker) were not run locally.
   
   ## Are there any user-facing changes?
   
   Yes — `double` values read from Avro are no longer rounded to single 
precision. This is a bug fix; existing manifests do not need to be rewritten, 
since the data on disk was always correct and only the decode was lossy.
   
   ---
   
   Disclosure: this change was written with AI assistance (Claude Code). The 
bug was found by auditing the Cython decoder's C return types, then confirmed 
against the pure-Python decoder and end to end through a manifest write/read 
round trip.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to