Skip to main content

datafusion_functions_aggregate/
percentile_cont.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::collections::HashMap;
19use std::fmt::Debug;
20use std::mem::{size_of, size_of_val};
21use std::sync::Arc;
22
23use arrow::array::{
24    ArrowNumericType, BooleanArray, ListArray, PrimitiveArray, PrimitiveBuilder,
25};
26use arrow::buffer::{OffsetBuffer, ScalarBuffer};
27use arrow::{
28    array::{Array, ArrayRef, AsArray},
29    datatypes::{DataType, Field, FieldRef, Float16Type, Float32Type, Float64Type},
30};
31
32use num_traits::AsPrimitive;
33
34use arrow::array::ArrowNativeTypeOp;
35use datafusion_common::internal_err;
36use datafusion_common::types::{NativeType, logical_float64};
37use datafusion_functions_aggregate_common::noop_accumulator::NoopAccumulator;
38
39use crate::min_max::{max_udaf, min_udaf};
40use datafusion_common::{
41    Result, ScalarValue, internal_datafusion_err, utils::take_function_args,
42};
43use datafusion_expr::utils::format_state_name;
44use datafusion_expr::{
45    Accumulator, AggregateUDFImpl, Coercion, Documentation, Expr, Signature,
46    TypeSignatureClass, Volatility,
47};
48use datafusion_expr::{EmitTo, GroupsAccumulator};
49use datafusion_expr::{
50    expr::{AggregateFunction, Sort},
51    function::{AccumulatorArgs, AggregateFunctionSimplification, StateFieldsArgs},
52    simplify::SimplifyContext,
53};
54use datafusion_functions_aggregate_common::aggregate::groups_accumulator::accumulate::accumulate;
55use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::filtered_null_mask;
56use datafusion_functions_aggregate_common::utils::{GenericDistinctBuffer, Hashable};
57use datafusion_macros::user_doc;
58
59use crate::utils::validate_percentile_expr;
60
61/// Precision multiplier for linear interpolation calculations.
62///
63/// This value of 1,000,000 was chosen to balance precision with overflow safety:
64/// - Provides 6 decimal places of precision for the fractional component
65/// - Small enough to avoid overflow when multiplied with typical numeric values
66/// - Sufficient precision for most statistical applications
67///
68/// The interpolation formula: `lower + (upper - lower) * fraction`
69/// is computed as: `lower + ((upper - lower) * (fraction * PRECISION)) / PRECISION`
70/// to avoid floating-point operations on integer types while maintaining precision.
71///
72/// The interpolation arithmetic is performed in f64 and then cast back to the
73/// native type to avoid overflowing Float16 intermediates.
74const INTERPOLATION_PRECISION: f64 = 1_000_000.0;
75
76create_func!(PercentileCont, percentile_cont_udaf);
77
78/// Computes the exact percentile continuous of a set of numbers
79pub fn percentile_cont(order_by: Sort, percentile: Expr) -> Expr {
80    let expr = order_by.expr.clone();
81    let args = vec![expr, percentile];
82
83    Expr::AggregateFunction(AggregateFunction::new_udf(
84        percentile_cont_udaf(),
85        args,
86        false,
87        None,
88        vec![order_by],
89        None,
90    ))
91}
92
93#[user_doc(
94    doc_section(label = "General Functions"),
95    description = "Returns the exact percentile of input values, interpolating between values if needed.",
96    syntax_example = "percentile_cont(percentile) WITHIN GROUP (ORDER BY expression)",
97    sql_example = r#"```sql
98> SELECT percentile_cont(0.75) WITHIN GROUP (ORDER BY column_name) FROM table_name;
99+----------------------------------------------------------+
100| percentile_cont(0.75) WITHIN GROUP (ORDER BY column_name) |
101+----------------------------------------------------------+
102| 45.5                                                     |
103+----------------------------------------------------------+
104```
105
106An alternate syntax is also supported:
107```sql
108> SELECT percentile_cont(column_name, 0.75) FROM table_name;
109+---------------------------------------+
110| percentile_cont(column_name, 0.75)    |
111+---------------------------------------+
112| 45.5                                  |
113+---------------------------------------+
114```"#,
115    standard_argument(name = "expression", prefix = "The"),
116    argument(
117        name = "percentile",
118        description = "Percentile to compute. Must be a float value between 0 and 1 (inclusive)."
119    )
120)]
121/// PERCENTILE_CONT aggregate expression. This uses an exact calculation and stores all values
122/// in memory before computing the result. If an approximation is sufficient then
123/// APPROX_PERCENTILE_CONT provides a much more efficient solution.
124///
125/// If using the distinct variation, the memory usage will be similarly high if the
126/// cardinality is high as it stores all distinct values in memory before computing the
127/// result, but if cardinality is low then memory usage will also be lower.
128#[derive(PartialEq, Eq, Hash, Debug)]
129pub struct PercentileCont {
130    signature: Signature,
131    aliases: Vec<String>,
132}
133
134impl Default for PercentileCont {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140impl PercentileCont {
141    pub fn new() -> Self {
142        Self {
143            signature: Signature::coercible(
144                vec![
145                    Coercion::new_implicit(
146                        TypeSignatureClass::Float,
147                        vec![TypeSignatureClass::Numeric],
148                        NativeType::Float64,
149                    ),
150                    Coercion::new_implicit(
151                        TypeSignatureClass::Native(logical_float64()),
152                        vec![TypeSignatureClass::Numeric],
153                        NativeType::Float64,
154                    ),
155                ],
156                Volatility::Immutable,
157            )
158            .with_parameter_names(vec!["expr", "percentile"])
159            .unwrap(),
160            aliases: vec![String::from("quantile_cont")],
161        }
162    }
163}
164
165impl AggregateUDFImpl for PercentileCont {
166    fn name(&self) -> &str {
167        "percentile_cont"
168    }
169
170    fn aliases(&self) -> &[String] {
171        &self.aliases
172    }
173
174    fn signature(&self) -> &Signature {
175        &self.signature
176    }
177
178    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
179        match &arg_types[0] {
180            DataType::Null => Ok(DataType::Float64),
181            dt => Ok(dt.clone()),
182        }
183    }
184
185    fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
186        let input_type = args.input_fields[0].data_type().clone();
187        if input_type.is_null() {
188            return Ok(vec![
189                Field::new(
190                    format_state_name(args.name, self.name()),
191                    DataType::Null,
192                    true,
193                )
194                .into(),
195            ]);
196        }
197
198        let field = Field::new_list_field(input_type, true);
199        let state_name = if args.is_distinct {
200            "distinct_percentile_cont"
201        } else {
202            "percentile_cont"
203        };
204
205        Ok(vec![
206            Field::new(
207                format_state_name(args.name, state_name),
208                DataType::List(Arc::new(field)),
209                true,
210            )
211            .into(),
212        ])
213    }
214
215    fn accumulator(&self, args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
216        let percentile = get_percentile(&args)?;
217
218        let input_dt = args.expr_fields[0].data_type();
219        if input_dt.is_null() {
220            return Ok(Box::new(NoopAccumulator::new(ScalarValue::Float64(None))));
221        }
222
223        if args.is_distinct {
224            match input_dt {
225                DataType::Float16 => Ok(Box::new(DistinctPercentileContAccumulator::<
226                    Float16Type,
227                >::new(percentile))),
228                DataType::Float32 => Ok(Box::new(DistinctPercentileContAccumulator::<
229                    Float32Type,
230                >::new(percentile))),
231                DataType::Float64 => Ok(Box::new(DistinctPercentileContAccumulator::<
232                    Float64Type,
233                >::new(percentile))),
234                dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
235            }
236        } else {
237            match input_dt {
238                DataType::Float16 => Ok(Box::new(
239                    PercentileContAccumulator::<Float16Type>::new(percentile),
240                )),
241                DataType::Float32 => Ok(Box::new(
242                    PercentileContAccumulator::<Float32Type>::new(percentile),
243                )),
244                DataType::Float64 => Ok(Box::new(
245                    PercentileContAccumulator::<Float64Type>::new(percentile),
246                )),
247                dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
248            }
249        }
250    }
251
252    fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
253        !args.is_distinct && !args.expr_fields[0].data_type().is_null()
254    }
255
256    fn create_groups_accumulator(
257        &self,
258        args: AccumulatorArgs,
259    ) -> Result<Box<dyn GroupsAccumulator>> {
260        let percentile = get_percentile(&args)?;
261
262        let input_dt = args.expr_fields[0].data_type();
263        match input_dt {
264            DataType::Float16 => Ok(Box::new(PercentileContGroupsAccumulator::<
265                Float16Type,
266            >::new(percentile))),
267            DataType::Float32 => Ok(Box::new(PercentileContGroupsAccumulator::<
268                Float32Type,
269            >::new(percentile))),
270            DataType::Float64 => Ok(Box::new(PercentileContGroupsAccumulator::<
271                Float64Type,
272            >::new(percentile))),
273            dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
274        }
275    }
276
277    fn simplify(&self) -> Option<AggregateFunctionSimplification> {
278        Some(Box::new(|aggregate_function, info| {
279            simplify_percentile_cont_aggregate(aggregate_function, info)
280        }))
281    }
282
283    fn supports_within_group_clause(&self) -> bool {
284        true
285    }
286
287    fn documentation(&self) -> Option<&Documentation> {
288        self.doc()
289    }
290}
291
292fn get_percentile(args: &AccumulatorArgs) -> Result<f64> {
293    let percentile = validate_percentile_expr(&args.exprs[1], "PERCENTILE_CONT")?;
294
295    let is_descending = args
296        .order_bys
297        .first()
298        .map(|sort_expr| sort_expr.options.descending)
299        .unwrap_or(false);
300
301    let percentile = if is_descending {
302        1.0 - percentile
303    } else {
304        percentile
305    };
306
307    Ok(percentile)
308}
309
310fn simplify_percentile_cont_aggregate(
311    aggregate_function: AggregateFunction,
312    info: &SimplifyContext,
313) -> Result<Expr> {
314    enum PercentileRewriteTarget {
315        Min,
316        Max,
317    }
318
319    let params = &aggregate_function.params;
320    let [value, percentile] = take_function_args("percentile_cont", &params.args)?;
321    //
322    // For simplicity we don't bother with null types (otherwise we'd need to
323    // cast the return type)
324    let input_type = info.get_data_type(value)?;
325    if input_type.is_null() {
326        return Ok(Expr::AggregateFunction(aggregate_function));
327    }
328
329    let is_descending = params
330        .order_by
331        .first()
332        .map(|sort| !sort.asc)
333        .unwrap_or(false);
334
335    let rewrite_target = match percentile {
336        Expr::Literal(ScalarValue::Float64(Some(0.0)), _) => {
337            if is_descending {
338                PercentileRewriteTarget::Max
339            } else {
340                PercentileRewriteTarget::Min
341            }
342        }
343        Expr::Literal(ScalarValue::Float64(Some(1.0)), _) => {
344            if is_descending {
345                PercentileRewriteTarget::Min
346            } else {
347                PercentileRewriteTarget::Max
348            }
349        }
350        _ => return Ok(Expr::AggregateFunction(aggregate_function)),
351    };
352
353    let udaf = match rewrite_target {
354        PercentileRewriteTarget::Min => min_udaf(),
355        PercentileRewriteTarget::Max => max_udaf(),
356    };
357
358    let rewritten = Expr::AggregateFunction(AggregateFunction::new_udf(
359        udaf,
360        vec![value.clone()],
361        params.distinct,
362        params.filter.clone(),
363        vec![],
364        params.null_treatment,
365    ));
366    Ok(rewritten)
367}
368
369/// The percentile_cont accumulator accumulates the raw input values
370/// as native types.
371///
372/// The intermediate state is represented as a List of scalar values updated by
373/// `merge_batch` and a `Vec` of native values that are converted to scalar values
374/// in the final evaluation step so that we avoid expensive conversions and
375/// allocations during `update_batch`.
376#[derive(Debug)]
377struct PercentileContAccumulator<T: ArrowNumericType + Debug> {
378    all_values: Vec<T::Native>,
379    percentile: f64,
380}
381
382impl<T: ArrowNumericType + Debug> PercentileContAccumulator<T> {
383    fn new(percentile: f64) -> Self {
384        Self {
385            all_values: vec![],
386            percentile,
387        }
388    }
389}
390
391impl<T> Accumulator for PercentileContAccumulator<T>
392where
393    T: ArrowNumericType + Debug,
394    T::Native: Copy + AsPrimitive<f64>,
395    f64: AsPrimitive<T::Native>,
396{
397    fn state(&mut self) -> Result<Vec<ScalarValue>> {
398        // Convert `all_values` to `ListArray` and return a single List ScalarValue
399
400        // Build offsets
401        let offsets =
402            OffsetBuffer::new(ScalarBuffer::from(vec![0, self.all_values.len() as i32]));
403
404        // Build inner array
405        let values_array = PrimitiveArray::<T>::new(
406            ScalarBuffer::from(std::mem::take(&mut self.all_values)),
407            None,
408        );
409
410        // Build the result list array
411        let list_array = ListArray::new(
412            Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
413            offsets,
414            Arc::new(values_array),
415            None,
416        );
417
418        Ok(vec![ScalarValue::List(Arc::new(list_array))])
419    }
420
421    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
422        let values = values[0].as_primitive::<T>();
423        self.all_values.reserve(values.len() - values.null_count());
424        self.all_values.extend(values.iter().flatten());
425        Ok(())
426    }
427
428    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
429        let array = states[0].as_list::<i32>();
430        self.update_batch(&[array.value(0)])?;
431        Ok(())
432    }
433
434    fn evaluate(&mut self) -> Result<ScalarValue> {
435        let value = calculate_percentile::<T>(&mut self.all_values, self.percentile);
436        ScalarValue::new_primitive::<T>(value, &T::DATA_TYPE)
437    }
438
439    fn size(&self) -> usize {
440        size_of_val(self) + self.all_values.capacity() * size_of::<T::Native>()
441    }
442
443    fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
444        let mut to_remove: HashMap<Hashable<T::Native>, usize> = HashMap::new();
445
446        let arr = values[0].as_primitive::<T>();
447        for value in arr.iter().flatten() {
448            *to_remove.entry(Hashable(value)).or_default() += 1;
449        }
450
451        let mut i = 0;
452        while i < self.all_values.len() {
453            let k = Hashable(self.all_values[i]);
454            if let Some(count) = to_remove.get_mut(&k)
455                && *count > 0
456            {
457                self.all_values.swap_remove(i);
458                *count -= 1;
459                if *count == 0 {
460                    to_remove.remove(&k);
461                    if to_remove.is_empty() {
462                        break;
463                    }
464                }
465            } else {
466                i += 1;
467            }
468        }
469        Ok(())
470    }
471
472    fn supports_retract_batch(&self) -> bool {
473        true
474    }
475}
476
477/// The percentile_cont groups accumulator accumulates the raw input values
478///
479/// For calculating the exact percentile of groups, we need to store all values
480/// of groups before final evaluation.
481/// So values in each group will be stored in a `Vec<T>`, and the total group values
482/// will be actually organized as a `Vec<Vec<T>>`.
483#[derive(Debug)]
484struct PercentileContGroupsAccumulator<T: ArrowNumericType + Send> {
485    group_values: Vec<Vec<T::Native>>,
486    percentile: f64,
487}
488
489impl<T: ArrowNumericType + Send> PercentileContGroupsAccumulator<T> {
490    fn new(percentile: f64) -> Self {
491        Self {
492            group_values: vec![],
493            percentile,
494        }
495    }
496}
497
498impl<T> GroupsAccumulator for PercentileContGroupsAccumulator<T>
499where
500    T: ArrowNumericType + Send,
501    T::Native: Copy + AsPrimitive<f64>,
502    f64: AsPrimitive<T::Native>,
503{
504    fn update_batch(
505        &mut self,
506        values: &[ArrayRef],
507        group_indices: &[usize],
508        opt_filter: Option<&BooleanArray>,
509        total_num_groups: usize,
510    ) -> Result<()> {
511        // For ordered-set aggregates, we only care about the ORDER BY column (first element)
512        // The percentile parameter is already stored in self.percentile
513
514        let values = values[0].as_primitive::<T>();
515
516        // Push the `not nulls + not filtered` row into its group
517        self.group_values.resize(total_num_groups, Vec::new());
518        accumulate(
519            group_indices,
520            values,
521            opt_filter,
522            |group_index, new_value| {
523                self.group_values[group_index].push(new_value);
524            },
525        );
526
527        Ok(())
528    }
529
530    fn merge_batch(
531        &mut self,
532        values: &[ArrayRef],
533        group_indices: &[usize],
534        // Since aggregate filter should be applied in partial stage, in final stage there should be no filter
535        _opt_filter: Option<&BooleanArray>,
536        total_num_groups: usize,
537    ) -> Result<()> {
538        assert_eq!(values.len(), 1, "one argument to merge_batch");
539
540        let input_group_values = values[0].as_list::<i32>();
541
542        // Ensure group values big enough
543        self.group_values.resize(total_num_groups, Vec::new());
544
545        // Extend values to related groups
546        group_indices
547            .iter()
548            .zip(input_group_values.iter())
549            .for_each(|(&group_index, values_opt)| {
550                if let Some(values) = values_opt {
551                    let values = values.as_primitive::<T>();
552                    self.group_values[group_index].extend(values.values().iter());
553                }
554            });
555
556        Ok(())
557    }
558
559    fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
560        // Emit values
561        let emit_group_values = emit_to.take_needed(&mut self.group_values);
562
563        // Build offsets
564        let mut offsets = Vec::with_capacity(self.group_values.len() + 1);
565        offsets.push(0);
566        let mut cur_len = 0_i32;
567        for group_value in &emit_group_values {
568            cur_len += group_value.len() as i32;
569            offsets.push(cur_len);
570        }
571        let offsets = OffsetBuffer::new(ScalarBuffer::from(offsets));
572
573        // Build inner array
574        let flatten_group_values =
575            emit_group_values.into_iter().flatten().collect::<Vec<_>>();
576        let group_values_array =
577            PrimitiveArray::<T>::new(ScalarBuffer::from(flatten_group_values), None);
578
579        // Build the result list array
580        let result_list_array = ListArray::new(
581            Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
582            offsets,
583            Arc::new(group_values_array),
584            None,
585        );
586
587        Ok(vec![Arc::new(result_list_array)])
588    }
589
590    fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
591        // Emit values
592        let mut emit_group_values = emit_to.take_needed(&mut self.group_values);
593
594        // Calculate percentile for each group
595        let mut evaluate_result_builder =
596            PrimitiveBuilder::<T>::with_capacity(emit_group_values.len());
597        for values in &mut emit_group_values {
598            let value = calculate_percentile::<T>(values.as_mut_slice(), self.percentile);
599            evaluate_result_builder.append_option(value);
600        }
601
602        Ok(Arc::new(evaluate_result_builder.finish()))
603    }
604
605    fn convert_to_state(
606        &self,
607        values: &[ArrayRef],
608        opt_filter: Option<&BooleanArray>,
609    ) -> Result<Vec<ArrayRef>> {
610        assert_eq!(values.len(), 1, "one argument to merge_batch");
611
612        let input_array = values[0].as_primitive::<T>();
613
614        // Directly convert the input array to states, each row will be
615        // seen as a respective group.
616        // For detail, the `input_array` will be converted to a `ListArray`.
617        // And if row is `not null + not filtered`, it will be converted to a list
618        // with only one element; otherwise, this row in `ListArray` will be set
619        // to null.
620
621        // Reuse values buffer in `input_array` to build `values` in `ListArray`
622        let values = PrimitiveArray::<T>::new(input_array.values().clone(), None);
623
624        // `offsets` in `ListArray`, each row as a list element
625        let offset_end = i32::try_from(input_array.len()).map_err(|e| {
626            internal_datafusion_err!(
627                "cast array_len to i32 failed in convert_to_state of group percentile_cont, err:{e:?}"
628            )
629        })?;
630        let offsets = (0..=offset_end).collect::<Vec<_>>();
631        // Safety: The offsets vector is constructed as a sequential range from 0 to input_array.len(),
632        // which guarantees all OffsetBuffer invariants:
633        // 1. Offsets are monotonically increasing (each element is prev + 1)
634        // 2. No offset exceeds the values array length (max offset = input_array.len())
635        // 3. First offset is 0 and last offset equals the total length
636        // Therefore new_unchecked is safe to use here.
637        let offsets = unsafe { OffsetBuffer::new_unchecked(ScalarBuffer::from(offsets)) };
638
639        // `nulls` for converted `ListArray`
640        let nulls = filtered_null_mask(opt_filter, input_array);
641
642        let converted_list_array = ListArray::new(
643            Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
644            offsets,
645            Arc::new(values),
646            nulls,
647        );
648
649        Ok(vec![Arc::new(converted_list_array)])
650    }
651
652    fn supports_convert_to_state(&self) -> bool {
653        true
654    }
655
656    fn size(&self) -> usize {
657        self.group_values
658            .iter()
659            .map(|values| values.capacity() * size_of::<T::Native>())
660            .sum::<usize>()
661            // account for size of self.group_values too
662            + self.group_values.capacity() * size_of::<Vec<T::Native>>()
663    }
664}
665
666#[derive(Debug)]
667struct DistinctPercentileContAccumulator<T: ArrowNumericType> {
668    distinct_values: GenericDistinctBuffer<T>,
669    percentile: f64,
670}
671
672impl<T: ArrowNumericType + Debug> DistinctPercentileContAccumulator<T> {
673    fn new(percentile: f64) -> Self {
674        Self {
675            distinct_values: GenericDistinctBuffer::new(T::DATA_TYPE),
676            percentile,
677        }
678    }
679}
680
681impl<T> Accumulator for DistinctPercentileContAccumulator<T>
682where
683    T: ArrowNumericType + Debug,
684    T::Native: Copy + AsPrimitive<f64>,
685    f64: AsPrimitive<T::Native>,
686{
687    fn state(&mut self) -> Result<Vec<ScalarValue>> {
688        self.distinct_values.state()
689    }
690
691    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
692        self.distinct_values.update_batch(values)
693    }
694
695    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
696        self.distinct_values.merge_batch(states)
697    }
698
699    fn evaluate(&mut self) -> Result<ScalarValue> {
700        let mut values: Vec<T::Native> =
701            self.distinct_values.values.iter().map(|v| v.0).collect();
702        let value = calculate_percentile::<T>(&mut values, self.percentile);
703        ScalarValue::new_primitive::<T>(value, &T::DATA_TYPE)
704    }
705
706    fn size(&self) -> usize {
707        size_of_val(self) + self.distinct_values.size()
708    }
709
710    fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
711        if values.is_empty() {
712            return Ok(());
713        }
714
715        let arr = values[0].as_primitive::<T>();
716        for value in arr.iter().flatten() {
717            self.distinct_values.values.remove(&Hashable(value));
718        }
719        Ok(())
720    }
721
722    fn supports_retract_batch(&self) -> bool {
723        true
724    }
725}
726
727/// Calculate the percentile value for a given set of values.
728/// This function performs an exact calculation by sorting all values.
729///
730/// The percentile is calculated using linear interpolation between closest ranks.
731/// For percentile p and n values:
732/// - If p * (n-1) is an integer, return the value at that position
733/// - Otherwise, interpolate between the two closest values
734///
735/// Note: This function takes a mutable slice and sorts it in place, but does not
736/// consume the data. This is important for window frame queries where evaluate()
737/// may be called multiple times on the same accumulator state.
738fn calculate_percentile<T: ArrowNumericType>(
739    values: &mut [T::Native],
740    percentile: f64,
741) -> Option<T::Native>
742where
743    T::Native: Copy + AsPrimitive<f64>,
744    f64: AsPrimitive<T::Native>,
745{
746    let cmp = |x: &T::Native, y: &T::Native| x.compare(*y);
747
748    let len = values.len();
749    if len == 0 {
750        None
751    } else if len == 1 {
752        Some(values[0])
753    } else if percentile == 0.0 {
754        // Get minimum value
755        Some(
756            *values
757                .iter()
758                .min_by(|a, b| cmp(a, b))
759                .expect("we checked for len > 0 a few lines above"),
760        )
761    } else if percentile == 1.0 {
762        // Get maximum value
763        Some(
764            *values
765                .iter()
766                .max_by(|a, b| cmp(a, b))
767                .expect("we checked for len > 0 a few lines above"),
768        )
769    } else {
770        // Calculate the index using the formula: p * (n - 1)
771        let index = percentile * ((len - 1) as f64);
772        let lower_index = index.floor() as usize;
773        let upper_index = index.ceil() as usize;
774
775        if lower_index == upper_index {
776            // Exact index, return the value at that position
777            let (_, value, _) = values.select_nth_unstable_by(lower_index, cmp);
778            Some(*value)
779        } else {
780            // Need to interpolate between two values
781            // First, partition at lower_index to get the lower value
782            let (_, lower_value, _) = values.select_nth_unstable_by(lower_index, cmp);
783            let lower_value = *lower_value;
784
785            // Then partition at upper_index to get the upper value
786            let (_, upper_value, _) = values.select_nth_unstable_by(upper_index, cmp);
787            let upper_value = *upper_value;
788
789            // Linear interpolation.
790            // We compute a quantized interpolation weight using `INTERPOLATION_PRECISION` because:
791            // 1. Both values come from the input data, so (upper - lower) is bounded by the value range
792            // 2. fraction is between 0 and 1; quantizing it provides stable, predictable results
793            // 3. The result is guaranteed to be between lower_value and upper_value (modulo cast rounding)
794            // 4. Arithmetic is performed in f64 and cast back to avoid overflowing Float16 intermediates
795            let fraction = index - (lower_index as f64);
796            let scaled = (fraction * INTERPOLATION_PRECISION) as usize;
797            let weight = scaled as f64 / INTERPOLATION_PRECISION;
798
799            let lower_f: f64 = lower_value.as_();
800            let upper_f: f64 = upper_value.as_();
801            let interpolated_f = lower_f + (upper_f - lower_f) * weight;
802            Some(interpolated_f.as_())
803        }
804    }
805}
806
807#[cfg(test)]
808mod tests {
809    use super::calculate_percentile;
810    use half::f16;
811
812    #[test]
813    fn f16_interpolation_does_not_overflow_to_nan() {
814        // Regression test for https://github.com/apache/datafusion/issues/18945
815        // Interpolating between 0 and the max finite f16 value previously overflowed
816        // intermediate f16 computations and produced NaN.
817        let mut values = vec![f16::from_f32(0.0), f16::from_f32(65504.0)];
818        let result =
819            calculate_percentile::<arrow::datatypes::Float16Type>(&mut values, 0.5)
820                .expect("non-empty input");
821        let result_f = result.to_f32();
822        assert!(
823            !result_f.is_nan(),
824            "expected non-NaN result, got {result_f}"
825        );
826        // 0.5 percentile should be close to midpoint
827        assert!(
828            (result_f - 32752.0).abs() < 1.0,
829            "unexpected result {result_f}"
830        );
831    }
832}