Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
38de09b
feat(fixed_point): optional sticky overflow tracking on doing decimal…
Avinash-Raj May 2, 2026
cb1e7c9
feat(fixed_point): NTTP-based opt-in overflow tracking with decimal*_…
Avinash-Raj May 5, 2026
927fe61
Track float→decimal overflow for decimal*_safe
Avinash-Raj May 7, 2026
6a33a77
feat(binaryop): add binary_operation_safe with device overflow flag f…
Avinash-Raj May 11, 2026
281dce6
reverted modifications to fixed_point class and introduced free funct…
Avinash-Raj May 16, 2026
063f1d7
Merge branch 'main' into avi/decimal-overflow
Avinash-Raj May 16, 2026
f874b5e
introduced compile-time branching in convert-float-to-integer functio…
Avinash-Raj May 22, 2026
d738fe8
Fix -INT_MIN UB in convert_floating_to_integral and unify sign-reapply
Avinash-Raj May 23, 2026
736169f
Return per-row overflow column from binary_operation_safe
Avinash-Raj May 26, 2026
e378183
Make multiply_power10_saturating O(1) by precomputing the overflow th…
Avinash-Raj May 27, 2026
0e0d29c
Revert unrelated changes to binary_ops.cuh
Avinash-Raj May 27, 2026
ad57bd8
fixed_point: restore IIFE form in convert_floating_to_fixed
Avinash-Raj May 27, 2026
2cb1ace
Added floating conversion overflow tests
Avinash-Raj May 27, 2026
69095e1
Plumb mr through binary_operation_safe for exec_policy_nosync
Avinash-Raj Jun 1, 2026
eee3690
Merge branch 'main' into avi/decimal-overflow
Avinash-Raj Jun 1, 2026
73b68f8
Merge branch 'avi/decimal-overflow' of github.com:Avinash-Raj/cudf in…
Avinash-Raj Jun 1, 2026
72b132a
Addressed all the comments written by CodeRabbit
Avinash-Raj Jun 1, 2026
a585cfa
fix ci-styles
Avinash-Raj Jun 1, 2026
670af6c
Merge branch 'main' into avi/decimal-overflow
Avinash-Raj Jun 2, 2026
98dda38
Restore Rep return type in convert_floating_to_integral_shifting for …
Avinash-Raj Jun 2, 2026
9e5d015
Skip arithmetic on the overflow path in safe_* fixed-point primitives
Avinash-Raj Jun 3, 2026
c237180
Merge branch 'main' into avi/decimal-overflow
Avinash-Raj Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ mark_as_advanced(CUDF_BUILD_STREAMS_TEST_UTIL)
option(CUDF_CLANG_TIDY "Enable clang-tidy during compilation" OFF)
option(CUDF_IWYU "Enable IWYU during compilation" OFF)
option(CUDF_CLANG_TIDY_AUTOFIX "Enable clang-tidy autofixes" OFF)

option(
CUDF_KVIKIO_REMOTE_IO
"Enable remote IO (e.g. AWS S3) support through KvikIO. If disabled, cudf-python will still be able to do remote IO through fsspec."
Expand Down Expand Up @@ -452,6 +451,7 @@ add_library(
src/binaryop/compiled/Sub.cu
src/binaryop/compiled/TrueDiv.cu
src/binaryop/compiled/binary_ops.cu
src/binaryop/compiled/binary_ops_safe.cu
src/binaryop/compiled/equality_ops.cu
src/binaryop/compiled/util.cpp
src/labeling/label_bins.cu
Expand Down
42 changes: 42 additions & 0 deletions cpp/include/cudf/binaryop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <cudf/utilities/export.hpp>
#include <cudf/utilities/memory_resource.hpp>

#include <cstdint>
#include <memory>
#include <utility>

namespace CUDF_EXPORT cudf {

Expand Down Expand Up @@ -221,6 +223,46 @@ std::unique_ptr<column> binary_operation(
rmm::cuda_stream_view stream = cudf::get_default_stream(),
rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref());

/**
* @brief Decimal fixed-point binary operation with a per-row overflow column.
*
* Like `binary_operation` for two base-10 decimal operands, but additionally returns a
* `BOOL8` column of the same length as the result. Element `i` of that column is `true`
* iff row `i` is an **active** (non-null on both sides) row whose arithmetic or rescale
* to @p output_type overflowed. Rows that are null on either side, or rows whose
* arithmetic completed without overflow, hold `false`. The overflow column has no null
* mask.
*
* Supported operators: ADD, SUB, MUL, DIV, MOD, PMOD, PYMOD.
*
* @return A pair `{result, overflow}` where `result` is the arithmetic result column
* (same semantics as `binary_operation`) and `overflow` is the per-row BOOL8
* overflow column described above.
*/
std::pair<std::unique_ptr<column>, std::unique_ptr<column>> binary_operation_safe(

@PointKernel PointKernel Jun 2, 2026 •

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.

Compared to ansi, I do like the safe naming though it's not perfect.

Actually, safe is probably not a great choice either as we already have existing uses of unsafe to denote an operation is not thread safe, e.g. https://github.com/rapidsai/cudf/blob/4162d61633c332be91851126df388fb6489aac9c/cpp/include/cudf/utilities/bit.hpp#L73

AFAIK, we currently have three different naming schemes in libcudf related to overflow checking:

  1. For aggregation, we use _WITH_OVERFLOW suffix to denote it has overflow check https://github.com/rapidsai/cudf/blob/aa3cdee199d636d0075b6ae165d8ed09aff0b92a/cpp/include/cudf/aggregation.hpp#L81
  2. For JIT operators, we use the SQL term ansi, e.g. https://github.com/rapidsai/cudf/blob/6f8c429d0a532bdfd5d5e4057643addec6f5f486/cpp/include/cudf/detail/operators/ansi_arithmetic.cuh#L32
  3. The current effort with safe prefix/suffix

We should really converge on a consistent naming scheme going forward to avoid further fragmentation and branching.

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.

Yes, we've agreed on using an "_overflow" suffix

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.

Another minor difference I wanted to mention: in the SUM_WITH_OVERFLOW aggregation, both reduce and groupby currently return a single STRUCT column, where the first field is the sum and the second is the overflow flag.

It would be good to align with the existing pattern, or at least converge on a consistent one. That said, it’s not immediately obvious how to adjust the groupby or reduce code paths, since the input-output mapping assumes one input column projects to one output column, and there’s no straightforward way to change that.

Changing the output here from two columns to a single struct column seems relatively straightforward, but I’m not sure how important this consistency is in practice, or whether it’s worth enforcing it across the board.

scalar const& lhs,
column_view const& rhs,
binary_operator op,
data_type output_type,
rmm::cuda_stream_view stream = cudf::get_default_stream(),
rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref());

std::pair<std::unique_ptr<column>, std::unique_ptr<column>> binary_operation_safe(

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.

can you make this behaviour part of binary_operation instead?

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.

What behavior are you referring to?

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.

the error checking. rather than having binary_operation and binary_operation_safe, we should have a single binary_operation with enums to specify whether to check for errors.

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 disagree with this suggestion.

column_view const& lhs,
scalar const& rhs,
binary_operator op,
data_type output_type,
rmm::cuda_stream_view stream = cudf::get_default_stream(),
rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref());

std::pair<std::unique_ptr<column>, std::unique_ptr<column>> binary_operation_safe(
column_view const& lhs,
column_view const& rhs,
binary_operator op,
data_type output_type,
rmm::cuda_stream_view stream = cudf::get_default_stream(),
rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref());
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @brief Performs a binary operation between two columns using a
* user-defined PTX function.
Expand Down
241 changes: 192 additions & 49 deletions cpp/include/cudf/fixed_point/detail/floating_conversion.hpp

Large diffs are not rendered by default.

273 changes: 273 additions & 0 deletions cpp/include/cudf/fixed_point/detail/safe_arithmetic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cudf/fixed_point/detail/floating_conversion.hpp>
#include <cudf/fixed_point/fixed_point.hpp>
#include <cudf/utilities/export.hpp>
#include <cudf/utilities/traits.hpp>

#include <cuda/std/type_traits>
#include <cuda/std/utility>

/**
* @file safe_arithmetic.hpp
* @brief Overflow-aware free functions for `numeric::fixed_point`.
*
* These free functions intentionally live outside of `fixed_point` itself
* (mirroring how `floating_conversion.hpp` lives outside `fixed_point` for the
* float<->decimal conversion path). They take regular, non-tracking
* `fixed_point` operands and return a `safe_result` that pairs the computed
* value with an `overflow` flag describing whether the operation -- including
* any internal rescale -- would have wrapped the underlying integer storage.
*
* The intent is that overflow-aware code paths (the `binary_operation_safe`
* kernel today; specialized reduce/groupby aggregations in the future) compose
* these primitives explicitly, instead of relying on a sticky bit baked into
* the value type.
*/

namespace CUDF_EXPORT numeric {
namespace detail {

/**
* @brief Result of an overflow-checked `fixed_point` operation.
*
* @tparam Rep Storage type of the wrapped `fixed_point` value
* @tparam Rad Radix of the wrapped `fixed_point` value
*/
template <typename Rep, Radix Rad>
struct safe_result {
fixed_point<Rep, Rad> value; ///< Computed `fixed_point` value
bool overflow; ///< Whether the producing operation overflowed
};
Comment on lines +48 to +52

@lamarrr lamarrr Jun 5, 2026 •

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.

can you replace this and all occurrences with cuda::std::expected<decimal, errc>? to be consistent with CUDF's operator library


/**
* @brief Whether `shift<Rep, Rad>(val, scale)` would incur signed-integer overflow
*
* Mirrors the overflow conditions of `multiplication_overflow` /
* `division_overflow` on the intermediate scale factor.
*
* @tparam Rep Representation type
* @tparam Rad Radix
* @tparam T Type of the value being shifted (typically `Rep`)
* @param val The value being shifted
* @param scale The amount to shift the value by
* @return true if the shift would overflow `Rep`, false otherwise
*/
template <typename Rep, Radix Rad, typename T>
CUDF_HOST_DEVICE inline constexpr bool shift_overflows(T const& val, scale_type const& scale)
{
auto const v = static_cast<Rep>(val);
if (scale == 0) { return false; }
if (scale > 0) {
Rep const divisor = ipow<Rep, Rad>(static_cast<int32_t>(scale));
return division_overflow<Rep>(v, divisor);
}
Rep const multiplier = ipow<Rep, Rad>(static_cast<int32_t>(-scale));
return multiplication_overflow<Rep>(v, multiplier);
}

/**
* @brief Rescale a `fixed_point` value, reporting whether the underlying shift overflows
*
* Equivalent to `x.rescaled(new_scale)` but surfaces the shift overflow flag
* instead of dropping it.
*
* @tparam Rep Storage type
* @tparam Rad Radix
* @param x The value to rescale
* @param new_scale The target scale
* @return `{rescaled_value, overflow}`
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_rescaled(fixed_point<Rep, Rad> x,
scale_type new_scale)
{
if (new_scale == x.scale()) { return safe_result<Rep, Rad>{x, false}; }
auto const scale_delta = scale_type{new_scale - x.scale()};
bool const overflow = shift_overflows<Rep, Rad>(x.value(), scale_delta);
Rep const value = shift<Rep, Rad>(x.value(), scale_delta);
return safe_result<Rep, Rad>{fixed_point<Rep, Rad>{scaled_integer<Rep>{value, new_scale}},
overflow};
}

/**
* @brief Overflow-checked addition of two `fixed_point` values
*
* Rescales both operands to the smaller of their two scales (matching
* `operator+`), then performs the add. The returned `overflow` flag is the
* disjunction of any rescale overflow and the integer add overflow.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_add(fixed_point<Rep, Rad> lhs,
Comment thread
PointKernel marked this conversation as resolved.
fixed_point<Rep, Rad> rhs)
{
auto const common_scale = cuda::std::min(lhs.scale(), rhs.scale());
auto const lhs_r = safe_rescaled(lhs, common_scale);
auto const rhs_r = safe_rescaled(rhs, common_scale);
Rep const lv = lhs_r.value.value();
Rep const rv = rhs_r.value.value();
bool const op_overflow = addition_overflow<Rep>(lv, rv);
Rep const sum = lv + rv;
return safe_result<Rep, Rad>{
fixed_point<Rep, Rad>{scaled_integer<Rep>{sum, common_scale}},
op_overflow || lhs_r.overflow || rhs_r.overflow};
}

/**
* @brief Overflow-checked subtraction of two `fixed_point` values
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_sub(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
auto const common_scale = cuda::std::min(lhs.scale(), rhs.scale());
auto const lhs_r = safe_rescaled(lhs, common_scale);
auto const rhs_r = safe_rescaled(rhs, common_scale);
Rep const lv = lhs_r.value.value();
Rep const rv = rhs_r.value.value();
bool const op_overflow = subtraction_overflow<Rep>(lv, rv);
Rep const diff = lv - rv;
return safe_result<Rep, Rad>{
fixed_point<Rep, Rad>{scaled_integer<Rep>{diff, common_scale}},
op_overflow || lhs_r.overflow || rhs_r.overflow};
}

/**
* @brief Overflow-checked multiplication of two `fixed_point` values
*
* No rescale is needed -- the result scale is `lhs.scale() + rhs.scale()`.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_mul(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
Rep const lv = lhs.value();
Rep const rv = rhs.value();
bool const op_overflow = multiplication_overflow<Rep>(lv, rv);
Rep const prod = lv * rv;
scale_type const out_scale{lhs.scale() + rhs.scale()};
return safe_result<Rep, Rad>{fixed_point<Rep, Rad>{scaled_integer<Rep>{prod, out_scale}},
op_overflow};
}

/**
* @brief Overflow-checked division of two `fixed_point` values
*
* Only `INT_MIN / -1` is a signed-integer division overflow; that's exactly what
* `division_overflow` catches.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_div(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
Rep const lv = lhs.value();
Rep const rv = rhs.value();
bool const op_overflow = division_overflow<Rep>(lv, rv);
Rep const quot = lv / rv;
scale_type const out_scale{lhs.scale() - rhs.scale()};
return safe_result<Rep, Rad>{fixed_point<Rep, Rad>{scaled_integer<Rep>{quot, out_scale}},
op_overflow};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

/**
* @brief Overflow-checked modulo of two `fixed_point` values
*
* Integer `%` itself cannot overflow when both operands are representable, so
* the only overflow source is the rescale to the common scale.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_mod(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
auto const common_scale = cuda::std::min(lhs.scale(), rhs.scale());
auto const lhs_r = safe_rescaled(lhs, common_scale);
auto const rhs_r = safe_rescaled(rhs, common_scale);
Rep const remainder = lhs_r.value.value() % rhs_r.value.value();
return safe_result<Rep, Rad>{
fixed_point<Rep, Rad>{scaled_integer<Rep>{remainder, common_scale}},
lhs_r.overflow || rhs_r.overflow};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

/**
* @brief Overflow-checked positive modulo, matching `ops::PMod` semantics for decimals.
*
* Implements `rem = x % y; (rem < 0) ? (rem + y) % y : rem`. The intermediate
* `rem + y` can overflow even though `%` itself cannot, so this surfaces the
* add overflow in addition to the rescale overflows reported by `safe_mod`.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_pmod(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
auto const m = safe_mod(lhs, rhs);
if (!(m.value.value() < Rep{0})) { return m; }

auto const rhs_r = safe_rescaled(rhs, m.value.scale());
Rep const mv = m.value.value();
Rep const rv = rhs_r.value.value();
bool const add_overflow = addition_overflow<Rep>(mv, rv);
Rep const corrected = (mv + rv) % rv;
return safe_result<Rep, Rad>{
fixed_point<Rep, Rad>{scaled_integer<Rep>{corrected, m.value.scale()}},
m.overflow || rhs_r.overflow || add_overflow};
}

/**
* @brief Overflow-checked Python-style modulo: `((x % y) + y) % y`.
*
* The intermediate add can overflow; the final `%` cannot.
*/
template <typename Rep, Radix Rad>
CUDF_HOST_DEVICE inline safe_result<Rep, Rad> safe_pymod(fixed_point<Rep, Rad> lhs,
fixed_point<Rep, Rad> rhs)
{
auto const m = safe_mod(lhs, rhs);

auto const rhs_r = safe_rescaled(rhs, m.value.scale());
Rep const mv = m.value.value();
Rep const rv = rhs_r.value.value();
bool const add_overflow = addition_overflow<Rep>(mv, rv);
Rep const corrected = (mv + rv) % rv;
return safe_result<Rep, Rad>{
fixed_point<Rep, Rad>{scaled_integer<Rep>{corrected, m.value.scale()}},
m.overflow || rhs_r.overflow || add_overflow};
}

/**
* @brief Overflow-checked floating-point -> `fixed_point` conversion
*
* Uses `convert_floating_to_integral<Rep, true>` (in
* `floating_conversion.hpp`) for base-10 decimals, which saturates and reports
* overflow. For base-2 radixes there is no checked path today, so `overflow`
* is always `false`.
*
* @tparam Fixed Target `fixed_point` instantiation
* @tparam Floating Source floating-point type
* @param floating The floating-point value to convert
* @param scale The desired scale of the result
* @return `{fixed_point_value, overflow}`
*/
template <typename Fixed,
typename Floating,
CUDF_ENABLE_IF(cuda::std::is_floating_point_v<Floating> &&
cudf::is_fixed_point<Fixed>())>
CUDF_HOST_DEVICE inline safe_result<typename Fixed::rep, Fixed::rad>
safe_convert_floating_to_fixed(Floating floating, scale_type scale)
{
using Rep = typename Fixed::rep;
if constexpr (Fixed::rad == Radix::BASE_10) {
auto const [value, overflow] = convert_floating_to_integral<Rep, true>(floating, scale);
return safe_result<Rep, Fixed::rad>{Fixed{scaled_integer<Rep>{value, scale}}, overflow};
} else {
Rep const value = static_cast<Rep>(shift<Rep, Fixed::rad>(floating, scale));
return safe_result<Rep, Fixed::rad>{Fixed{scaled_integer<Rep>{value, scale}}, false};
}
}

} // namespace detail
} // namespace CUDF_EXPORT numeric
Comment on lines +43 to +299

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.

can you please confirm each of these operators hasn't been implemented in cudf/detail/operators/*?

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.

please also merge these into cudf/detail/operators/ansi_arithmetic.cuh or cudf/detail/operators/ansi_casts.cuh.

@Avinash-Raj Avinash-Raj Jun 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ansi_arithmetic.cuh already provides overflow-checked add/sub/mul/div/mod (and abs/neg/precision_check), so it overlaps with safe_add/safe_sub/safe_mul/safe_div.

However, it doesn't detect rescale-induced overflow in add/sub
( Given the overlap, we should converge the two — e.g. have one delegate to the other ie, _safe kernel delegating to ansi_* (after closing ansi_*'s coverage gaps), not changing binary_operation itself).

The main reason for this implementation is that, by default, Velox GPU/Velox-cuDF silently wraps the result on decimal arithmetic overflow instead of throwing an error.

@lamarrr lamarrr Jun 5, 2026 •

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'm not convinced scale-induced overflow/underflow needs to be checked in the device code, whilst it is useful to have.
It is a metadata that should be checked before computing the operators.
Can you please move it outside of the operators and add it as a pre-kernel-launch error check instead.

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.

It might be worth doing benchmark before trying this. A single kernel to do both seems like it would be faster than 2 separate kernels but a benchmark would certainly show the impact.

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.

The scale is the same for all the rows of a column. So it is trivial to compute and check before launching the binary operation (2 scalars). AST has a similar setup. It's what Spark RAPIDS does.

@Avinash-Raj Avinash-Raj Jun 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @lamarrr. Two separate things here:

Scale/type validity -> agreed, I'll add a pre-kernel check. Scales are column metadata (same for all rows), so I can add a host-side CUDF_EXPECTS guard before launch that rejects invalid scale/output-type combos and fails fast without launching the kernel.

Rescale overflow -> has to stay per-row. Whether a rescale overflows depends on each row's value, not just the scale:

DECIMAL64, common rescale ×10^-3
row 0: rep = 5      → 5000        (Pass)
row 1: rep = 9.2e15 → overflows   (Fail)

Same scale metadata, different outcome-> so a scalar pre-check can't produce the per-row BOOL8 this API exposes .

So the metadata check is additive. A cheap early guard but doesn't remove the kernel's per-row checks.

@lamarrr lamarrr Jun 22, 2026 •

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.

can you instead implement overflow checks into "checked_arithmetic.cuh" header, using #22836 as base?
Otherwise, reuse the equivalent functions in "checked_arithmetic.cuh". We'd like to reduce code duplication

8 changes: 0 additions & 8 deletions cpp/include/cudf/fixed_point/fixed_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -663,10 +663,8 @@ CUDF_HOST_DEVICE inline fixed_point<Rep1, Rad1> operator+(fixed_point<Rep1, Rad1
auto const sum = lhs.rescaled(scale)._value + rhs.rescaled(scale)._value;

#if defined(__CUDACC_DEBUG__)

assert(!addition_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
"fixed_point overflow");

#endif

return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{sum, scale}};
Expand All @@ -681,10 +679,8 @@ CUDF_HOST_DEVICE inline fixed_point<Rep1, Rad1> operator-(fixed_point<Rep1, Rad1
auto const diff = lhs.rescaled(scale)._value - rhs.rescaled(scale)._value;

#if defined(__CUDACC_DEBUG__)

assert(!subtraction_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
"fixed_point overflow");

#endif

return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{diff, scale}};
Expand All @@ -696,9 +692,7 @@ CUDF_HOST_DEVICE inline fixed_point<Rep1, Rad1> operator*(fixed_point<Rep1, Rad1
fixed_point<Rep1, Rad1> const& rhs)
{
#if defined(__CUDACC_DEBUG__)

assert(!multiplication_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");

#endif

return fixed_point<Rep1, Rad1>{
Expand All @@ -711,9 +705,7 @@ CUDF_HOST_DEVICE inline fixed_point<Rep1, Rad1> operator/(fixed_point<Rep1, Rad1
fixed_point<Rep1, Rad1> const& rhs)
{
#if defined(__CUDACC_DEBUG__)

assert(!division_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");

#endif

return fixed_point<Rep1, Rad1>{
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/cudf/utilities/type_dispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ constexpr bool type_id_matches_device_storage_type(type_id id)
{
return (id == type_id::DECIMAL32 && std::is_same_v<T, int32_t>) ||
(id == type_id::DECIMAL64 && std::is_same_v<T, int64_t>) ||
(id == type_id::DECIMAL128 && std::is_same_v<T, __int128_t>) || id == type_to_id<T>();
(id == type_id::DECIMAL128 && std::is_same_v<T, __int128_t>) ||
id == type_to_id<T>();
}

/**
Expand Down
Loading
Loading