Skip to content

Commit 5e0a0d2

Browse files
committed
fix BYTE_ARRAY_DECIMAL conversion
1 parent c2942b1 commit 5e0a0d2

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

fastparquet/converted_types.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,16 +178,27 @@ def convert(data, se, timestamp96=True, dtype=None):
178178
if data.dtype.kind in ['i', 'f']:
179179
return data * scale_factor
180180
else: # byte-string
181-
# NB: general but slow method
182-
# could optimize when data.dtype.itemsize <= 8
183-
# TODO: easy cythonize (but rare)
184-
# TODO: extension point for pandas-decimal (no conversion needed)
185-
return np.array([
186-
int.from_bytes(
187-
data.data[i:i + 1], byteorder='big', signed=True
188-
) * scale_factor
189-
for i in range(len(data))
190-
])
181+
# Handle both FIXED_LEN_BYTE_ARRAY and BYTE_ARRAY
182+
if se.type == parquet_thrift.Type.FIXED_LEN_BYTE_ARRAY:
183+
# Fixed-length: use buffer slicing approach
184+
# This handles the case where numpy may truncate during iteration
185+
# (see commit 53ceac2dbb141b76f603318a5cc0f78e64769d62)
186+
its = data.dtype.itemsize
187+
by = data.tobytes()
188+
return np.array([
189+
int.from_bytes(
190+
by[i * its:(i + 1) * its],
191+
byteorder='big', signed=True
192+
) * scale_factor
193+
for i in range(len(data))
194+
])
195+
else:
196+
# Variable-length (BYTE_ARRAY): iterate over elements directly
197+
# Each element is a bytes object in the object array
198+
return np.array([
199+
int.from_bytes(d, byteorder='big', signed=True) * scale_factor
200+
for d in data
201+
])
191202
elif ctype == parquet_thrift.ConvertedType.DATE:
192203
data = data * DAYS_TO_NANOS
193204
return data.view('datetime64[ns]')

fastparquet/test/test_converted_types.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,34 @@ def test_big_decimal():
157157
np.array([0., 777.2, 751.6, 345.1, 644.1])).all()
158158

159159

160+
def test_byte_array_decimal():
161+
"""Test decimal data stored as variable-length BYTE_ARRAY."""
162+
schema = pt.SchemaElement(
163+
type=pt.Type.BYTE_ARRAY, # Variable length (not FIXED_LEN)
164+
name="test",
165+
converted_type=pt.ConvertedType.DECIMAL,
166+
scale=4,
167+
precision=19
168+
)
169+
# Create DECIMAL values as variable-length byte arrays
170+
# Stored as: value * 10^scale
171+
# $0.00 -> 0 * 10^4 = 0 -> b'\x00'
172+
# $606.00 -> 606 * 10^4 = 6060000 -> b'\x5c\x77\xe0'
173+
# $280.00 -> 280 * 10^4 = 2800000 -> b'\x2a\xb9\x80'
174+
# $625.00 -> 625 * 10^4 = 6250000 -> b'\x5f\x5e\x10'
175+
data = np.array([
176+
b'\x00', # 0
177+
b'\x5c\x77\xe0', # 6,060,000 (3 bytes)
178+
b'\x2a\xb9\x80', # 2,800,000 (3 bytes)
179+
b'\x5f\x5e\x10', # 6,250,000 (3 bytes)
180+
], dtype=object)
181+
182+
result = convert(data, schema)
183+
expected = np.array([0.0, 606.0, 280.0, 625.0])
184+
185+
assert np.allclose(result, expected), \
186+
f"Expected {expected}, got {result}"
187+
160188
def test_tz_nonstring(tmpdir):
161189
# https://github.com/dask/fastparquet/issues/578
162190
import uuid

0 commit comments

Comments
 (0)