Rodrigo-Palma opened a new issue, #3996:
URL: https://github.com/apache/iceberg-python/issues/3996

   `to_bytes` for `DecimalType` takes the absolute value of the exponent before 
comparing it
   against the type's scale:
   
   ```python
   _, digits, exponent = value.as_tuple()
   exponent = abs(int(exponent))
   if exponent != primitive_type.scale:
       raise ValueError(...)
   ```
   
   A `Decimal` carries the negated scale as its exponent, so a value with a 
*negative* scale
   passes this check as if it had the matching positive one. 
`decimal_to_unscaled` then uses
   only the digits, and the exponent is dropped:
   
   ```python
   sign, digits, _ = value.as_tuple()
   return int(Decimal((sign, digits, 0)).to_integral_value())
   ```
   
   The value is written four orders of magnitude off, with no error.
   
   ## Reproduction
   
   ```python
   from decimal import Decimal
   from pyiceberg.conversions import to_bytes, from_bytes
   from pyiceberg.types import DecimalType
   
   t = DecimalType(10, 2)
   print(from_bytes(t, to_bytes(t, Decimal("1E+2"))))   # 0.01, expected 100.00
   print(from_bytes(t, to_bytes(t, Decimal("5E+1"))))   # 0.50 for decimal(10, 
1) -> 0.5, expected 50.0
   ```
   
   This is not an exotic input. `Decimal.normalize()` produces exactly this 
form:
   
   ```python
   Decimal("100").normalize()   # Decimal('1E+2')
   ```
   
   so a value that has been normalized, or that comes out of arithmetic that 
trims trailing
   zeros, hits it.
   
   ## Impact
   
   `to_bytes` writes the `lower_bounds` and `upper_bounds` of a data file
   (`pyiceberg/manifest.py`, `_write_data_file_statistics`), and the same 
conversion is used
   in `pyiceberg/io/pyarrow.py`. A bound written as `0.01` instead of `100.00` 
makes scan
   planning prune files that do hold matching rows, so a query silently returns 
fewer rows
   than it should.
   
   Values with a positive scale are unaffected: `Decimal("100.00")` round-trips 
correctly.
   
   ## Suggested fix
   
   Compare the signed scale, `-exponent`, against `primitive_type.scale`, so a 
mismatching
   value is rejected instead of being silently rescaled. Rescaling the value to 
the type's
   scale would be the other option, but that widens the contract of a function 
that today
   requires an exact match.
   
   Happy to open a PR.
   


-- 
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