martin-g commented on code in PR #3619:
URL: https://github.com/apache/datafusion-comet/pull/3619#discussion_r2889422328


##########
native/spark-expr/src/math_funcs/internal/decimal_rescale_check.rs:
##########
@@ -0,0 +1,431 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused decimal rescale + overflow check expression.
+//!
+//! Replaces the pattern `CheckOverflow(Cast(expr, Decimal128(p2,s2)), 
Decimal128(p2,s2))`
+//! with a single expression that rescales and validates precision in one pass.
+
+use arrow::array::{as_primitive_array, Array, ArrayRef, Decimal128Array};
+use arrow::datatypes::{DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::hash::Hash;
+use std::{
+    any::Any,
+    fmt::{Display, Formatter},
+    sync::Arc,
+};
+
+/// A fused expression that rescales a Decimal128 value (changing scale) and 
checks
+/// for precision overflow in a single pass. Replaces the two-step
+/// `CheckOverflow(Cast(expr, Decimal128(p,s)))` pattern.
+#[derive(Debug, Eq)]
+pub struct DecimalRescaleCheckOverflow {
+    child: Arc<dyn PhysicalExpr>,
+    input_scale: i8,
+    output_precision: u8,
+    output_scale: i8,
+    fail_on_error: bool,
+}
+
+impl Hash for DecimalRescaleCheckOverflow {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.child.hash(state);
+        self.input_scale.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.fail_on_error.hash(state);
+    }
+}
+
+impl PartialEq for DecimalRescaleCheckOverflow {
+    fn eq(&self, other: &Self) -> bool {
+        self.child.eq(&other.child)
+            && self.input_scale == other.input_scale
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.fail_on_error == other.fail_on_error
+    }
+}
+
+impl DecimalRescaleCheckOverflow {
+    pub fn new(
+        child: Arc<dyn PhysicalExpr>,
+        input_scale: i8,
+        output_precision: u8,
+        output_scale: i8,
+        fail_on_error: bool,
+    ) -> Self {
+        Self {
+            child,
+            input_scale,
+            output_precision,
+            output_scale,
+            fail_on_error,
+        }
+    }
+}
+
+impl Display for DecimalRescaleCheckOverflow {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "DecimalRescaleCheckOverflow [child: {}, input_scale: {}, output: 
Decimal128({}, {}), fail_on_error: {}]",
+            self.child, self.input_scale, self.output_precision, 
self.output_scale, self.fail_on_error
+        )
+    }
+}
+
+/// Maximum absolute value for a given decimal precision: 10^p - 1.
+#[inline]
+fn precision_bound(precision: u8) -> i128 {
+    10i128.pow(precision as u32) - 1

Review Comment:
   maybe add some validation that `precision` is <=38 ?
   or use checked_pow() and return a Result



##########
native/spark-expr/src/math_funcs/wide_decimal_binary_expr.rs:
##########
@@ -0,0 +1,502 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused wide-decimal binary expression for Decimal128 add/sub/mul that may 
overflow.
+//!
+//! Instead of building a 4-node expression tree (Cast→BinaryExpr→Cast→Cast), 
this performs
+//! i256 intermediate arithmetic in a single expression, producing only one 
output array.
+
+use crate::math_funcs::utils::get_precision_scale;
+use crate::EvalMode;
+use arrow::array::{Array, ArrayRef, AsArray, Decimal128Array};
+use arrow::datatypes::{i256, DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::Result;
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::fmt::{Display, Formatter};
+use std::hash::Hash;
+use std::{any::Any, sync::Arc};
+
+/// The arithmetic operation to perform.
+#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
+pub enum WideDecimalOp {
+    Add,
+    Subtract,
+    Multiply,
+}
+
+impl Display for WideDecimalOp {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            WideDecimalOp::Add => write!(f, "+"),
+            WideDecimalOp::Subtract => write!(f, "-"),
+            WideDecimalOp::Multiply => write!(f, "*"),
+        }
+    }
+}
+
+/// A fused expression that evaluates Decimal128 add/sub/mul using i256 
intermediate arithmetic,
+/// applies scale adjustment with HALF_UP rounding, checks precision bounds, 
and outputs
+/// a single Decimal128 array.
+#[derive(Debug, Eq)]
+pub struct WideDecimalBinaryExpr {
+    left: Arc<dyn PhysicalExpr>,
+    right: Arc<dyn PhysicalExpr>,
+    op: WideDecimalOp,
+    output_precision: u8,
+    output_scale: i8,
+    eval_mode: EvalMode,
+}
+
+impl Hash for WideDecimalBinaryExpr {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.left.hash(state);
+        self.right.hash(state);
+        self.op.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.eval_mode.hash(state);
+    }
+}
+
+impl PartialEq for WideDecimalBinaryExpr {
+    fn eq(&self, other: &Self) -> bool {
+        self.left.eq(&other.left)
+            && self.right.eq(&other.right)
+            && self.op == other.op
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.eval_mode == other.eval_mode
+    }
+}
+
+impl WideDecimalBinaryExpr {
+    pub fn new(
+        left: Arc<dyn PhysicalExpr>,
+        right: Arc<dyn PhysicalExpr>,
+        op: WideDecimalOp,
+        output_precision: u8,
+        output_scale: i8,
+        eval_mode: EvalMode,
+    ) -> Self {
+        Self {
+            left,
+            right,
+            op,
+            output_precision,
+            output_scale,
+            eval_mode,
+        }
+    }
+}
+
+impl Display for WideDecimalBinaryExpr {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "WideDecimalBinaryExpr [{} {} {}, output: Decimal128({}, {})]",
+            self.left, self.op, self.right, self.output_precision, 
self.output_scale
+        )
+    }
+}
+
+/// Compute `value / divisor` with HALF_UP rounding.
+#[inline]
+fn div_round_half_up(value: i256, divisor: i256) -> i256 {
+    let (quot, rem) = (value / divisor, value % divisor);
+    // HALF_UP: if |remainder| * 2 >= |divisor|, round away from zero
+    let abs_rem_x2 = if rem < i256::ZERO {
+        rem.wrapping_neg()
+    } else {
+        rem
+    }
+    .wrapping_mul(i256::from_i128(2));
+    let abs_divisor = if divisor < i256::ZERO {
+        divisor.wrapping_neg()
+    } else {
+        divisor
+    };
+    if abs_rem_x2 >= abs_divisor {
+        if (value < i256::ZERO) != (divisor < i256::ZERO) {
+            quot.wrapping_sub(i256::ONE)
+        } else {
+            quot.wrapping_add(i256::ONE)
+        }
+    } else {
+        quot
+    }
+}
+
+/// i256 constant for 10.
+const I256_TEN: i256 = i256::from_i128(10);
+
+/// Compute 10^exp as i256.
+#[inline]
+fn i256_pow10(exp: u32) -> i256 {
+    let mut result = i256::ONE;
+    for _ in 0..exp {
+        result = result.wrapping_mul(I256_TEN);
+    }
+    result
+}
+
+/// Maximum i128 value for a given decimal precision (1-indexed).
+/// precision p allows values in [-10^p + 1, 10^p - 1].
+#[inline]
+fn max_for_precision(precision: u8) -> i256 {
+    i256_pow10(precision as u32).wrapping_sub(i256::ONE)
+}
+
+impl PhysicalExpr for WideDecimalBinaryExpr {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
+        Ok(DataType::Decimal128(
+            self.output_precision,
+            self.output_scale,
+        ))
+    }
+
+    fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
+        Ok(true)
+    }
+
+    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
+        let left_val = self.left.evaluate(batch)?;
+        let right_val = self.right.evaluate(batch)?;
+
+        let (left_arr, right_arr): (ArrayRef, ArrayRef) = match (&left_val, 
&right_val) {
+            (ColumnarValue::Array(l), ColumnarValue::Array(r)) => 
(Arc::clone(l), Arc::clone(r)),
+            (ColumnarValue::Scalar(l), ColumnarValue::Array(r)) => {
+                (l.to_array_of_size(r.len())?, Arc::clone(r))
+            }
+            (ColumnarValue::Array(l), ColumnarValue::Scalar(r)) => {
+                (Arc::clone(l), r.to_array_of_size(l.len())?)
+            }
+            (ColumnarValue::Scalar(l), ColumnarValue::Scalar(r)) => 
(l.to_array()?, r.to_array()?),
+        };
+
+        let left = left_arr.as_primitive::<Decimal128Type>();
+        let right = right_arr.as_primitive::<Decimal128Type>();
+        let (_p1, s1) = get_precision_scale(left.data_type());
+        let (_p2, s2) = get_precision_scale(right.data_type());
+
+        let p_out = self.output_precision;
+        let s_out = self.output_scale;
+        let op = self.op;
+        let eval_mode = self.eval_mode;
+
+        let bound = max_for_precision(p_out);
+        let neg_bound = i256::ZERO.wrapping_sub(bound);
+
+        let result: Decimal128Array = match op {
+            WideDecimalOp::Add | WideDecimalOp::Subtract => {
+                let max_scale = std::cmp::max(s1, s2);
+                let l_scale_up = i256_pow10((max_scale - s1) as u32);
+                let r_scale_up = i256_pow10((max_scale - s2) as u32);
+                let need_rescale = s_out < max_scale;
+                let rescale_divisor = if need_rescale {
+                    i256_pow10((max_scale - s_out) as u32)
+                } else {
+                    i256::ONE
+                };
+
+                arrow::compute::kernels::arity::try_binary(left, right, |l, r| 
{
+                    let l256 = i256::from_i128(l).wrapping_mul(l_scale_up);
+                    let r256 = i256::from_i128(r).wrapping_mul(r_scale_up);
+                    let raw = match op {
+                        WideDecimalOp::Add => l256.wrapping_add(r256),
+                        WideDecimalOp::Subtract => l256.wrapping_sub(r256),
+                        _ => unreachable!(),
+                    };
+                    let result = if need_rescale {
+                        div_round_half_up(raw, rescale_divisor)
+                    } else {
+                        raw
+                    };
+                    check_overflow_and_convert(result, bound, neg_bound, 
eval_mode)
+                })?
+            }
+            WideDecimalOp::Multiply => {
+                let natural_scale = s1 + s2;
+                let need_rescale = s_out < natural_scale;
+                let rescale_divisor = if need_rescale {
+                    i256_pow10((natural_scale - s_out) as u32)
+                } else {
+                    i256::ONE
+                };
+
+                arrow::compute::kernels::arity::try_binary(left, right, |l, r| 
{
+                    let raw = 
i256::from_i128(l).wrapping_mul(i256::from_i128(r));
+                    let result = if need_rescale {
+                        div_round_half_up(raw, rescale_divisor)
+                    } else {
+                        raw
+                    };
+                    check_overflow_and_convert(result, bound, neg_bound, 
eval_mode)
+                })?
+            }
+        };
+
+        let result = if eval_mode != EvalMode::Ansi {
+            result.null_if_overflow_precision(p_out)
+        } else {
+            result
+        };
+        let result = result.with_data_type(DataType::Decimal128(p_out, s_out));
+        Ok(ColumnarValue::Array(Arc::new(result)))
+    }
+
+    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
+        vec![&self.left, &self.right]
+    }
+
+    fn with_new_children(
+        self: Arc<Self>,
+        children: Vec<Arc<dyn PhysicalExpr>>,
+    ) -> Result<Arc<dyn PhysicalExpr>> {

Review Comment:
   ```suggestion
       ) -> Result<Arc<dyn PhysicalExpr>> {
           if children.len() != 2 {
               return Err(DataFusionError::Internal(format!(
                   "WideDecimalBinaryExpr expects 2 children, got {}",
                   children.len()
               )));
           }
   ```



##########
native/core/src/execution/planner.rs:
##########
@@ -376,10 +377,37 @@ impl PhysicalPlanner {
                 )))
             }
             ExprStruct::CheckOverflow(expr) => {
-                let child = self.create_expr(expr.child.as_ref().unwrap(), 
input_schema)?;
+                let child =
+                    self.create_expr(expr.child.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
                 let data_type = 
to_arrow_datatype(expr.datatype.as_ref().unwrap());
                 let fail_on_error = expr.fail_on_error;
 
+                // WideDecimalBinaryExpr already handles overflow — skip 
redundant check
+                if child
+                    .as_any()
+                    .downcast_ref::<WideDecimalBinaryExpr>()
+                    .is_some()
+                {
+                    return Ok(child);
+                }
+
+                // Fuse Cast(Decimal128→Decimal128) + CheckOverflow into 
single rescale+check
+                if let Some(cast) = child.as_any().downcast_ref::<Cast>() {

Review Comment:
   Should this also check that the Cast's precision/scale match the output's 
p/s before fusing ?



##########
native/core/src/execution/planner.rs:
##########
@@ -674,31 +702,31 @@ impl PhysicalPlanner {
         ) {
             (
                 DataFusionOperator::Plus | DataFusionOperator::Minus | 
DataFusionOperator::Multiply,
-                Ok(DataType::Decimal128(p1, s1)),
-                Ok(DataType::Decimal128(p2, s2)),
+                Ok(DataType::Decimal128(_p1, _s1)),

Review Comment:
   What is the reason to use `_` prefix ? The variables are used



##########
native/spark-expr/src/math_funcs/internal/decimal_rescale_check.rs:
##########
@@ -0,0 +1,431 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused decimal rescale + overflow check expression.
+//!
+//! Replaces the pattern `CheckOverflow(Cast(expr, Decimal128(p2,s2)), 
Decimal128(p2,s2))`
+//! with a single expression that rescales and validates precision in one pass.
+
+use arrow::array::{as_primitive_array, Array, ArrayRef, Decimal128Array};
+use arrow::datatypes::{DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::hash::Hash;
+use std::{
+    any::Any,
+    fmt::{Display, Formatter},
+    sync::Arc,
+};
+
+/// A fused expression that rescales a Decimal128 value (changing scale) and 
checks
+/// for precision overflow in a single pass. Replaces the two-step
+/// `CheckOverflow(Cast(expr, Decimal128(p,s)))` pattern.
+#[derive(Debug, Eq)]
+pub struct DecimalRescaleCheckOverflow {
+    child: Arc<dyn PhysicalExpr>,
+    input_scale: i8,
+    output_precision: u8,
+    output_scale: i8,
+    fail_on_error: bool,
+}
+
+impl Hash for DecimalRescaleCheckOverflow {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.child.hash(state);
+        self.input_scale.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.fail_on_error.hash(state);
+    }
+}
+
+impl PartialEq for DecimalRescaleCheckOverflow {
+    fn eq(&self, other: &Self) -> bool {
+        self.child.eq(&other.child)
+            && self.input_scale == other.input_scale
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.fail_on_error == other.fail_on_error
+    }
+}
+
+impl DecimalRescaleCheckOverflow {
+    pub fn new(
+        child: Arc<dyn PhysicalExpr>,
+        input_scale: i8,
+        output_precision: u8,
+        output_scale: i8,
+        fail_on_error: bool,
+    ) -> Self {
+        Self {
+            child,
+            input_scale,
+            output_precision,
+            output_scale,
+            fail_on_error,
+        }
+    }
+}
+
+impl Display for DecimalRescaleCheckOverflow {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "DecimalRescaleCheckOverflow [child: {}, input_scale: {}, output: 
Decimal128({}, {}), fail_on_error: {}]",
+            self.child, self.input_scale, self.output_precision, 
self.output_scale, self.fail_on_error
+        )
+    }
+}
+
+/// Maximum absolute value for a given decimal precision: 10^p - 1.
+#[inline]
+fn precision_bound(precision: u8) -> i128 {
+    10i128.pow(precision as u32) - 1
+}
+
+/// Rescale a single i128 value by the given delta (output_scale - input_scale)
+/// and check precision bounds. Returns `Ok(value)` or `Ok(i128::MAX)` as 
sentinel
+/// for overflow in legacy mode, or `Err` in ANSI mode.
+#[inline]
+fn rescale_and_check(
+    value: i128,
+    delta: i8,
+    scale_factor: i128,
+    bound: i128,
+    fail_on_error: bool,
+) -> Result<i128, ArrowError> {
+    let rescaled = if delta > 0 {
+        // Scale up: multiply. Check for overflow.
+        match value.checked_mul(scale_factor) {
+            Some(v) => v,
+            None => {
+                if fail_on_error {
+                    return Err(ArrowError::ComputeError(
+                        "Decimal overflow during rescale".to_string(),
+                    ));
+                }
+                return Ok(i128::MAX); // sentinel
+            }
+        }
+    } else if delta < 0 {
+        // Scale down with HALF_UP rounding
+        // divisor = 10^(-delta), half = divisor / 2
+        let divisor = scale_factor; // already 10^abs(delta)
+        let half = divisor / 2;
+        let sign = if value < 0 { -1i128 } else { 1i128 };
+        (value + sign * half) / divisor
+    } else {
+        value
+    };
+
+    // Precision check
+    if rescaled.abs() > bound {
+        if fail_on_error {
+            return Err(ArrowError::ComputeError(
+                "Decimal overflow: value does not fit in 
precision".to_string(),
+            ));
+        }
+        Ok(i128::MAX) // sentinel for null_if_overflow_precision
+    } else {
+        Ok(rescaled)
+    }
+}
+
+impl PhysicalExpr for DecimalRescaleCheckOverflow {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        Display::fmt(self, f)
+    }
+
+    fn data_type(&self, _: &Schema) -> datafusion::common::Result<DataType> {
+        Ok(DataType::Decimal128(
+            self.output_precision,
+            self.output_scale,
+        ))
+    }
+
+    fn nullable(&self, _: &Schema) -> datafusion::common::Result<bool> {
+        Ok(true)
+    }
+
+    fn evaluate(&self, batch: &RecordBatch) -> 
datafusion::common::Result<ColumnarValue> {
+        let arg = self.child.evaluate(batch)?;
+        let delta = self.output_scale - self.input_scale;

Review Comment:
   The scales could be negative.
   Let's say `output_scale=38` ( the maximum supported one) and 
`input_scale=-1`, the `delta=39`.
   This will lead to an error below at `10i128.pow(abs_delta as u32);`



##########
native/spark-expr/src/math_funcs/wide_decimal_binary_expr.rs:
##########
@@ -0,0 +1,502 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused wide-decimal binary expression for Decimal128 add/sub/mul that may 
overflow.
+//!
+//! Instead of building a 4-node expression tree (Cast→BinaryExpr→Cast→Cast), 
this performs
+//! i256 intermediate arithmetic in a single expression, producing only one 
output array.
+
+use crate::math_funcs::utils::get_precision_scale;
+use crate::EvalMode;
+use arrow::array::{Array, ArrayRef, AsArray, Decimal128Array};
+use arrow::datatypes::{i256, DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::Result;
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::fmt::{Display, Formatter};
+use std::hash::Hash;
+use std::{any::Any, sync::Arc};
+
+/// The arithmetic operation to perform.
+#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
+pub enum WideDecimalOp {
+    Add,
+    Subtract,
+    Multiply,
+}
+
+impl Display for WideDecimalOp {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            WideDecimalOp::Add => write!(f, "+"),
+            WideDecimalOp::Subtract => write!(f, "-"),
+            WideDecimalOp::Multiply => write!(f, "*"),
+        }
+    }
+}
+
+/// A fused expression that evaluates Decimal128 add/sub/mul using i256 
intermediate arithmetic,
+/// applies scale adjustment with HALF_UP rounding, checks precision bounds, 
and outputs
+/// a single Decimal128 array.
+#[derive(Debug, Eq)]
+pub struct WideDecimalBinaryExpr {
+    left: Arc<dyn PhysicalExpr>,
+    right: Arc<dyn PhysicalExpr>,
+    op: WideDecimalOp,
+    output_precision: u8,
+    output_scale: i8,
+    eval_mode: EvalMode,
+}
+
+impl Hash for WideDecimalBinaryExpr {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.left.hash(state);
+        self.right.hash(state);
+        self.op.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.eval_mode.hash(state);
+    }
+}
+
+impl PartialEq for WideDecimalBinaryExpr {
+    fn eq(&self, other: &Self) -> bool {
+        self.left.eq(&other.left)
+            && self.right.eq(&other.right)
+            && self.op == other.op
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.eval_mode == other.eval_mode
+    }
+}
+
+impl WideDecimalBinaryExpr {
+    pub fn new(
+        left: Arc<dyn PhysicalExpr>,
+        right: Arc<dyn PhysicalExpr>,
+        op: WideDecimalOp,
+        output_precision: u8,
+        output_scale: i8,
+        eval_mode: EvalMode,
+    ) -> Self {
+        Self {
+            left,
+            right,
+            op,
+            output_precision,
+            output_scale,
+            eval_mode,
+        }
+    }
+}
+
+impl Display for WideDecimalBinaryExpr {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "WideDecimalBinaryExpr [{} {} {}, output: Decimal128({}, {})]",
+            self.left, self.op, self.right, self.output_precision, 
self.output_scale
+        )
+    }
+}
+
+/// Compute `value / divisor` with HALF_UP rounding.
+#[inline]
+fn div_round_half_up(value: i256, divisor: i256) -> i256 {
+    let (quot, rem) = (value / divisor, value % divisor);
+    // HALF_UP: if |remainder| * 2 >= |divisor|, round away from zero
+    let abs_rem_x2 = if rem < i256::ZERO {
+        rem.wrapping_neg()
+    } else {
+        rem
+    }
+    .wrapping_mul(i256::from_i128(2));
+    let abs_divisor = if divisor < i256::ZERO {
+        divisor.wrapping_neg()
+    } else {
+        divisor
+    };
+    if abs_rem_x2 >= abs_divisor {
+        if (value < i256::ZERO) != (divisor < i256::ZERO) {
+            quot.wrapping_sub(i256::ONE)
+        } else {
+            quot.wrapping_add(i256::ONE)
+        }
+    } else {
+        quot
+    }
+}
+
+/// i256 constant for 10.
+const I256_TEN: i256 = i256::from_i128(10);
+
+/// Compute 10^exp as i256.
+#[inline]
+fn i256_pow10(exp: u32) -> i256 {

Review Comment:
   extreme case: `exp>77` will overflow



##########
native/spark-expr/src/math_funcs/internal/decimal_rescale_check.rs:
##########
@@ -0,0 +1,431 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused decimal rescale + overflow check expression.
+//!
+//! Replaces the pattern `CheckOverflow(Cast(expr, Decimal128(p2,s2)), 
Decimal128(p2,s2))`
+//! with a single expression that rescales and validates precision in one pass.
+
+use arrow::array::{as_primitive_array, Array, ArrayRef, Decimal128Array};
+use arrow::datatypes::{DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::hash::Hash;
+use std::{
+    any::Any,
+    fmt::{Display, Formatter},
+    sync::Arc,
+};
+
+/// A fused expression that rescales a Decimal128 value (changing scale) and 
checks
+/// for precision overflow in a single pass. Replaces the two-step
+/// `CheckOverflow(Cast(expr, Decimal128(p,s)))` pattern.
+#[derive(Debug, Eq)]
+pub struct DecimalRescaleCheckOverflow {
+    child: Arc<dyn PhysicalExpr>,
+    input_scale: i8,
+    output_precision: u8,
+    output_scale: i8,
+    fail_on_error: bool,
+}
+
+impl Hash for DecimalRescaleCheckOverflow {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.child.hash(state);
+        self.input_scale.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.fail_on_error.hash(state);
+    }
+}
+
+impl PartialEq for DecimalRescaleCheckOverflow {
+    fn eq(&self, other: &Self) -> bool {
+        self.child.eq(&other.child)
+            && self.input_scale == other.input_scale
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.fail_on_error == other.fail_on_error
+    }
+}
+
+impl DecimalRescaleCheckOverflow {
+    pub fn new(
+        child: Arc<dyn PhysicalExpr>,
+        input_scale: i8,
+        output_precision: u8,
+        output_scale: i8,
+        fail_on_error: bool,
+    ) -> Self {
+        Self {
+            child,
+            input_scale,
+            output_precision,
+            output_scale,
+            fail_on_error,
+        }
+    }
+}
+
+impl Display for DecimalRescaleCheckOverflow {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "DecimalRescaleCheckOverflow [child: {}, input_scale: {}, output: 
Decimal128({}, {}), fail_on_error: {}]",
+            self.child, self.input_scale, self.output_precision, 
self.output_scale, self.fail_on_error
+        )
+    }
+}
+
+/// Maximum absolute value for a given decimal precision: 10^p - 1.
+#[inline]
+fn precision_bound(precision: u8) -> i128 {
+    10i128.pow(precision as u32) - 1
+}
+
+/// Rescale a single i128 value by the given delta (output_scale - input_scale)
+/// and check precision bounds. Returns `Ok(value)` or `Ok(i128::MAX)` as 
sentinel
+/// for overflow in legacy mode, or `Err` in ANSI mode.
+#[inline]
+fn rescale_and_check(
+    value: i128,
+    delta: i8,
+    scale_factor: i128,
+    bound: i128,
+    fail_on_error: bool,
+) -> Result<i128, ArrowError> {
+    let rescaled = if delta > 0 {
+        // Scale up: multiply. Check for overflow.
+        match value.checked_mul(scale_factor) {
+            Some(v) => v,
+            None => {
+                if fail_on_error {
+                    return Err(ArrowError::ComputeError(
+                        "Decimal overflow during rescale".to_string(),
+                    ));
+                }
+                return Ok(i128::MAX); // sentinel
+            }
+        }
+    } else if delta < 0 {
+        // Scale down with HALF_UP rounding
+        // divisor = 10^(-delta), half = divisor / 2
+        let divisor = scale_factor; // already 10^abs(delta)
+        let half = divisor / 2;
+        let sign = if value < 0 { -1i128 } else { 1i128 };
+        (value + sign * half) / divisor
+    } else {
+        value
+    };
+
+    // Precision check
+    if rescaled.abs() > bound {
+        if fail_on_error {
+            return Err(ArrowError::ComputeError(
+                "Decimal overflow: value does not fit in 
precision".to_string(),
+            ));
+        }
+        Ok(i128::MAX) // sentinel for null_if_overflow_precision
+    } else {
+        Ok(rescaled)
+    }
+}
+
+impl PhysicalExpr for DecimalRescaleCheckOverflow {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        Display::fmt(self, f)
+    }
+
+    fn data_type(&self, _: &Schema) -> datafusion::common::Result<DataType> {
+        Ok(DataType::Decimal128(
+            self.output_precision,
+            self.output_scale,
+        ))
+    }
+
+    fn nullable(&self, _: &Schema) -> datafusion::common::Result<bool> {
+        Ok(true)
+    }
+
+    fn evaluate(&self, batch: &RecordBatch) -> 
datafusion::common::Result<ColumnarValue> {
+        let arg = self.child.evaluate(batch)?;
+        let delta = self.output_scale - self.input_scale;
+        let abs_delta = delta.unsigned_abs();
+        let scale_factor = 10i128.pow(abs_delta as u32);
+        let bound = precision_bound(self.output_precision);
+        let fail_on_error = self.fail_on_error;
+        let p_out = self.output_precision;
+        let s_out = self.output_scale;
+
+        match arg {
+            ColumnarValue::Array(array)
+                if matches!(array.data_type(), DataType::Decimal128(_, _)) =>
+            {
+                let decimal_array = 
as_primitive_array::<Decimal128Type>(&array);
+
+                let result: Decimal128Array =
+                    arrow::compute::kernels::arity::try_unary(decimal_array, 
|value| {
+                        rescale_and_check(value, delta, scale_factor, bound, 
fail_on_error)
+                    })?;
+
+                let result = if !fail_on_error {
+                    result.null_if_overflow_precision(p_out)
+                } else {
+                    result
+                };
+
+                let result = result
+                    .with_precision_and_scale(p_out, s_out)
+                    .map(|a| Arc::new(a) as ArrayRef)?;
+
+                Ok(ColumnarValue::Array(result))
+            }
+            ColumnarValue::Scalar(ScalarValue::Decimal128(v, _precision, 
_scale)) => {
+                let new_v = v.and_then(|val| {
+                    rescale_and_check(val, delta, scale_factor, bound, 
fail_on_error)
+                        .ok()

Review Comment:
   If `fail_on_error=true` then this `.ok()` will convert it to `None` and hide 
the error



##########
native/spark-expr/src/math_funcs/internal/decimal_rescale_check.rs:
##########
@@ -0,0 +1,431 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused decimal rescale + overflow check expression.
+//!
+//! Replaces the pattern `CheckOverflow(Cast(expr, Decimal128(p2,s2)), 
Decimal128(p2,s2))`
+//! with a single expression that rescales and validates precision in one pass.
+
+use arrow::array::{as_primitive_array, Array, ArrayRef, Decimal128Array};
+use arrow::datatypes::{DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::hash::Hash;
+use std::{
+    any::Any,
+    fmt::{Display, Formatter},
+    sync::Arc,
+};
+
+/// A fused expression that rescales a Decimal128 value (changing scale) and 
checks
+/// for precision overflow in a single pass. Replaces the two-step
+/// `CheckOverflow(Cast(expr, Decimal128(p,s)))` pattern.
+#[derive(Debug, Eq)]
+pub struct DecimalRescaleCheckOverflow {
+    child: Arc<dyn PhysicalExpr>,
+    input_scale: i8,
+    output_precision: u8,
+    output_scale: i8,
+    fail_on_error: bool,
+}
+
+impl Hash for DecimalRescaleCheckOverflow {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.child.hash(state);
+        self.input_scale.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.fail_on_error.hash(state);
+    }
+}
+
+impl PartialEq for DecimalRescaleCheckOverflow {
+    fn eq(&self, other: &Self) -> bool {
+        self.child.eq(&other.child)
+            && self.input_scale == other.input_scale
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.fail_on_error == other.fail_on_error
+    }
+}
+
+impl DecimalRescaleCheckOverflow {
+    pub fn new(
+        child: Arc<dyn PhysicalExpr>,
+        input_scale: i8,
+        output_precision: u8,
+        output_scale: i8,
+        fail_on_error: bool,
+    ) -> Self {
+        Self {
+            child,
+            input_scale,
+            output_precision,
+            output_scale,
+            fail_on_error,
+        }
+    }
+}
+
+impl Display for DecimalRescaleCheckOverflow {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "DecimalRescaleCheckOverflow [child: {}, input_scale: {}, output: 
Decimal128({}, {}), fail_on_error: {}]",
+            self.child, self.input_scale, self.output_precision, 
self.output_scale, self.fail_on_error
+        )
+    }
+}
+
+/// Maximum absolute value for a given decimal precision: 10^p - 1.
+#[inline]
+fn precision_bound(precision: u8) -> i128 {
+    10i128.pow(precision as u32) - 1
+}
+
+/// Rescale a single i128 value by the given delta (output_scale - input_scale)
+/// and check precision bounds. Returns `Ok(value)` or `Ok(i128::MAX)` as 
sentinel
+/// for overflow in legacy mode, or `Err` in ANSI mode.
+#[inline]
+fn rescale_and_check(
+    value: i128,
+    delta: i8,
+    scale_factor: i128,
+    bound: i128,
+    fail_on_error: bool,
+) -> Result<i128, ArrowError> {
+    let rescaled = if delta > 0 {
+        // Scale up: multiply. Check for overflow.
+        match value.checked_mul(scale_factor) {
+            Some(v) => v,
+            None => {
+                if fail_on_error {
+                    return Err(ArrowError::ComputeError(
+                        "Decimal overflow during rescale".to_string(),
+                    ));
+                }
+                return Ok(i128::MAX); // sentinel
+            }
+        }
+    } else if delta < 0 {
+        // Scale down with HALF_UP rounding
+        // divisor = 10^(-delta), half = divisor / 2
+        let divisor = scale_factor; // already 10^abs(delta)
+        let half = divisor / 2;
+        let sign = if value < 0 { -1i128 } else { 1i128 };
+        (value + sign * half) / divisor
+    } else {
+        value
+    };
+
+    // Precision check
+    if rescaled.abs() > bound {
+        if fail_on_error {
+            return Err(ArrowError::ComputeError(
+                "Decimal overflow: value does not fit in 
precision".to_string(),
+            ));
+        }
+        Ok(i128::MAX) // sentinel for null_if_overflow_precision
+    } else {
+        Ok(rescaled)
+    }
+}
+
+impl PhysicalExpr for DecimalRescaleCheckOverflow {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        Display::fmt(self, f)
+    }
+
+    fn data_type(&self, _: &Schema) -> datafusion::common::Result<DataType> {
+        Ok(DataType::Decimal128(
+            self.output_precision,
+            self.output_scale,
+        ))
+    }
+
+    fn nullable(&self, _: &Schema) -> datafusion::common::Result<bool> {
+        Ok(true)
+    }
+
+    fn evaluate(&self, batch: &RecordBatch) -> 
datafusion::common::Result<ColumnarValue> {
+        let arg = self.child.evaluate(batch)?;
+        let delta = self.output_scale - self.input_scale;
+        let abs_delta = delta.unsigned_abs();
+        let scale_factor = 10i128.pow(abs_delta as u32);
+        let bound = precision_bound(self.output_precision);
+        let fail_on_error = self.fail_on_error;
+        let p_out = self.output_precision;
+        let s_out = self.output_scale;
+
+        match arg {
+            ColumnarValue::Array(array)
+                if matches!(array.data_type(), DataType::Decimal128(_, _)) =>
+            {
+                let decimal_array = 
as_primitive_array::<Decimal128Type>(&array);
+
+                let result: Decimal128Array =
+                    arrow::compute::kernels::arity::try_unary(decimal_array, 
|value| {
+                        rescale_and_check(value, delta, scale_factor, bound, 
fail_on_error)
+                    })?;
+
+                let result = if !fail_on_error {
+                    result.null_if_overflow_precision(p_out)
+                } else {
+                    result
+                };
+
+                let result = result
+                    .with_precision_and_scale(p_out, s_out)
+                    .map(|a| Arc::new(a) as ArrayRef)?;
+
+                Ok(ColumnarValue::Array(result))
+            }
+            ColumnarValue::Scalar(ScalarValue::Decimal128(v, _precision, 
_scale)) => {
+                let new_v = v.and_then(|val| {
+                    rescale_and_check(val, delta, scale_factor, bound, 
fail_on_error)
+                        .ok()
+                        .and_then(|r| if r == i128::MAX { None } else { 
Some(r) })
+                });
+                Ok(ColumnarValue::Scalar(ScalarValue::Decimal128(
+                    new_v, p_out, s_out,
+                )))
+            }
+            v => Err(DataFusionError::Execution(format!(
+                "DecimalRescaleCheckOverflow expects Decimal128, but found 
{v:?}"
+            ))),
+        }
+    }
+
+    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
+        vec![&self.child]
+    }
+
+    fn with_new_children(
+        self: Arc<Self>,
+        children: Vec<Arc<dyn PhysicalExpr>>,
+    ) -> datafusion::common::Result<Arc<dyn PhysicalExpr>> {

Review Comment:
   ```suggestion
       ) -> datafusion::common::Result<Arc<dyn PhysicalExpr>> {
           if children.len() != 1 {
               return Err(DataFusionError::Internal(format!(
                   "DecimalRescaleCheckOverflow expects 1 child, got {}",
                       children.len()
               )));
           }
   ```
   



##########
native/spark-expr/src/math_funcs/internal/decimal_rescale_check.rs:
##########
@@ -0,0 +1,431 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Fused decimal rescale + overflow check expression.
+//!
+//! Replaces the pattern `CheckOverflow(Cast(expr, Decimal128(p2,s2)), 
Decimal128(p2,s2))`
+//! with a single expression that rescales and validates precision in one pass.
+
+use arrow::array::{as_primitive_array, Array, ArrayRef, Decimal128Array};
+use arrow::datatypes::{DataType, Decimal128Type, Schema};
+use arrow::error::ArrowError;
+use arrow::record_batch::RecordBatch;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::logical_expr::ColumnarValue;
+use datafusion::physical_expr::PhysicalExpr;
+use std::hash::Hash;
+use std::{
+    any::Any,
+    fmt::{Display, Formatter},
+    sync::Arc,
+};
+
+/// A fused expression that rescales a Decimal128 value (changing scale) and 
checks
+/// for precision overflow in a single pass. Replaces the two-step
+/// `CheckOverflow(Cast(expr, Decimal128(p,s)))` pattern.
+#[derive(Debug, Eq)]
+pub struct DecimalRescaleCheckOverflow {
+    child: Arc<dyn PhysicalExpr>,
+    input_scale: i8,
+    output_precision: u8,
+    output_scale: i8,
+    fail_on_error: bool,
+}
+
+impl Hash for DecimalRescaleCheckOverflow {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.child.hash(state);
+        self.input_scale.hash(state);
+        self.output_precision.hash(state);
+        self.output_scale.hash(state);
+        self.fail_on_error.hash(state);
+    }
+}
+
+impl PartialEq for DecimalRescaleCheckOverflow {
+    fn eq(&self, other: &Self) -> bool {
+        self.child.eq(&other.child)
+            && self.input_scale == other.input_scale
+            && self.output_precision == other.output_precision
+            && self.output_scale == other.output_scale
+            && self.fail_on_error == other.fail_on_error
+    }
+}
+
+impl DecimalRescaleCheckOverflow {
+    pub fn new(
+        child: Arc<dyn PhysicalExpr>,
+        input_scale: i8,
+        output_precision: u8,
+        output_scale: i8,
+        fail_on_error: bool,
+    ) -> Self {
+        Self {
+            child,
+            input_scale,
+            output_precision,
+            output_scale,
+            fail_on_error,
+        }
+    }
+}
+
+impl Display for DecimalRescaleCheckOverflow {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "DecimalRescaleCheckOverflow [child: {}, input_scale: {}, output: 
Decimal128({}, {}), fail_on_error: {}]",
+            self.child, self.input_scale, self.output_precision, 
self.output_scale, self.fail_on_error
+        )
+    }
+}
+
+/// Maximum absolute value for a given decimal precision: 10^p - 1.
+#[inline]
+fn precision_bound(precision: u8) -> i128 {
+    10i128.pow(precision as u32) - 1
+}
+
+/// Rescale a single i128 value by the given delta (output_scale - input_scale)
+/// and check precision bounds. Returns `Ok(value)` or `Ok(i128::MAX)` as 
sentinel
+/// for overflow in legacy mode, or `Err` in ANSI mode.
+#[inline]
+fn rescale_and_check(
+    value: i128,
+    delta: i8,
+    scale_factor: i128,
+    bound: i128,
+    fail_on_error: bool,
+) -> Result<i128, ArrowError> {
+    let rescaled = if delta > 0 {
+        // Scale up: multiply. Check for overflow.
+        match value.checked_mul(scale_factor) {
+            Some(v) => v,
+            None => {
+                if fail_on_error {
+                    return Err(ArrowError::ComputeError(
+                        "Decimal overflow during rescale".to_string(),
+                    ));
+                }
+                return Ok(i128::MAX); // sentinel
+            }
+        }
+    } else if delta < 0 {
+        // Scale down with HALF_UP rounding
+        // divisor = 10^(-delta), half = divisor / 2
+        let divisor = scale_factor; // already 10^abs(delta)
+        let half = divisor / 2;
+        let sign = if value < 0 { -1i128 } else { 1i128 };

Review Comment:
   ```suggestion
           let sign = value.signum();
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to