Skip to content

Commit 0bdef33

Browse files
Toomas Ormissonclaude
andcommitted
Exclude float columns from delta encoding
Delta encoding violated spec §2.3's MUST round-trip requirement for float columns: prev + (cur - prev) in IEEE-754 does not recover the original double's bit pattern for arbitrary values, and round(diff, 10) at the encode step compounded the loss. Benchmark data exposed this as e.g. 1865.43 decoding to 1865.4299999999994. Restrict SparseMode.DELTA eligibility to int-only columns. Float columns now fall through to standard value encoding, which round-trips exactly via Python's shortest-round-trip str(float). Int delta encoding (the common case: IDs, counts, timestamps) is unchanged. Regression test covers multiple precision regimes — benchmark values, math.pi/math.e, 0.1+0.2, extreme exponents, negatives — so a future round-to-N workaround cannot sneak through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6824a8a commit 0bdef33

2 files changed

Lines changed: 41 additions & 7 deletions

File tree

zon-format/src/zon/core/encoder.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,17 @@ def _analyze_optimal_sparse_mode(self, values: List[Any]) -> SparseMode:
173173
if len(values) < 5:
174174
return SparseMode.NONE
175175

176-
is_numeric = True
176+
# Only int columns are eligible for delta encoding. Float delta
177+
# encoding cannot satisfy the spec §2.3 MUST round-trip requirement:
178+
# prev + (cur - prev) in IEEE-754 does not preserve the original
179+
# double's bit pattern for arbitrary floats.
180+
is_int_only = True
177181
for val in values:
178-
if not isinstance(val, (int, float)) or isinstance(val, bool):
179-
is_numeric = False
182+
if not isinstance(val, int) or isinstance(val, bool):
183+
is_int_only = False
180184
break
181-
182-
if is_numeric:
185+
186+
if is_int_only:
183187
return SparseMode.DELTA
184188

185189
return SparseMode.NONE

zon-format/tests/unit/test_delta.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import unittest
23
from zon import ZonEncoder, ZonDecoder
34

@@ -107,12 +108,41 @@ def test_deep_nesting(self):
107108
data = [
108109
{'a': {'b': {'c': {'d': {'e': 1}}}}}
109110
]
110-
111+
111112
encoded = self.encoder.encode(data)
112113
self.assertIn('a.b.c.d.e', encoded)
113-
114+
114115
decoded = self.decoder.decode(encoded)
115116
self.assertEqual(decoded, data)
116117

118+
def test_float_column_roundtrip_is_lossless(self):
119+
"""Float columns must round-trip bit-exactly (spec §2.3 MUST).
120+
121+
Covers multiple precision regimes so a partial fix (e.g. round-to-N)
122+
cannot sneak through.
123+
"""
124+
data = [
125+
{'v': 1865.43}, # benchmark regression case
126+
{'v': 3579.16}, # benchmark regression case
127+
{'v': math.pi}, # 17-sig-digit irrational
128+
{'v': math.e}, # 17-sig-digit irrational
129+
{'v': 0.1 + 0.2}, # classic non-terminating binary: 0.30000000000000004
130+
{'v': -42.5}, # negative, crosses zero in deltas
131+
{'v': 1e-10}, # small exponent
132+
{'v': 1e15}, # large exponent
133+
]
134+
135+
decoded = self.decoder.decode(self.encoder.encode(data))
136+
137+
for original, got in zip(data, decoded):
138+
# repr(float) is the shortest string that round-trips to the same
139+
# double, so repr equality is equivalent to bit equality.
140+
self.assertEqual(
141+
repr(original['v']),
142+
repr(got['v']),
143+
f"float roundtrip lost precision: {original['v']!r} -> {got['v']!r}",
144+
)
145+
146+
117147
if __name__ == "__main__":
118148
unittest.main()

0 commit comments

Comments
 (0)