Skip to content

Decimal casting - #8562

Merged
gatesn merged 4 commits into
developfrom
ngates/fix-decimal-casting
Jun 24, 2026
Merged

Decimal casting#8562
gatesn merged 4 commits into
developfrom
ngates/fix-decimal-casting

Conversation

@gatesn

@gatesn gatesn commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

No description provided.

gatesn added 2 commits June 23, 2026 16:47
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
@gatesn
gatesn requested a review from a team June 23, 2026 21:20
@gatesn gatesn added the changelog/feature A new feature label Jun 23, 2026
Use array_session in decimal cast tests so the PR merge ref matches develop's test-session import pattern.

I, Nicholas Gates <nick@nickgates.com>, hereby add my Signed-off-by to this commit: 25713a5

I, Nicholas Gates <nick@nickgates.com>, hereby add my Signed-off-by to this commit: 8948181

Signed-off-by: Nicholas Gates <nick@nickgates.com>
@codspeed-hq

codspeed-hq Bot commented Jun 23, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚡ 4 improved benchmarks
❌ 2 regressed benchmarks
✅ 1579 untouched benchmarks
⏩ 4 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation chunked_varbinview_canonical_into[(1000, 10)] 154.8 µs 191 µs -18.99%
Simulation slice_empty_vortex 310 ns 368.3 ns -15.84%
Simulation chunked_bool_canonical_into[(1000, 10)] 26.7 µs 16.2 µs +64.55%
Simulation chunked_varbinview_canonical_into[(100, 100)] 259.1 µs 224.4 µs +15.49%
Simulation chunked_varbinview_into_canonical[(100, 100)] 305.7 µs 271.4 µs +12.63%
Simulation bitwise_not_vortex_buffer_mut[128] 273.6 ns 244.4 ns +11.93%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing ngates/fix-decimal-casting (e2178ef) with develop (4668b6e)

Open in CodSpeed

Footnotes

  1. 4 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Generalize the decimal CastKernel buffer-reuse fast path: when the scale is
unchanged, the target precision only widens, and the current physical value
type already fits the target precision, reuse the values buffer instead of
allocating and re-scanning every lane. This restores the zero-copy behavior
the vectorized cast regressed for the common widening case (e.g. casting a
Decimal(10,2) i64 array to Decimal(18,2)).

Also assert (debug-only) that the vectorized fast path and the scalar slow
path agree: decimal_cast_error's success arm is unreachable unless they drift.

Add a regression test asserting the widening cast shares the source buffer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Nicholas Gates <nick@nickgates.com>
@gatesn
gatesn enabled auto-merge (squash) June 23, 2026 23:33
@gatesn
gatesn merged commit 1118a20 into develop Jun 24, 2026
94 of 96 checks passed
@gatesn
gatesn deleted the ngates/fix-decimal-casting branch June 24, 2026 02:03

assert_eq!(casted.dtype(), &target_dtype);
let scalar = casted
.execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NOOOO, I have just fixed all the tests to not use it.

Comment on lines +146 to +195
fn cast_decimal_values<F, T>(
array: ArrayView<'_, Decimal>,
from_decimal_dtype: DecimalDType,
to_decimal_dtype: DecimalDType,
validity: Validity,
valid_values: &Mask,
) -> VortexResult<ArrayRef>
where
F: NativeDecimalType,
T: NativeDecimalType + CheckedMul,
DecimalValue: From<F>,
{
let values = array.buffer::<F>();
let values = values.as_slice();
let cast_plan = DecimalCastPlan::<T>::new(from_decimal_dtype, to_decimal_dtype);

let buffer = match valid_values {
Mask::AllTrue(_) => {
let mut buffer = BufferMut::<T>::with_capacity(values.len());
values
.try_map_into(&mut buffer.spare_capacity_mut()[..values.len()], |value| {
cast_plan.cast(value)
})
.map_err(|idx| {
decimal_cast_error::<F, T>(values[idx], from_decimal_dtype, to_decimal_dtype)
})?;
// SAFETY: try_map_into initializes every lane before returning Ok.
unsafe { buffer.set_len(values.len()) };
buffer.freeze()
}
Mask::AllFalse(_) => BufferMut::<T>::zeroed(values.len()).freeze(),
Mask::Values(mask) => {
let mut buffer = BufferMut::<T>::with_capacity(values.len());
values
.try_map_masked_into(
mask.bit_buffer(),
&mut buffer.spare_capacity_mut()[..values.len()],
|value| cast_plan.cast(value),
)
.map_err(|idx| {
decimal_cast_error::<F, T>(values[idx], from_decimal_dtype, to_decimal_dtype)
})?;
// SAFETY: try_map_masked_into initializes every lane before returning Ok.
unsafe { buffer.set_len(values.len()) };
buffer.freeze()
}
};

Ok(DecimalArray::new(buffer, to_decimal_dtype, validity).into_array())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should use the same logic that PrimitiveArray cast uses

Comment on lines +164 to +169
let rescaled = Self::rescale_i256(
self.as_i256(),
from_decimal_dtype.scale(),
to_decimal_dtype.scale(),
)?;
Self::try_from_i256(rescaled, to_decimal_dtype)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why would this need to widen to i256?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see, it's a bit less code to do it in i256


let scaled = DecimalValue::rescale_i256(value, 0, decimal_dtype.scale())?;
DecimalValue::try_from_i256(scaled, decimal_dtype)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems lazy? There's 3 conversions here which seems excessive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants