diff --git a/pyiceberg/manifest.py b/pyiceberg/manifest.py index aacd61dd79..52e5890651 100644 --- a/pyiceberg/manifest.py +++ b/pyiceberg/manifest.py @@ -736,6 +736,12 @@ def update(self, value: Any) -> None: else: self._max = max(self._max, value) self._min = min(self._min, value) + if isinstance(value, float) and value == 0.0: + # min and max keep whichever zero came first, but bounds must order -0.0 below 0.0 + if math.copysign(1.0, value) < 0 and self._min == 0.0: + self._min = value + elif math.copysign(1.0, value) > 0 and self._max == 0.0: + self._max = value def construct_partition_summaries(spec: PartitionSpec, schema: Schema, partitions: list[Record]) -> list[PartitionFieldSummary]: diff --git a/tests/utils/test_manifest.py b/tests/utils/test_manifest.py index a73d0feeea..d2da6ad71b 100644 --- a/tests/utils/test_manifest.py +++ b/tests/utils/test_manifest.py @@ -27,6 +27,7 @@ import pyiceberg.manifest as manifest_module from pyiceberg.avro.codecs import AvroCompressionCodec from pyiceberg.avro.file import DEFAULT_SYNC_INTERVAL, AvroFile, AvroOutputFile +from pyiceberg.conversions import to_bytes from pyiceberg.encryption.key_metadata import StandardKeyMetadata from pyiceberg.encryption.manager import EncryptionManager from pyiceberg.encryption.stream import _encrypt_ags1 @@ -47,15 +48,17 @@ _inherit_from_manifest, _manifests, clear_manifest_cache, + construct_partition_summaries, read_manifest_list, write_manifest, write_manifest_list, ) -from pyiceberg.partitioning import UNPARTITIONED_PARTITION_SPEC, PartitionSpec +from pyiceberg.partitioning import UNPARTITIONED_PARTITION_SPEC, PartitionField, PartitionSpec from pyiceberg.schema import Schema from pyiceberg.table.snapshots import Operation, Snapshot, Summary +from pyiceberg.transforms import IdentityTransform from pyiceberg.typedef import Record, TableVersion -from pyiceberg.types import IntegerType, ListType, LongType, NestedField, StructType +from pyiceberg.types import DoubleType, FloatType, IntegerType, ListType, LongType, NestedField, StructType from tests.encryption.memory_kms import MemoryKeyManagementClient @@ -1365,6 +1368,21 @@ def test_write_manifest_min_sequence_number_zero(format_version: TableVersion) - assert manifest_file.min_sequence_number == 0 +def test_construct_partition_summaries_orders_negative_zero_below_zero() -> None: + spec = PartitionSpec(PartitionField(source_id=1, field_id=1000, transform=IdentityTransform(), name="x")) + for field_type in (FloatType(), DoubleType()): + schema = Schema(NestedField(1, "x", field_type, required=False)) + expected = PartitionFieldSummary.from_args( + contains_null=False, + contains_nan=False, + lower_bound=to_bytes(field_type, -0.0), + upper_bound=to_bytes(field_type, 0.0), + ) + for values in ([0.0, -0.0], [-0.0, 0.0]): + summaries = construct_partition_summaries(spec, schema, [Record(value) for value in values]) + assert summaries == [expected], (field_type, values) + + def test_inherit_from_manifest_snapshot_id() -> None: entry = ManifestEntry.from_args( status=ManifestEntryStatus.ADDED,