Skip to content

fix: render unsigned integers unsigned in CSV export (#208)#213

Merged
dfa1 merged 3 commits into
mainfrom
fix/208-unsigned-csv-rendering
Jul 6, 2026
Merged

fix: render unsigned integers unsigned in CSV export (#208)#213
dfa1 merged 3 commits into
mainfrom
fix/208-unsigned-csv-rendering

Conversation

@dfa1

@dfa1 dfa1 commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Fixes #208, found by the Raincloud conformance suite (#205): CsvExporter.cellValue formatted integer cells purely by array class, never consulting dtype signedness, so U8/U16/U32/U64 high-half values printed as two's-complement negatives — silent corruption (uci-wine magnesium U8 132 exported as -124; penguins flipper_length_mm all 344 rows negative).

  • ByteArray/ShortArray arms now go through their dtype-aware getInt defaults (zero-extend on U8/U16).
  • IntArray/LongArray gain an isUnsigned(dtype) gate choosing Integer.toUnsignedString/Long.toUnsignedString.
  • 5 new tests incl. the uci-wine shape, a nullable U8? (MaskedArray inner-dtype path), and U64 -1L18446744073709551615.

Verified on the real corpus: uci-wine magnesium row 34 now exports 132 matching the Parquet oracle.

Survey (follow-up issues to file, not fixed here) — same dtype-blindness elsewhere: GridRender.java:61-64, InspectorRender.java:51-54, FilterCommand.java:203-206 (filter predicates compare U* as signed), VortexTable.java:812-817 (Calcite U32/U64 raw), VortexAggregates.java:107,112 (U32/U64 sums wrong).

Merge notes: independent of #212, either order; trivial CHANGELOG conflict with it. After both merge, the conformance matrix flips uci-wine → ok (and penguins → ok together with #212) in a coordination commit.

Closes #208

🤖 Generated with Claude Code

dfa1 and others added 2 commits July 6, 2026 21:44
cellValue() formatted integer cells by array class alone (Byte/Short/Int/
LongArray), never consulting dtype signedness. High-half values in U8/U16/
U32/U64 columns were printed as their two's-complement negatives — silent
value corruption on export.

Byte/ShortArray.getInt already zero-extend per their dtype, so those two arms
now widen to int and print signed decimal (correct for both signed and
unsigned). Int/LongArray have no dtype-aware getter, so a small isUnsigned()
helper gates Integer.toUnsignedString / Long.toUnsignedString on the ptype.
The helper reads dtype().ptype(), which a MaskedArray's inner values array
carries identically, so the nullable recursion stays correct.

Real-world evidence: uci-wine `magnesium` (U8?, values 70–162) exported row
34 as -124 instead of 132; palmer-penguins `flipper_length_mm` (U8, 172–231)
exported every row negative. Verified the uci-wine export now matches the
parquet oracle (132) after the fix.

Closes #208

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dfa1

dfa1 commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Reviewed (vortex-reviewer agent, full diff + tests executed at the PR ref): approve. Correctness verified on all axes — signed I8/I16 output unchanged (dtype-gated zero-extension only), MaskedArray recursion consults the leaf array's ptype, isUnsigned total over non-primitive dtypes, U32/U64 expectations arithmetically checked. Two follow-ups applied in 1d01c08: reworded a misplaced javadoc clause on isUnsigned, and added an explicit signed-I8 -5 regression test (9/9 green).

@dfa1 dfa1 merged commit 66d32f3 into main Jul 6, 2026
6 checks passed
@dfa1 dfa1 deleted the fix/208-unsigned-csv-rendering branch July 6, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSV export renders unsigned integers as signed — silent value corruption (U8 132 → -124)

1 participant