Skip to content

Conversation

@kumarUjjawal
Copy link
Contributor

Which issue does this PR close?

Rationale for this change

The math UDF lacks the support for the decimal for the round.

What changes are included in this PR?

  • Add round support for Decimal32/64/128/256 while preserving original precision/scale (no implicit cast to Float64).
  • Added SLT coverage for decimal round

Are these changes tested?

Are there any user-facing changes?

@github-actions github-actions bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels Dec 18, 2025
Copy link
Contributor

@Jefffrey Jefffrey left a comment

Choose a reason for hiding this comment

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

Thanks for picking this up, left a few comments

@kumarUjjawal
Copy link
Contributor Author

@Jefffrey made some changes, take a look when you get the time.

Copy link
Contributor

@Jefffrey Jefffrey left a comment

Choose a reason for hiding this comment

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

Minor suggestion for refactor, otherwise looks good

round_decimal_i256(value, scale, decimal_places)
}

fn round_decimal_i128(
Copy link
Contributor

Choose a reason for hiding this comment

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

We can replace these decimal implementations with a single generic version:

fn round_decimal<V: ArrowNativeTypeOp>(
    value: V,
    scale: i8,
    decimal_places: i32,
) -> Result<V, ArrowError> {
    let diff = scale as i32 - decimal_places;
    if diff <= 0 {
        return Ok(value);
    }
    let diff = diff as u32; // safe since we check sign above

    // these can't truncate/overflow
    let one = V::ONE;
    let two = V::from_usize(2).unwrap();
    let ten = V::from_usize(10).unwrap();

    let factor = ten.pow_checked(diff).map_err(|_| {
        ArrowError::ComputeError(format!(
            "Overflow while rounding decimal with scale {scale} and decimal places {decimal_places}"
        ))
    })?;

    let mut quotient = value.div_wrapping(factor);
    let remainder = value.mod_wrapping(factor);

    // `factor` is an even number (10^n, n > 0), so `factor / 2` is the tie threshold
    let threshold = factor.div_wrapping(two);
    if remainder >= threshold {
        quotient = quotient.add_checked(one).map_err(|_| {
            ArrowError::ComputeError("Overflow while rounding decimal".into())
        })?;
    } else if remainder <= threshold.neg_wrapping() {
        quotient = quotient.sub_checked(one).map_err(|_| {
            ArrowError::ComputeError("Overflow while rounding decimal".into())
        })?;
    }

    quotient
        .mul_checked(factor)
        .map_err(|_| ArrowError::ComputeError("Overflow while rounding decimal".into()))
}

Then can call them directly like so:

        Decimal128(precision, scale) => {
            let result = calculate_binary_decimal_math::<
                Decimal128Type,
                Int32Type,
                Decimal128Type,
                _,
            >(
                value_array.as_ref(),
                decimal_places,
                |v, dp| round_decimal(v, *scale, dp), // <--- here
                *precision,
                *scale,
            )?;
            result as _
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@Jefffrey Jefffrey added this pull request to the merge queue Dec 23, 2025
Merged via the queue into apache:main with commit 72f1746 Dec 23, 2025
27 checks passed
@Jefffrey
Copy link
Contributor

Thanks @kumarUjjawal

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

Labels

functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ROUND support decimal

2 participants