Skip to content

fix(arrow/decimal): handle native word widths in big integer conversions - #1409

Merged
zeroshade merged 2 commits into
apache:mainfrom
zeroshade:fix-386-decimal-ree-validate
Oct 26, 2026
Merged

zeroshade merged 2 commits into
apache:mainfrom
zeroshade:fix-386-decimal-ree-validate

Conversation

@zeroshade

@zeroshade zeroshade commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Rationale for this change

Found while fixing the 32-bit test failures in #1408.

Decimal conversions are wrong on 32-bit platforms. decimal128.Num and decimal256.Num use 64-bit limbs, but their big.Int conversions assumed big.Word is also 64 bits. On 386 it is 32 bits, so:

  • decimal128.FromBigInt read b[0]/b[1] as the low and high limbs (decimal128.go:95–97), misplacing words and dropping the rest;
  • decimal128's toBigIntPositive built []big.Word{big.Word(n.lo), big.Word(n.hi)} (decimal128.go:362), truncating each limb;
  • decimal256.FromBigInt copied word i into limb i (decimal256.go:370), misplacing words and panicking when a value needed more than four native words;
  • decimal256's toBigIntPositive truncated its limbs the same way (decimal256.go:379).

Anything going through big.Int (string formatting and parsing, rescaling, GetOneForMarshal, CSV decimal parsing) produced wrong values or spurious precision errors on 386, e.g. TestDecimal128Slice printed -1717986918 instead of -7.378697629e+18, and TestDecimal128GetOneForMarshal turned 1234567890.123456789 into 437278697.08398250845211745659132109676.

The run-end-encoded validation fix that was originally part of this PR has moved to #1421.

What changes are included in this PR?

  • arrow/decimal128, arrow/decimal256: when bits.UintSize == 32, combine pairs of 32-bit big.Words into each 64-bit limb in FromBigInt, and split each limb into two words in toBigIntPositive. bits.UintSize is a constant, so 64-bit builds compile the existing code unchanged. Also corrects the decimal128.FromBigInt doc (it panics above 127 bits, as the code already does).
  • CI: the 386 job also runs the decimal packages' tests.

Are these changes tested?

Yes.

  • On GOARCH=386: TestDecimal128Slice, TestDecimal128StringRoundTrip, TestDecimal128GetOneForMarshal, TestDecimal256Slice, TestDecimal256GetOneForMarshal, all nine TestDecimal/TestWithNulls#01 precisions, TestDecimalScalarStringCastUsesExactScaling and CSV's TestReadCSVDecimalCols all failed before and pass now. The TestDecimalToBigInt tests in both packages gain word-boundary cases with independently computed expectations, which fail on 386 without the fix (decimal256 panics). arrow/decimal128, arrow/decimal256 and arrow/decimal/... pass in full on 386.
  • amd64: arrow/decimal128, arrow/decimal256, arrow/decimal/... pass.
  • go vet and gofmt -l are clean on the changed packages.

Are there any user-facing changes?

Yes: decimal values convert correctly on 32-bit platforms. No API changes.

@lidavidm lidavidm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Since the changes are unrelated, perhaps we can separate them into two PRs?)

Decimal128 and Decimal256 treated math/big.Word as a 64-bit limb. On
32-bit platforms this truncated and misplaced coefficient bits, and
Decimal256.FromBigInt could index past its four-limb array.

Combine and split pairs of 32-bit words while preserving the native
64-bit conversion paths. Cover each word boundary and independently
check FromBigInt against literal expected coefficients.

Generated-by: OpenAI Codex (GPT-6 Astra)
The decimal128/decimal256 big.Int conversion fix has 32-bit-only code
paths (bits.UintSize == 32), but the 386 job only ran parquet/file tests,
so CI never exercised them. The decimal packages now pass on 386; add
them to the job.
@zeroshade
zeroshade force-pushed the fix-386-decimal-ree-validate branch from 822195d to 458cf82 Compare October 26, 2026 15:25
@zeroshade zeroshade changed the title fix(arrow): decimal big.Int conversions on 32-bit and validation of bufferless run-end encoded arrays fix(arrow/decimal): handle native word widths in big integer conversions Oct 26, 2026
@zeroshade
zeroshade merged commit 8100cd7 into apache:main Oct 26, 2026
23 checks passed
zeroshade added a commit that referenced this pull request Oct 26, 2026
### Rationale for this change

Split out of #1409.

`ValidateFull` rejects run-end-encoded arrays from the builder. A REE
array from `NewRunEndEncodedBuilder` (or a `Concatenate` of such arrays)
fails with `expected at least 1 buffers for run_end_encoded<...>, got
0`, on every platform. The REE layout declares one always-null
placeholder buffer (`datatype_encoded.go:40`); `NewRunEndEncodedArray`
and the IPC reader supply a `nil` placeholder, but the builder and C
Data import produce zero buffers. The Arrow spec says REE arrays have no
physical buffers, so both representations are legitimate.

### What changes are included in this PR?

`arrow/array/validate.go` accepts a REE array (including extension types
with REE storage) that has no buffers. Arrays with a `nil` placeholder
go through the existing checks as before, and non-nil or extra parent
buffers are still rejected. Constructors, layouts, IPC and C Data are
unchanged.

### Are these changes tested?

Yes. New `TestRunEndEncodedValidateConstructedArrays` checks that
builder-produced, empty, sliced, constructor-built, `Concatenate`d and
REE-extension arrays pass `ValidateFull`. The builder, `Concatenate`,
empty, sliced and extension cases fail before the fix; constructor cases
passed already. Existing rejection cases (bad run ends, non-nil/extra
parent buffers) still fail validation. `go vet` and `gofmt -l` are clean
on `arrow/array`.

### Are there any user-facing changes?

Yes: `ValidateFull` accepts run-end-encoded arrays produced by the
builder, `Concatenate` and C Data import. No API changes.
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.

2 participants