Skip to main content

datafusion_functions/math/
floor.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use arrow::array::{ArrayRef, AsArray};
21use arrow::compute::{DecimalCast, rescale_decimal};
22use arrow::datatypes::{
23    ArrowNativeTypeOp, DataType, Decimal32Type, Decimal64Type, Decimal128Type,
24    Decimal256Type, DecimalType, Float32Type, Float64Type,
25};
26use datafusion_common::{Result, ScalarValue, exec_err};
27use datafusion_expr::interval_arithmetic::Interval;
28use datafusion_expr::preimage::PreimageResult;
29use datafusion_expr::simplify::SimplifyContext;
30use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
31use datafusion_expr::{
32    Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDFImpl,
33    Signature, TypeSignature, TypeSignatureClass, Volatility,
34};
35use datafusion_macros::user_doc;
36use num_traits::{CheckedAdd, Float, One};
37
38use super::decimal::{apply_decimal_op, floor_decimal_value};
39
40#[user_doc(
41    doc_section(label = "Math Functions"),
42    description = "Returns the nearest integer less than or equal to a number.",
43    syntax_example = "floor(numeric_expression)",
44    standard_argument(name = "numeric_expression", prefix = "Numeric"),
45    sql_example = r#"```sql
46> SELECT floor(3.14);
47+-------------+
48| floor(3.14) |
49+-------------+
50| 3.0         |
51+-------------+
52```"#
53)]
54#[derive(Debug, PartialEq, Eq, Hash)]
55pub struct FloorFunc {
56    signature: Signature,
57}
58
59impl Default for FloorFunc {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl FloorFunc {
66    pub fn new() -> Self {
67        let decimal_sig = Coercion::new_exact(TypeSignatureClass::Decimal);
68        Self {
69            signature: Signature::one_of(
70                vec![
71                    TypeSignature::Coercible(vec![decimal_sig]),
72                    TypeSignature::Uniform(1, vec![DataType::Float64, DataType::Float32]),
73                ],
74                Volatility::Immutable,
75            ),
76        }
77    }
78}
79
80// ============ Macro for preimage bounds ============
81/// Generates the code to call the appropriate bounds function and wrap results.
82macro_rules! preimage_bounds {
83    // Float types: call float_preimage_bounds and wrap in ScalarValue
84    (float: $variant:ident, $value:expr) => {
85        float_preimage_bounds($value).map(|(lo, hi)| {
86            (
87                ScalarValue::$variant(Some(lo)),
88                ScalarValue::$variant(Some(hi)),
89            )
90        })
91    };
92
93    // Integer types: call int_preimage_bounds and wrap in ScalarValue
94    (int: $variant:ident, $value:expr) => {
95        int_preimage_bounds($value).map(|(lo, hi)| {
96            (
97                ScalarValue::$variant(Some(lo)),
98                ScalarValue::$variant(Some(hi)),
99            )
100        })
101    };
102
103    // Decimal types: call decimal_preimage_bounds with precision/scale and wrap in ScalarValue
104    (decimal: $variant:ident, $decimal_type:ty, $value:expr, $precision:expr, $scale:expr) => {
105        decimal_preimage_bounds::<$decimal_type>($value, $precision, $scale).map(
106            |(lo, hi)| {
107                (
108                    ScalarValue::$variant(Some(lo), $precision, $scale),
109                    ScalarValue::$variant(Some(hi), $precision, $scale),
110                )
111            },
112        )
113    };
114}
115
116impl ScalarUDFImpl for FloorFunc {
117    fn name(&self) -> &str {
118        "floor"
119    }
120
121    fn signature(&self) -> &Signature {
122        &self.signature
123    }
124
125    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
126        match &arg_types[0] {
127            DataType::Null => Ok(DataType::Float64),
128            other => Ok(other.clone()),
129        }
130    }
131
132    fn is_strict(&self) -> bool {
133        true
134    }
135
136    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
137        let arg = &args.args[0];
138
139        // Scalar fast path for float types - avoid array conversion overhead entirely
140        if let ColumnarValue::Scalar(scalar) = arg {
141            match scalar {
142                ScalarValue::Float64(v) => {
143                    return Ok(ColumnarValue::Scalar(ScalarValue::Float64(
144                        v.map(f64::floor),
145                    )));
146                }
147                ScalarValue::Float32(v) => {
148                    return Ok(ColumnarValue::Scalar(ScalarValue::Float32(
149                        v.map(f32::floor),
150                    )));
151                }
152                ScalarValue::Null => {
153                    return Ok(ColumnarValue::Scalar(ScalarValue::Float64(None)));
154                }
155                // For decimals: convert to array of size 1, process, then extract scalar
156                // This ensures we don't expand the array while reusing overflow validation
157                _ => {}
158            }
159        }
160
161        // Track if input was a scalar to convert back at the end
162        let is_scalar = matches!(arg, ColumnarValue::Scalar(_));
163
164        // Array path (also handles decimal scalars converted to size-1 arrays)
165        let value = arg.to_array(args.number_rows)?;
166
167        let result: ArrayRef = match value.data_type() {
168            DataType::Float64 => Arc::new(
169                value
170                    .as_primitive::<Float64Type>()
171                    .unary::<_, Float64Type>(f64::floor),
172            ),
173            DataType::Float32 => Arc::new(
174                value
175                    .as_primitive::<Float32Type>()
176                    .unary::<_, Float32Type>(f32::floor),
177            ),
178            DataType::Null => {
179                return Ok(ColumnarValue::Scalar(ScalarValue::Float64(None)));
180            }
181            DataType::Decimal32(precision, scale) => {
182                apply_decimal_op::<Decimal32Type, _>(
183                    &value,
184                    *precision,
185                    *scale,
186                    self.name(),
187                    floor_decimal_value,
188                )?
189            }
190            DataType::Decimal64(precision, scale) => {
191                apply_decimal_op::<Decimal64Type, _>(
192                    &value,
193                    *precision,
194                    *scale,
195                    self.name(),
196                    floor_decimal_value,
197                )?
198            }
199            DataType::Decimal128(precision, scale) => {
200                apply_decimal_op::<Decimal128Type, _>(
201                    &value,
202                    *precision,
203                    *scale,
204                    self.name(),
205                    floor_decimal_value,
206                )?
207            }
208            DataType::Decimal256(precision, scale) => {
209                apply_decimal_op::<Decimal256Type, _>(
210                    &value,
211                    *precision,
212                    *scale,
213                    self.name(),
214                    floor_decimal_value,
215                )?
216            }
217            other => {
218                return exec_err!(
219                    "Unsupported data type {other:?} for function {}",
220                    self.name()
221                );
222            }
223        };
224
225        // If input was a scalar, convert result back to scalar
226        if is_scalar {
227            ScalarValue::try_from_array(&result, 0).map(ColumnarValue::Scalar)
228        } else {
229            Ok(ColumnarValue::Array(result))
230        }
231    }
232
233    fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
234        Ok(input[0].sort_properties)
235    }
236
237    fn evaluate_bounds(&self, inputs: &[&Interval]) -> Result<Interval> {
238        let data_type = inputs[0].data_type();
239        Interval::make_unbounded(&data_type)
240    }
241
242    /// Compute the preimage for floor function.
243    ///
244    /// For `floor(x) = N`, the preimage is `x >= N AND x < N + 1`
245    /// because floor(x) = N for all x in [N, N+1).
246    ///
247    /// This enables predicate pushdown optimizations, transforming:
248    /// `floor(col) = 100` into `col >= 100 AND col < 101`
249    fn preimage(
250        &self,
251        args: &[Expr],
252        lit_expr: &Expr,
253        _info: &SimplifyContext,
254    ) -> Result<PreimageResult> {
255        // floor takes exactly one argument and we do not expect to reach here with multiple arguments.
256        debug_assert!(args.len() == 1, "floor() takes exactly one argument");
257
258        let arg = args[0].clone();
259
260        // Extract the literal value being compared to
261        let Expr::Literal(lit_value, _) = lit_expr else {
262            return Ok(PreimageResult::None);
263        };
264
265        // Compute lower bound (N) and upper bound (N + 1) using helper functions
266        let Some((lower, upper)) = (match lit_value {
267            // Floating-point types
268            ScalarValue::Float64(Some(n)) => preimage_bounds!(float: Float64, *n),
269            ScalarValue::Float32(Some(n)) => preimage_bounds!(float: Float32, *n),
270
271            // Integer types (not reachable from SQL/SLT: floor() only accepts Float64/Float32/Decimal,
272            // so the RHS literal is always coerced to one of those before preimage runs; kept for
273            // programmatic use and unit tests)
274            ScalarValue::Int8(Some(n)) => preimage_bounds!(int: Int8, *n),
275            ScalarValue::Int16(Some(n)) => preimage_bounds!(int: Int16, *n),
276            ScalarValue::Int32(Some(n)) => preimage_bounds!(int: Int32, *n),
277            ScalarValue::Int64(Some(n)) => preimage_bounds!(int: Int64, *n),
278
279            // Decimal types
280            // DECIMAL(precision, scale) where precision ≤ 38 -> Decimal128(precision, scale)
281            // DECIMAL(precision, scale) where precision > 38 -> Decimal256(precision, scale)
282            // Decimal32 and Decimal64 are unreachable from SQL/SLT.
283            ScalarValue::Decimal32(Some(n), precision, scale) => {
284                preimage_bounds!(decimal: Decimal32, Decimal32Type, *n, *precision, *scale)
285            }
286            ScalarValue::Decimal64(Some(n), precision, scale) => {
287                preimage_bounds!(decimal: Decimal64, Decimal64Type, *n, *precision, *scale)
288            }
289            ScalarValue::Decimal128(Some(n), precision, scale) => {
290                preimage_bounds!(decimal: Decimal128, Decimal128Type, *n, *precision, *scale)
291            }
292            ScalarValue::Decimal256(Some(n), precision, scale) => {
293                preimage_bounds!(decimal: Decimal256, Decimal256Type, *n, *precision, *scale)
294            }
295
296            // Unsupported types
297            _ => None,
298        }) else {
299            return Ok(PreimageResult::None);
300        };
301
302        Ok(PreimageResult::Range {
303            expr: arg,
304            interval: Box::new(Interval::try_new(lower, upper)?),
305        })
306    }
307
308    fn documentation(&self) -> Option<&Documentation> {
309        self.doc()
310    }
311}
312
313// ============ Helper functions for preimage bounds ============
314
315/// Compute preimage bounds for floor function on floating-point types.
316/// For floor(x) = n, the preimage is [n, n+1).
317/// Returns None if:
318/// - The value is non-finite (infinity, NaN)
319/// - The value is not an integer (floor always returns integers, so floor(x) = 1.3 has no solution)
320/// - Adding 1 would lose precision at extreme values
321fn float_preimage_bounds<F: Float>(n: F) -> Option<(F, F)> {
322    let one = F::one();
323    // Check for non-finite values (infinity, NaN)
324    if !n.is_finite() {
325        return None;
326    }
327    // floor always returns an integer, so if n has a fractional part, there's no solution
328    if n.fract() != F::zero() {
329        return None;
330    }
331    // Check for precision loss at extreme values
332    if n + one <= n {
333        return None;
334    }
335    Some((n, n + one))
336}
337
338/// Compute preimage bounds for floor function on integer types.
339/// For floor(x) = n, the preimage is [n, n+1).
340/// Returns None if adding 1 would overflow.
341fn int_preimage_bounds<I: CheckedAdd + One + Copy>(n: I) -> Option<(I, I)> {
342    let upper = n.checked_add(&I::one())?;
343    Some((n, upper))
344}
345
346/// Compute preimage bounds for floor function on decimal types.
347/// For floor(x) = n, the preimage is [n, n+1).
348/// Returns None if:
349/// - The value has a fractional part (floor always returns integers)
350/// - Adding 1 would overflow
351fn decimal_preimage_bounds<D: DecimalType>(
352    value: D::Native,
353    precision: u8,
354    scale: i8,
355) -> Option<(D::Native, D::Native)>
356where
357    D::Native: DecimalCast + ArrowNativeTypeOp + std::ops::Rem<Output = D::Native>,
358{
359    // Use rescale_decimal to compute "1" at target scale (avoids manual pow)
360    // Convert integer 1 (scale=0) to the target scale
361    let one_scaled: D::Native = rescale_decimal::<D, D>(
362        D::Native::ONE, // value = 1
363        1,              // input_precision = 1
364        0,              // input_scale = 0 (integer)
365        precision,      // output_precision
366        scale,          // output_scale
367    )?;
368
369    // floor always returns an integer, so if value has a fractional part, there's no solution
370    // Check: value % one_scaled != 0 means fractional part exists
371    if scale > 0 && value % one_scaled != D::Native::ZERO {
372        return None;
373    }
374
375    // Compute upper bound using checked addition
376    // Before preimage stage, the internal i128/i256(value) is validated based on the precision and scale.
377    // MAX_DECIMAL128_FOR_EACH_PRECISION and MAX_DECIMAL256_FOR_EACH_PRECISION are used to validate the internal i128/i256.
378    // Any invalid i128/i256 will not reach here.
379    // Therefore, the add_checked will always succeed if tested via SQL/SLT path.
380    let upper = value.add_checked(one_scaled).ok()?;
381
382    Some((value, upper))
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use arrow_buffer::i256;
389    use datafusion_expr::col;
390
391    /// Helper to test valid preimage cases that should return a Range
392    fn assert_preimage_range(
393        input: ScalarValue,
394        expected_lower: ScalarValue,
395        expected_upper: ScalarValue,
396    ) {
397        let floor_func = FloorFunc::new();
398        let args = vec![col("x")];
399        let lit_expr = Expr::Literal(input.clone(), None);
400        let info = SimplifyContext::default();
401
402        let result = floor_func.preimage(&args, &lit_expr, &info).unwrap();
403
404        match result {
405            PreimageResult::Range { expr, interval } => {
406                assert_eq!(expr, col("x"));
407                assert_eq!(interval.lower().clone(), expected_lower);
408                assert_eq!(interval.upper().clone(), expected_upper);
409            }
410            PreimageResult::None => {
411                panic!("Expected Range, got None for input {input:?}")
412            }
413        }
414    }
415
416    /// Helper to test cases that should return None
417    fn assert_preimage_none(input: ScalarValue) {
418        let floor_func = FloorFunc::new();
419        let args = vec![col("x")];
420        let lit_expr = Expr::Literal(input.clone(), None);
421        let info = SimplifyContext::default();
422
423        let result = floor_func.preimage(&args, &lit_expr, &info).unwrap();
424        assert!(
425            matches!(result, PreimageResult::None),
426            "Expected None for input {input:?}"
427        );
428    }
429
430    #[test]
431    fn test_floor_preimage_valid_cases() {
432        // Float64
433        assert_preimage_range(
434            ScalarValue::Float64(Some(100.0)),
435            ScalarValue::Float64(Some(100.0)),
436            ScalarValue::Float64(Some(101.0)),
437        );
438        // Float32
439        assert_preimage_range(
440            ScalarValue::Float32(Some(50.0)),
441            ScalarValue::Float32(Some(50.0)),
442            ScalarValue::Float32(Some(51.0)),
443        );
444        // Int64
445        assert_preimage_range(
446            ScalarValue::Int64(Some(42)),
447            ScalarValue::Int64(Some(42)),
448            ScalarValue::Int64(Some(43)),
449        );
450        // Int32
451        assert_preimage_range(
452            ScalarValue::Int32(Some(100)),
453            ScalarValue::Int32(Some(100)),
454            ScalarValue::Int32(Some(101)),
455        );
456        // Negative values
457        assert_preimage_range(
458            ScalarValue::Float64(Some(-5.0)),
459            ScalarValue::Float64(Some(-5.0)),
460            ScalarValue::Float64(Some(-4.0)),
461        );
462        // Zero
463        assert_preimage_range(
464            ScalarValue::Float64(Some(0.0)),
465            ScalarValue::Float64(Some(0.0)),
466            ScalarValue::Float64(Some(1.0)),
467        );
468    }
469
470    #[test]
471    fn test_floor_preimage_non_integer_float() {
472        // floor(x) = 1.3 has NO SOLUTION because floor always returns an integer
473        // Therefore preimage should return None for non-integer literals
474        assert_preimage_none(ScalarValue::Float64(Some(1.3)));
475        assert_preimage_none(ScalarValue::Float64(Some(-2.5)));
476        assert_preimage_none(ScalarValue::Float32(Some(3.7)));
477    }
478
479    #[test]
480    fn test_floor_preimage_integer_overflow() {
481        // All integer types at MAX value should return None
482        assert_preimage_none(ScalarValue::Int64(Some(i64::MAX)));
483        assert_preimage_none(ScalarValue::Int32(Some(i32::MAX)));
484        assert_preimage_none(ScalarValue::Int16(Some(i16::MAX)));
485        assert_preimage_none(ScalarValue::Int8(Some(i8::MAX)));
486    }
487
488    #[test]
489    fn test_floor_preimage_float_edge_cases() {
490        // Float64 edge cases
491        assert_preimage_none(ScalarValue::Float64(Some(f64::INFINITY)));
492        assert_preimage_none(ScalarValue::Float64(Some(f64::NEG_INFINITY)));
493        assert_preimage_none(ScalarValue::Float64(Some(f64::NAN)));
494        assert_preimage_none(ScalarValue::Float64(Some(f64::MAX))); // precision loss
495
496        // Float32 edge cases
497        assert_preimage_none(ScalarValue::Float32(Some(f32::INFINITY)));
498        assert_preimage_none(ScalarValue::Float32(Some(f32::NEG_INFINITY)));
499        assert_preimage_none(ScalarValue::Float32(Some(f32::NAN)));
500        assert_preimage_none(ScalarValue::Float32(Some(f32::MAX))); // precision loss
501    }
502
503    #[test]
504    fn test_floor_preimage_null_values() {
505        assert_preimage_none(ScalarValue::Float64(None));
506        assert_preimage_none(ScalarValue::Float32(None));
507        assert_preimage_none(ScalarValue::Int64(None));
508    }
509
510    // ============ Decimal32 Tests (mirrors float/int tests) ============
511
512    #[test]
513    fn test_floor_preimage_decimal_valid_cases() {
514        // ===== Decimal32 =====
515        // Positive integer decimal: 100.00 (scale=2, so raw=10000)
516        // floor(x) = 100.00 -> x in [100.00, 101.00)
517        assert_preimage_range(
518            ScalarValue::Decimal32(Some(10000), 9, 2),
519            ScalarValue::Decimal32(Some(10000), 9, 2), // 100.00
520            ScalarValue::Decimal32(Some(10100), 9, 2), // 101.00
521        );
522
523        // Smaller positive: 50.00
524        assert_preimage_range(
525            ScalarValue::Decimal32(Some(5000), 9, 2),
526            ScalarValue::Decimal32(Some(5000), 9, 2), // 50.00
527            ScalarValue::Decimal32(Some(5100), 9, 2), // 51.00
528        );
529
530        // Negative integer decimal: -5.00
531        assert_preimage_range(
532            ScalarValue::Decimal32(Some(-500), 9, 2),
533            ScalarValue::Decimal32(Some(-500), 9, 2), // -5.00
534            ScalarValue::Decimal32(Some(-400), 9, 2), // -4.00
535        );
536
537        // Zero: 0.00
538        assert_preimage_range(
539            ScalarValue::Decimal32(Some(0), 9, 2),
540            ScalarValue::Decimal32(Some(0), 9, 2), // 0.00
541            ScalarValue::Decimal32(Some(100), 9, 2), // 1.00
542        );
543
544        // Scale 0 (pure integer): 42
545        assert_preimage_range(
546            ScalarValue::Decimal32(Some(42), 9, 0),
547            ScalarValue::Decimal32(Some(42), 9, 0),
548            ScalarValue::Decimal32(Some(43), 9, 0),
549        );
550
551        // ===== Decimal64 =====
552        assert_preimage_range(
553            ScalarValue::Decimal64(Some(10000), 18, 2),
554            ScalarValue::Decimal64(Some(10000), 18, 2), // 100.00
555            ScalarValue::Decimal64(Some(10100), 18, 2), // 101.00
556        );
557
558        // Negative
559        assert_preimage_range(
560            ScalarValue::Decimal64(Some(-500), 18, 2),
561            ScalarValue::Decimal64(Some(-500), 18, 2), // -5.00
562            ScalarValue::Decimal64(Some(-400), 18, 2), // -4.00
563        );
564
565        // Zero
566        assert_preimage_range(
567            ScalarValue::Decimal64(Some(0), 18, 2),
568            ScalarValue::Decimal64(Some(0), 18, 2),
569            ScalarValue::Decimal64(Some(100), 18, 2),
570        );
571
572        // ===== Decimal128 =====
573        assert_preimage_range(
574            ScalarValue::Decimal128(Some(10000), 38, 2),
575            ScalarValue::Decimal128(Some(10000), 38, 2), // 100.00
576            ScalarValue::Decimal128(Some(10100), 38, 2), // 101.00
577        );
578
579        // Negative
580        assert_preimage_range(
581            ScalarValue::Decimal128(Some(-500), 38, 2),
582            ScalarValue::Decimal128(Some(-500), 38, 2), // -5.00
583            ScalarValue::Decimal128(Some(-400), 38, 2), // -4.00
584        );
585
586        // Zero
587        assert_preimage_range(
588            ScalarValue::Decimal128(Some(0), 38, 2),
589            ScalarValue::Decimal128(Some(0), 38, 2),
590            ScalarValue::Decimal128(Some(100), 38, 2),
591        );
592
593        // ===== Decimal256 =====
594        assert_preimage_range(
595            ScalarValue::Decimal256(Some(i256::from(10000)), 76, 2),
596            ScalarValue::Decimal256(Some(i256::from(10000)), 76, 2), // 100.00
597            ScalarValue::Decimal256(Some(i256::from(10100)), 76, 2), // 101.00
598        );
599
600        // Negative
601        assert_preimage_range(
602            ScalarValue::Decimal256(Some(i256::from(-500)), 76, 2),
603            ScalarValue::Decimal256(Some(i256::from(-500)), 76, 2), // -5.00
604            ScalarValue::Decimal256(Some(i256::from(-400)), 76, 2), // -4.00
605        );
606
607        // Zero
608        assert_preimage_range(
609            ScalarValue::Decimal256(Some(i256::ZERO), 76, 2),
610            ScalarValue::Decimal256(Some(i256::ZERO), 76, 2),
611            ScalarValue::Decimal256(Some(i256::from(100)), 76, 2),
612        );
613    }
614
615    #[test]
616    fn test_floor_preimage_decimal_non_integer() {
617        // floor(x) = 1.30 has NO SOLUTION because floor always returns an integer
618        // Therefore preimage should return None for non-integer decimals
619
620        // Decimal32
621        assert_preimage_none(ScalarValue::Decimal32(Some(130), 9, 2)); // 1.30
622        assert_preimage_none(ScalarValue::Decimal32(Some(-250), 9, 2)); // -2.50
623        assert_preimage_none(ScalarValue::Decimal32(Some(370), 9, 2)); // 3.70
624        assert_preimage_none(ScalarValue::Decimal32(Some(1), 9, 2)); // 0.01
625
626        // Decimal64
627        assert_preimage_none(ScalarValue::Decimal64(Some(130), 18, 2)); // 1.30
628        assert_preimage_none(ScalarValue::Decimal64(Some(-250), 18, 2)); // -2.50
629
630        // Decimal128
631        assert_preimage_none(ScalarValue::Decimal128(Some(130), 38, 2)); // 1.30
632        assert_preimage_none(ScalarValue::Decimal128(Some(-250), 38, 2)); // -2.50
633
634        // Decimal256
635        assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(130)), 76, 2)); // 1.30
636        assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(-250)), 76, 2)); // -2.50
637
638        // Decimal32: i32::MAX - 50
639        // This return None because the value is not an integer, not because it is out of range.
640        assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX - 50), 10, 2));
641
642        // Decimal64: i64::MAX - 50
643        // This return None because the value is not an integer, not because it is out of range.
644        assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX - 50), 19, 2));
645    }
646
647    #[test]
648    fn test_floor_preimage_decimal_overflow() {
649        // Test near MAX where adding scale_factor would overflow
650
651        // Decimal32: i32::MAX
652        assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX), 10, 0));
653
654        // Decimal64: i64::MAX
655        assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX), 19, 0));
656    }
657
658    #[test]
659    fn test_floor_preimage_decimal_edge_cases() {
660        // ===== Decimal32 =====
661        // Large value that doesn't overflow
662        // Decimal(9,2) max value is 9,999,999.99 (stored as 999,999,999)
663        // Use a large value that fits Decimal(9,2) and is divisible by 100
664        let safe_max_aligned_32 = 999_999_900; // 9,999,999.00
665        assert_preimage_range(
666            ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2),
667            ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2),
668            ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 9, 2),
669        );
670
671        // Negative edge: use a large negative value that fits Decimal(9,2)
672        // Decimal(9,2) min value is -9,999,999.99 (stored as -999,999,999)
673        let min_aligned_32 = -999_999_900; // -9,999,999.00
674        assert_preimage_range(
675            ScalarValue::Decimal32(Some(min_aligned_32), 9, 2),
676            ScalarValue::Decimal32(Some(min_aligned_32), 9, 2),
677            ScalarValue::Decimal32(Some(min_aligned_32 + 100), 9, 2),
678        );
679    }
680
681    #[test]
682    fn test_floor_preimage_decimal_null() {
683        assert_preimage_none(ScalarValue::Decimal32(None, 9, 2));
684        assert_preimage_none(ScalarValue::Decimal64(None, 18, 2));
685        assert_preimage_none(ScalarValue::Decimal128(None, 38, 2));
686        assert_preimage_none(ScalarValue::Decimal256(None, 76, 2));
687    }
688}