This is an automated email from the ASF dual-hosted git repository.

mbutrovich pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new f06aa31b3e feat: support approx_count_distinct aggregate expression 
(#4819)
f06aa31b3e is described below

commit f06aa31b3ee00256bf395d4a87566c2714bdce52
Author: Andy Grove <[email protected]>
AuthorDate: Thu Jul 23 06:04:19 2026 -0600

    feat: support approx_count_distinct aggregate expression (#4819)
    
    * feat: support approx_count_distinct aggregate expression
    
    Add native support for Spark's approx_count_distinct, a faithful port of
    Spark's HyperLogLogPlusPlus / HyperLogLogPlusPlusHelper.
    
    Each non-null input is hashed with Comet's Spark-compatible XxHash64
    (seed 42, floats normalized first), and the HyperLogLog++ registers are
    stored in Spark's exact packed-Long buffer layout (10 six-bit registers
    per word). The cardinality is estimated with the same linear-counting and
    bias-correction tables Spark uses, so results are bit-identical to Spark
    and the partial-aggregation state matches Spark's aggBufferSchema.
    
    Includes a vectorized GroupsAccumulator, SQL file tests comparing against
    Spark across a range of cardinalities, native unit tests, benchmark
    coverage, and documentation updates.
    
    * feat: address review feedback on approx_count_distinct
    
    - restrict decimal inputs to precision <= 18 (Spark hashes wider decimals
      through BigDecimal, which the native i128 path does not match)
    - set supportsMixedPartialFinal=true; the register buffer matches Spark's
      aggBufferSchema, enabling mixed Comet/Spark partial and final aggregation
    - add getUnsupportedReasons so the Compatibility Guide reflects the type 
limits
    - derive Hash, add assert/debug_assert invariants and a float-order comment,
      reuse the shared normalize_float, drop a redundant field, make bias
      correction lazy, and reuse a per-accumulator hash scratch buffer
    - compact BIAS_DATA layout with rustfmt::skip (values unchanged)
    - add tests: decimal boundary and wide-decimal fallback, non-UTC timestamp,
      collated-string fallback, and mixed partial/final plan shape
---
 .../expression-audits/agg_funcs.md                 |    7 +
 docs/source/user-guide/latest/expressions.md       |    4 +-
 native/core/src/execution/planner.rs               |    9 +-
 native/proto/src/proto/expr.proto                  |    8 +
 native/spark-expr/src/agg_funcs/hll_plus_plus.rs   |  656 ++++++++
 .../src/agg_funcs/hll_plus_plus_const.rs           | 1666 ++++++++++++++++++++
 native/spark-expr/src/agg_funcs/mod.rs             |    3 +
 native/spark-expr/src/hash_funcs/mod.rs            |    1 +
 native/spark-expr/src/hash_funcs/xxhash64.rs       |    2 +-
 native/spark-expr/src/math_funcs/internal/mod.rs   |    1 +
 .../src/math_funcs/internal/normalize_nan.rs       |    2 +-
 .../org/apache/comet/serde/QueryPlanSerde.scala    |    1 +
 .../scala/org/apache/comet/serde/aggregates.scala  |   67 +-
 .../aggregate/approx_count_distinct.sql            |  158 ++
 .../aggregate/approx_count_distinct_collation.sql  |   37 +
 .../aggregate/approx_count_distinct_timezone.sql   |   40 +
 .../apache/comet/rules/CometExecRuleSuite.scala    |   48 +
 .../CometAggregateExpressionBenchmark.scala        |   18 +-
 18 files changed, 2719 insertions(+), 9 deletions(-)

diff --git a/docs/source/contributor-guide/expression-audits/agg_funcs.md 
b/docs/source/contributor-guide/expression-audits/agg_funcs.md
index 8888776d85..3904cca30c 100644
--- a/docs/source/contributor-guide/expression-audits/agg_funcs.md
+++ b/docs/source/contributor-guide/expression-audits/agg_funcs.md
@@ -21,6 +21,13 @@
 
 > Audit notes for expressions in this category that have been audited. Absence 
 > of an entry means the expression has not been audited yet, not that it is 
 > unsupported. See the user guide [Spark Expression Support] for current 
 > support status.
 
+## approx_count_distinct
+
+- Spark 3.4.3 (2026-07-03): registered as 
`expression[HyperLogLogPlusPlus]("approx_count_distinct")`, an 
`ImperativeAggregate` that hashes each non-null input with `XxHash64` (seed 42, 
floats normalized via `NormalizeNaNAndZero`) and keeps a HyperLogLog++ register 
buffer of `numWords` `Long`s (10 six-bit registers per word). The cardinality 
is estimated with linear counting for small inputs and bias-corrected HLL 
otherwise. Comet ports `HyperLogLogPlusPlusHelper` exactly, including the b 
[...]
+- Spark 3.5.8 (2026-07-03): algorithm and tables identical to 3.4.3.
+- Spark 4.0.1 (2026-07-03): `HyperLogLogPlusPlusHelper` moved to 
`catalyst.util` and `XxHash64Function.hash` gained collation parameters, but 
for the default `UTF8_BINARY` collation and non-string types the hash value is 
unchanged, so results match 3.4.3.
+- Spark 4.1.1 (2026-07-03): identical to 4.0.1.
+
 ## any
 
 - Spark 3.4.3 (audited 2026-05-26): registered as a SQL alias of `BoolOr`, 
which extends `RuntimeReplaceableAggregate` with `replacement = Max(child)`. 
Catalyst rewrites `any(x)` to `max(x)` before Comet sees the plan, so `any` is 
served by `CometMax` on a `BooleanType` column.
diff --git a/docs/source/user-guide/latest/expressions.md 
b/docs/source/user-guide/latest/expressions.md
index ae49f27f87..6323083cbc 100644
--- a/docs/source/user-guide/latest/expressions.md
+++ b/docs/source/user-guide/latest/expressions.md
@@ -60,7 +60,7 @@ expressions. The following function families are **not 
currently planned** for n
 
 The file-metadata functions `input_file_name`, `input_file_block_start`, and 
`input_file_block_length` depend on scan-internal per-row file information 
rather than the expression layer; their support status is covered in the [scan 
compatibility guide](compatibility/scans.md).
 
-Note that `approx_count_distinct`, `median`, and `mode` are planned: they are 
mainstream (`median` and `mode` are exact aggregates).
+Note that `median` and `mode` are planned: they are mainstream exact 
aggregates. `approx_count_distinct` is supported because Comet ports Spark's 
`HyperLogLogPlusPlus` exactly, so its result is bit-identical to Spark.
 
 The tables below list every Spark built-in expression with its current status.
 
@@ -70,7 +70,7 @@ The tables below list every Spark built-in expression with 
its current status.
 | --- | --- | --- |
 | `any` | ✅ |  |
 | `any_value` | ✅ |  |
-| `approx_count_distinct` | 🔜 | tracking 
[#4098](https://github.com/apache/datafusion-comet/issues/4098) |
+| `approx_count_distinct` | ✅ |  |
 | `approx_percentile` | ✅ | Byte, short, int, long, float, and double input; 
other input types fall back to Spark |
 | `array_agg` | 🔜 | Array aggregate (related to `collect_list`, 
[#2524](https://github.com/apache/datafusion-comet/issues/2524)) |
 | `avg` | ✅ | Interval types fall back |
diff --git a/native/core/src/execution/planner.rs 
b/native/core/src/execution/planner.rs
index 75d916768a..7c24bcabba 100644
--- a/native/core/src/execution/planner.rs
+++ b/native/core/src/execution/planner.rs
@@ -132,8 +132,8 @@ use datafusion_comet_proto::{
 use datafusion_comet_spark_expr::{
     jvm_udf::JvmScalarUdfExpr, ApproxPercentile, ArrayInsert, Avg, AvgDecimal, 
Cast, CheckOverflow,
     Correlation, Covariance, CreateNamedStruct, DecimalRescaleCheckOverflow, 
GetArrayStructFields,
-    GetStructField, IfExpr, ListExtract, NormalizeNaNAndZero, 
SparkCastOptions, Stddev, SumDecimal,
-    ToJson, UnboundColumn, Variance, WideDecimalBinaryExpr, WideDecimalOp,
+    GetStructField, HllPlusPlus, IfExpr, ListExtract, NormalizeNaNAndZero, 
SparkCastOptions,
+    Stddev, SumDecimal, ToJson, UnboundColumn, Variance, 
WideDecimalBinaryExpr, WideDecimalOp,
 };
 use itertools::Itertools;
 use jni::objects::{Global, JObject};
@@ -2674,6 +2674,11 @@ impl PhysicalPlanner {
                 let func = AggregateUDF::new_from_impl(SparkCollectSet::new());
                 Self::create_aggr_func_expr("collect_set", schema, 
vec![child], func)
             }
+            AggExprStruct::Hllpp(expr) => {
+                let child = self.create_expr(expr.child.as_ref().unwrap(), 
Arc::clone(&schema))?;
+                let func = 
AggregateUDF::new_from_impl(HllPlusPlus::new(expr.precision));
+                Self::create_aggr_func_expr("approx_count_distinct", schema, 
vec![child], func)
+            }
         }
     }
 
diff --git a/native/proto/src/proto/expr.proto 
b/native/proto/src/proto/expr.proto
index ee2a65b3d7..82f7e026b9 100644
--- a/native/proto/src/proto/expr.proto
+++ b/native/proto/src/proto/expr.proto
@@ -149,6 +149,7 @@ message AggExpr {
     CollectSet collectSet = 17;
     Percentile percentile = 18;
     ApproxPercentile approxPercentile = 19;
+    HllPlusPlus hllpp = 20;
   }
 
   // Optional filter expression for SQL FILTER (WHERE ...) clause.
@@ -296,6 +297,13 @@ message CollectSet {
   DataType datatype = 2;
 }
 
+// approx_count_distinct (Spark's HyperLogLogPlusPlus)
+message HllPlusPlus {
+  Expr child = 1;
+  // Number of addressing bits, computed from relativeSD to match Spark.
+  int32 precision = 2;
+}
+
 enum EvalMode {
   LEGACY = 0;
   TRY = 1;
diff --git a/native/spark-expr/src/agg_funcs/hll_plus_plus.rs 
b/native/spark-expr/src/agg_funcs/hll_plus_plus.rs
new file mode 100644
index 0000000000..01fa13800a
--- /dev/null
+++ b/native/spark-expr/src/agg_funcs/hll_plus_plus.rs
@@ -0,0 +1,656 @@
+// 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.
+
+//! Spark-compatible `approx_count_distinct`, a faithful port of Spark's
+//! `HyperLogLogPlusPlus` / `HyperLogLogPlusPlusHelper`. Values are hashed 
with Spark's
+//! `XxHash64` (seed 42, floats normalized first) and the registers are stored 
using the exact
+//! same packed-`Long` buffer layout Spark uses (10 six-bit registers per 
64-bit word). Keeping
+//! the wire format identical means the partial-aggregation state matches 
Spark's
+//! `aggBufferSchema`, and the cardinality estimate uses the same 
bias-correction tables, so
+//! results are bit-identical to Spark.
+
+use crate::agg_funcs::hll_plus_plus_const::{BIAS_DATA, RAW_ESTIMATE_DATA, 
THRESHOLDS};
+use crate::hash_funcs::create_xxhash64_hashes;
+use crate::math_funcs::internal::normalize_float;
+use arrow::array::{
+    Array, ArrayRef, AsArray, BooleanArray, Float32Array, Float64Array, 
Int64Array,
+};
+use arrow::datatypes::{DataType, Field, FieldRef, Float32Type, Float64Type};
+use datafusion::common::{Result, ScalarValue};
+use datafusion::logical_expr::function::{AccumulatorArgs, StateFieldsArgs};
+use datafusion::logical_expr::{
+    Accumulator, AggregateUDFImpl, EmitTo, GroupsAccumulator, Signature, 
Volatility,
+};
+use std::sync::Arc;
+
+/// Number of interpolation points used when estimating the bias (Spark's `K`).
+const K: usize = 6;
+/// The seed Spark's `HyperLogLogPlusPlus` uses for `XxHash64`.
+const HASH_SEED: u64 = 42;
+/// Bits per register (Spark's `REGISTER_SIZE`). A 64-bit hash yields at most 
64 leading zeros.
+const REGISTER_SIZE: u64 = 6;
+/// Registers packed per 64-bit word (Spark's `REGISTERS_PER_WORD`); only 60 
of 64 bits are used.
+const REGISTERS_PER_WORD: usize = 10;
+/// Mask for a single register within a word (Spark's `REGISTER_WORD_MASK`).
+const REGISTER_WORD_MASK: u64 = (1 << REGISTER_SIZE) - 1;
+
+/// Number of 64-bit words needed to store all `m = 1 << p` registers, 
matching Spark's `numWords`.
+fn num_words(p: usize) -> usize {
+    (1usize << p) / REGISTERS_PER_WORD + 1
+}
+
+/// Compute the precision `p` for a given `relativeSD`, matching Spark's
+/// `HyperLogLogPlusPlusHelper`. Kept here so the native tests can exercise 
it, but the
+/// serde computes the same value and passes `p` through the protobuf.
+pub fn hllpp_precision(relative_sd: f64) -> i32 {
+    (2.0 * (1.106 / relative_sd).ln() / 2.0_f64.ln()).ceil() as i32
+}
+
+/// Spark-compatible `approx_count_distinct` aggregate.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct HllPlusPlus {
+    name: String,
+    signature: Signature,
+    /// Number of addressing bits (precision). Determines the register count 
`m = 1 << p`.
+    p: usize,
+}
+
+impl HllPlusPlus {
+    pub fn new(p: i32) -> Self {
+        assert!(
+            p >= 4,
+            "Spark's HyperLogLogPlusPlusHelper requires p >= 4 (relativeSD <= 
39%)"
+        );
+        Self {
+            name: "approx_count_distinct".to_string(),
+            signature: Signature::any(1, Volatility::Immutable),
+            p: p as usize,
+        }
+    }
+}
+
+impl AggregateUDFImpl for HllPlusPlus {
+    fn name(&self) -> &str {
+        &self.name
+    }
+
+    fn signature(&self) -> &Signature {
+        &self.signature
+    }
+
+    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
+        Ok(DataType::Int64)
+    }
+
+    fn accumulator(&self, _acc_args: AccumulatorArgs) -> Result<Box<dyn 
Accumulator>> {
+        Ok(Box::new(HllPlusPlusAccumulator::new(self.p)))
+    }
+
+    fn state_fields(&self, _args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
+        // The partial state is the packed register buffer: one `Long` per 
word, exactly matching
+        // the `MS[i]` buffer attributes of Spark's `HyperLogLogPlusPlus`.
+        Ok((0..num_words(self.p))
+            .map(|i| Arc::new(Field::new(format!("MS[{i}]"), DataType::Int64, 
false)) as FieldRef)
+            .collect())
+    }
+
+    fn groups_accumulator_supported(&self, _args: AccumulatorArgs) -> bool {
+        true
+    }
+
+    fn create_groups_accumulator(
+        &self,
+        _args: AccumulatorArgs,
+    ) -> Result<Box<dyn GroupsAccumulator>> {
+        Ok(Box::new(HllPlusPlusGroupsAccumulator::new(self.p)))
+    }
+}
+
+/// Normalize a float/double column the way Spark's `NormalizeNaNAndZero` does 
before hashing:
+/// every NaN becomes the canonical NaN and `-0.0` becomes `0.0`. Returns the 
input unchanged for
+/// non-floating-point types.
+fn normalize_floats(array: &ArrayRef) -> ArrayRef {
+    match array.data_type() {
+        DataType::Float32 => {
+            let normalized: Float32Array =
+                array.as_primitive::<Float32Type>().unary(normalize_float);
+            Arc::new(normalized)
+        }
+        DataType::Float64 => {
+            let normalized: Float64Array =
+                array.as_primitive::<Float64Type>().unary(normalize_float);
+            Arc::new(normalized)
+        }
+        _ => Arc::clone(array),
+    }
+}
+
+/// Hash a value column with Spark's `XxHash64` (seed 42), normalizing floats 
first, reusing
+/// `buf` as scratch to avoid a per-batch allocation. The buffer is re-seeded 
(not just cleared)
+/// because `create_xxhash64_hashes` folds each value into the existing seed.
+fn hash_values_into(array: &ArrayRef, buf: &mut Vec<u64>) -> Result<()> {
+    let normalized = normalize_floats(array);
+    buf.clear();
+    buf.resize(normalized.len(), HASH_SEED);
+    create_xxhash64_hashes(&[normalized], buf)?;
+    Ok(())
+}
+
+/// Update the packed register buffer from a hashed value. Mirrors 
`HyperLogLogPlusPlusHelper.update`.
+#[inline]
+fn update_word(words: &mut [i64], p: usize, hash: u64) {
+    let idx = (hash >> (64 - p)) as usize;
+    let w_padding = 1u64 << (p - 1);
+    let pw = (((hash << p) | w_padding).leading_zeros() + 1) as u64;
+
+    let word_offset = idx / REGISTERS_PER_WORD;
+    let shift = REGISTER_SIZE * (idx - word_offset * REGISTERS_PER_WORD) as 
u64;
+    let word = words[word_offset] as u64;
+    let mask = REGISTER_WORD_MASK << shift;
+    let m_idx = (word & mask) >> shift;
+    if pw > m_idx {
+        words[word_offset] = ((word & !mask) | (pw << shift)) as i64;
+    }
+}
+
+/// Merge `src` packed registers into `dst` by taking the per-register 
maximum. Mirrors
+/// `HyperLogLogPlusPlusHelper.merge`.
+#[inline]
+fn merge_words(dst: &mut [i64], src: &[i64], m: usize) {
+    let mut idx = 0;
+    for (d, s) in dst.iter_mut().zip(src.iter()) {
+        let word1 = *d as u64;
+        let word2 = *s as u64;
+        let mut word = 0u64;
+        let mut i = 0;
+        let mut mask = REGISTER_WORD_MASK;
+        while idx < m && i < REGISTERS_PER_WORD {
+            word |= (word1 & mask).max(word2 & mask);
+            mask <<= REGISTER_SIZE;
+            i += 1;
+            idx += 1;
+        }
+        *d = word as i64;
+    }
+}
+
+/// The precomputed `alpha * m * m` constant, matching 
`HyperLogLogPlusPlusHelper.alphaM2`.
+fn alpha_m2(p: usize, m: f64) -> f64 {
+    match p {
+        4 => 0.673 * m * m,
+        5 => 0.697 * m * m,
+        6 => 0.709 * m * m,
+        _ => (0.7213 / (1.0 + 1.079 / m)) * m * m,
+    }
+}
+
+/// Estimate the bias using KNN interpolation over Spark's appendix tables.
+fn estimate_bias(p: usize, e: f64) -> f64 {
+    // The appendix tables are indexed by `p - 4` and only cover 4 <= p <= 18.
+    debug_assert!((4..19).contains(&p));
+    let estimates = RAW_ESTIMATE_DATA[p - 4];
+    let biases = BIAS_DATA[p - 4];
+    let num_estimates = estimates.len();
+
+    // Index of the interpolation estimate closest to `e` (matches Java's 
Arrays.binarySearch).
+    let nearest = match estimates.binary_search_by(|v| 
v.partial_cmp(&e).unwrap()) {
+        Ok(ix) => ix,
+        Err(ix) => ix,
+    };
+
+    let distance = |i: usize| {
+        let diff = e - estimates[i];
+        diff * diff
+    };
+
+    let mut low = nearest.saturating_sub(K - 1);
+    let mut high = (low + K).min(num_estimates);
+    while high < num_estimates && distance(high) < distance(low) {
+        low += 1;
+        high += 1;
+    }
+
+    let bias_sum: f64 = biases[low..high].iter().sum();
+    bias_sum / (high - low) as f64
+}
+
+/// Compute the HyperLogLog++ cardinality estimate from a packed register 
buffer. Faithful port of
+/// `HyperLogLogPlusPlusHelper.query`.
+fn query(p: usize, words: &[i64]) -> i64 {
+    let m_usize = 1usize << p;
+    let m = m_usize as f64;
+
+    let mut z_inverse = 0.0;
+    let mut v = 0.0;
+    let mut idx = 0;
+    for &word in words {
+        let word = word as u64;
+        let mut i = 0;
+        let mut shift = 0;
+        // Register iteration order matches Spark so the float summation into 
`z_inverse` stays
+        // bit-identical; do not reorder this into an iterator chain.
+        while idx < m_usize && i < REGISTERS_PER_WORD {
+            let m_idx = (word >> shift) & REGISTER_WORD_MASK;
+            z_inverse += 1.0 / ((1u64 << m_idx) as f64);
+            if m_idx == 0 {
+                v += 1.0;
+            }
+            shift += REGISTER_SIZE;
+            i += 1;
+            idx += 1;
+        }
+    }
+
+    let e = alpha_m2(p, m) / z_inverse;
+    // Bias correction runs a binary search plus KNN interpolation over the 
appendix tables.
+    // Compute it lazily so small-cardinality groups that take the 
linear-counting branch below
+    // do not pay for interpolation whose result is discarded.
+    let e_bias_corrected = || {
+        if p < 19 && e < 5.0 * m {
+            e - estimate_bias(p, e)
+        } else {
+            e
+        }
+    };
+
+    let estimate = if v > 0.0 {
+        // Linear counting for small cardinalities.
+        let h = m * (m / v).ln();
+        if (p < 19 && h <= THRESHOLDS[p - 4]) || e <= 2.5 * m {
+            h
+        } else {
+            e_bias_corrected()
+        }
+    } else {
+        e_bias_corrected()
+    };
+
+    // Match Java's Math.round: floor(x + 0.5).
+    (estimate + 0.5).floor() as i64
+}
+
+/// Per-group accumulator (also used for the ungrouped / merge path).
+#[derive(Debug)]
+struct HllPlusPlusAccumulator {
+    p: usize,
+    words: Vec<i64>,
+    /// Scratch buffer reused across batches to avoid a per-batch hash 
allocation.
+    hash_buf: Vec<u64>,
+}
+
+impl HllPlusPlusAccumulator {
+    fn new(p: usize) -> Self {
+        Self {
+            p,
+            words: vec![0i64; num_words(p)],
+            hash_buf: Vec::new(),
+        }
+    }
+}
+
+impl Accumulator for HllPlusPlusAccumulator {
+    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
+        let array = &values[0];
+        hash_values_into(array, &mut self.hash_buf)?;
+        for i in 0..array.len() {
+            if array.is_null(i) {
+                continue;
+            }
+            update_word(&mut self.words, self.p, self.hash_buf[i]);
+        }
+        Ok(())
+    }
+
+    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
+        let m = 1usize << self.p;
+        let num_rows = states[0].len();
+        let cols: Vec<&Int64Array> = states
+            .iter()
+            .map(|s| s.as_primitive::<arrow::datatypes::Int64Type>())
+            .collect();
+        let mut partial = vec![0i64; self.words.len()];
+        for row in 0..num_rows {
+            for (w, col) in cols.iter().enumerate() {
+                partial[w] = col.value(row);
+            }
+            merge_words(&mut self.words, &partial, m);
+        }
+        Ok(())
+    }
+
+    fn state(&mut self) -> Result<Vec<ScalarValue>> {
+        Ok(self
+            .words
+            .iter()
+            .map(|&w| ScalarValue::Int64(Some(w)))
+            .collect())
+    }
+
+    fn evaluate(&mut self) -> Result<ScalarValue> {
+        Ok(ScalarValue::Int64(Some(query(self.p, &self.words))))
+    }
+
+    fn size(&self) -> usize {
+        std::mem::size_of_val(self)
+            + self.words.capacity() * std::mem::size_of::<i64>()
+            + self.hash_buf.capacity() * std::mem::size_of::<u64>()
+    }
+}
+
+/// Vectorized grouped accumulator. The `numWords` words for all groups live 
in a single flat
+/// buffer, with group `g` occupying `words[g * num_words .. (g + 1) * 
num_words]`.
+#[derive(Debug)]
+struct HllPlusPlusGroupsAccumulator {
+    p: usize,
+    num_words: usize,
+    words: Vec<i64>,
+    /// Scratch buffer reused across batches to avoid a per-batch hash 
allocation.
+    hash_buf: Vec<u64>,
+}
+
+impl HllPlusPlusGroupsAccumulator {
+    fn new(p: usize) -> Self {
+        Self {
+            p,
+            num_words: num_words(p),
+            words: Vec::new(),
+            hash_buf: Vec::new(),
+        }
+    }
+
+    fn resize(&mut self, total_num_groups: usize) {
+        self.words.resize(total_num_groups * self.num_words, 0i64);
+    }
+
+    fn group_slice(&mut self, group_index: usize) -> &mut [i64] {
+        let start = group_index * self.num_words;
+        &mut self.words[start..start + self.num_words]
+    }
+
+    /// Drain the words for the emitted groups, leaving the rest for a 
subsequent emit.
+    fn emit_words(&mut self, emit_to: EmitTo) -> Vec<i64> {
+        match emit_to {
+            EmitTo::All => std::mem::take(&mut self.words),
+            EmitTo::First(n) => {
+                let split = n * self.num_words;
+                let remainder = self.words.split_off(split);
+                std::mem::replace(&mut self.words, remainder)
+            }
+        }
+    }
+}
+
+impl GroupsAccumulator for HllPlusPlusGroupsAccumulator {
+    fn update_batch(
+        &mut self,
+        values: &[ArrayRef],
+        group_indices: &[usize],
+        opt_filter: Option<&BooleanArray>,
+        total_num_groups: usize,
+    ) -> Result<()> {
+        self.resize(total_num_groups);
+        let p = self.p;
+        let array = &values[0];
+        hash_values_into(array, &mut self.hash_buf)?;
+        for (i, &group_index) in group_indices.iter().enumerate() {
+            if let Some(filter) = opt_filter {
+                if !filter.is_valid(i) || !filter.value(i) {
+                    continue;
+                }
+            }
+            if array.is_null(i) {
+                continue;
+            }
+            let hash = self.hash_buf[i];
+            update_word(self.group_slice(group_index), p, hash);
+        }
+        Ok(())
+    }
+
+    fn merge_batch(
+        &mut self,
+        values: &[ArrayRef],
+        group_indices: &[usize],
+        _opt_filter: Option<&BooleanArray>,
+        total_num_groups: usize,
+    ) -> Result<()> {
+        self.resize(total_num_groups);
+        let m = 1usize << self.p;
+        let nw = self.num_words;
+        let cols: Vec<&Int64Array> = values
+            .iter()
+            .map(|s| s.as_primitive::<arrow::datatypes::Int64Type>())
+            .collect();
+        let mut partial = vec![0i64; nw];
+        for (row, &group_index) in group_indices.iter().enumerate() {
+            for (w, col) in cols.iter().enumerate() {
+                partial[w] = col.value(row);
+            }
+            let start = group_index * nw;
+            merge_words(&mut self.words[start..start + nw], &partial, m);
+        }
+        Ok(())
+    }
+
+    fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
+        let p = self.p;
+        let nw = self.num_words;
+        let words = self.emit_words(emit_to);
+        let estimates: Vec<i64> = words.chunks_exact(nw).map(|w| query(p, 
w)).collect();
+        Ok(Arc::new(Int64Array::from(estimates)))
+    }
+
+    fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
+        let nw = self.num_words;
+        let words = self.emit_words(emit_to);
+        let num_groups = words.len() / nw;
+        // Emit one Int64 column per word, transposing the flat per-group 
layout.
+        let mut columns: Vec<ArrayRef> = Vec::with_capacity(nw);
+        for w in 0..nw {
+            let col: Vec<i64> = (0..num_groups).map(|g| words[g * nw + 
w]).collect();
+            columns.push(Arc::new(Int64Array::from(col)));
+        }
+        Ok(columns)
+    }
+
+    fn size(&self) -> usize {
+        std::mem::size_of_val(self)
+            + self.words.capacity() * std::mem::size_of::<i64>()
+            + self.hash_buf.capacity() * std::mem::size_of::<u64>()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use arrow::array::{Int32Array, StringArray};
+
+    fn acc(p: usize) -> HllPlusPlusAccumulator {
+        HllPlusPlusAccumulator::new(p)
+    }
+
+    fn eval(a: &mut HllPlusPlusAccumulator) -> i64 {
+        match a.evaluate().unwrap() {
+            ScalarValue::Int64(Some(v)) => v,
+            other => panic!("unexpected {other:?}"),
+        }
+    }
+
+    #[test]
+    fn precision_matches_spark_default() {
+        assert_eq!(hllpp_precision(0.05), 9);
+    }
+
+    #[test]
+    fn exact_for_small_cardinality() {
+        let mut a = acc(9);
+        let values: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 
1, 2, 3]));
+        a.update_batch(&[values]).unwrap();
+        assert_eq!(eval(&mut a), 5);
+    }
+
+    #[test]
+    fn empty_is_zero() {
+        let mut a = acc(9);
+        assert_eq!(eval(&mut a), 0);
+    }
+
+    #[test]
+    fn nulls_are_ignored() {
+        let mut a = acc(9);
+        let values: ArrayRef = Arc::new(Int32Array::from(vec![Some(1), None, 
Some(2), None]));
+        a.update_batch(&[values]).unwrap();
+        assert_eq!(eval(&mut a), 2);
+    }
+
+    #[test]
+    fn strings_small_cardinality() {
+        let mut a = acc(9);
+        let values: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "a", 
"c", "b"]));
+        a.update_batch(&[values]).unwrap();
+        assert_eq!(eval(&mut a), 3);
+    }
+
+    #[test]
+    fn merge_matches_single_shot() {
+        let single = {
+            let mut a = acc(9);
+            let v: ArrayRef = 
Arc::new(Int32Array::from((0..1000).collect::<Vec<i32>>()));
+            a.update_batch(&[v]).unwrap();
+            eval(&mut a)
+        };
+
+        let mut left = acc(9);
+        left.update_batch(
+            &[Arc::new(Int32Array::from((0..600).collect::<Vec<i32>>())) as 
ArrayRef],
+        )
+        .unwrap();
+        let mut right = acc(9);
+        right
+            .update_batch(&[
+                Arc::new(Int32Array::from((400..1000).collect::<Vec<i32>>())) 
as ArrayRef,
+            ])
+            .unwrap();
+
+        let mut merged = acc(9);
+        for a in [&mut left, &mut right] {
+            let state = a.state().unwrap();
+            let arrays: Vec<ArrayRef> = state
+                .into_iter()
+                .map(|s| ScalarValue::iter_to_array(vec![s]).unwrap())
+                .collect();
+            merged.merge_batch(&arrays).unwrap();
+        }
+        assert_eq!(eval(&mut merged), single);
+    }
+
+    #[test]
+    fn large_cardinality_is_approximate() {
+        let mut a = acc(9);
+        let v: ArrayRef = 
Arc::new(Int32Array::from((0..100_000).collect::<Vec<i32>>()));
+        a.update_batch(&[v]).unwrap();
+        let est = eval(&mut a);
+        let err = (est - 100_000).abs() as f64 / 100_000.0;
+        assert!(err < 0.1, "estimate {est} too far from 100000 (err {err})");
+    }
+
+    // ---- GroupsAccumulator ----
+
+    fn eval_groups(acc: &mut HllPlusPlusGroupsAccumulator) -> Vec<i64> {
+        acc.evaluate(EmitTo::All)
+            .unwrap()
+            .as_primitive::<arrow::datatypes::Int64Type>()
+            .values()
+            .to_vec()
+    }
+
+    #[test]
+    fn groups_multi_group_and_nulls() {
+        let mut acc = HllPlusPlusGroupsAccumulator::new(9);
+        // group 0: distinct {1,2,3}; group 1: {10}, plus a null (ignored)
+        let values: ArrayRef = Arc::new(Int32Array::from(vec![
+            Some(1),
+            Some(10),
+            Some(2),
+            Some(3),
+            None,
+        ]));
+        acc.update_batch(&[values], &[0, 1, 0, 0, 1], None, 2)
+            .unwrap();
+        assert_eq!(eval_groups(&mut acc), vec![3, 1]);
+    }
+
+    #[test]
+    fn groups_matches_scalar_accumulator() {
+        // The grouped path (single group) must agree with the scalar 
accumulator exactly.
+        let data: Vec<i32> = (0..20_000).map(|i| i % 7000).collect();
+        let scalar = {
+            let mut a = acc(12);
+            a.update_batch(&[Arc::new(Int32Array::from(data.clone())) as 
ArrayRef])
+                .unwrap();
+            eval(&mut a)
+        };
+        let mut g = HllPlusPlusGroupsAccumulator::new(12);
+        g.update_batch(
+            &[Arc::new(Int32Array::from(data.clone())) as ArrayRef],
+            &vec![0usize; data.len()],
+            None,
+            1,
+        )
+        .unwrap();
+        assert_eq!(eval_groups(&mut g)[0], scalar);
+    }
+
+    #[test]
+    fn groups_merge_roundtrip() {
+        let mut left = HllPlusPlusGroupsAccumulator::new(9);
+        left.update_batch(
+            &[Arc::new(Int32Array::from((0..600).collect::<Vec<i32>>())) as 
ArrayRef],
+            &vec![0usize; 600],
+            None,
+            1,
+        )
+        .unwrap();
+        let mut right = HllPlusPlusGroupsAccumulator::new(9);
+        right
+            .update_batch(
+                
&[Arc::new(Int32Array::from((400..1000).collect::<Vec<i32>>())) as ArrayRef],
+                &vec![0usize; 600],
+                None,
+                1,
+            )
+            .unwrap();
+
+        let mut merged = HllPlusPlusGroupsAccumulator::new(9);
+        for part in [&mut left, &mut right] {
+            let state = part.state(EmitTo::All).unwrap();
+            let n = state[0].len();
+            merged
+                .merge_batch(&state, &vec![0usize; n], None, 1)
+                .unwrap();
+        }
+        let single = {
+            let mut a = acc(9);
+            a.update_batch(&[
+                Arc::new(Int32Array::from((0..1000).collect::<Vec<i32>>())) as 
ArrayRef
+            ])
+            .unwrap();
+            eval(&mut a)
+        };
+        assert_eq!(eval_groups(&mut merged)[0], single);
+    }
+}
diff --git a/native/spark-expr/src/agg_funcs/hll_plus_plus_const.rs 
b/native/spark-expr/src/agg_funcs/hll_plus_plus_const.rs
new file mode 100644
index 0000000000..8cb5195a57
--- /dev/null
+++ b/native/spark-expr/src/agg_funcs/hll_plus_plus_const.rs
@@ -0,0 +1,1666 @@
+// 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.
+
+//! HyperLogLog++ bias-correction lookup tables, ported verbatim from Spark's
+//! `org.apache.spark.sql.catalyst.util.HyperLogLogPlusPlusHelper`. Index `[p 
- 4]`
+//! selects the table for precision `p` (valid range 4..=18).
+
+/// Linear-counting thresholds per precision (p = 4..=18).
+pub(crate) static THRESHOLDS: [f64; 15] = [
+    10.0, 20.0, 40.0, 80.0, 220.0, 400.0, 900.0, 1800.0, 3100.0, 6500.0, 
15500.0, 20000.0, 50000.0,
+    120000.0, 350000.0,
+];
+
+/// Raw estimate interpolation points per precision (p = 4..=18).
+pub(crate) static RAW_ESTIMATE_DATA: [&[f64]; 15] = [
+    &[
+        11.0, 11.717, 12.207, 12.7896, 13.2882, 13.8204, 14.3772, 14.9342, 
15.5202, 16.161,
+        16.7722, 17.4636, 18.0396, 18.6766, 19.3566, 20.0454, 20.7936, 
21.4856, 22.2666, 22.9946,
+        23.766, 24.4692, 25.3638, 26.0764, 26.7864, 27.7602, 28.4814, 29.433, 
30.2926, 31.0664,
+        31.9996, 32.7956, 33.5366, 34.5894, 35.5738, 36.2698, 37.3682, 
38.0544, 39.2342, 40.0108,
+        40.7966, 41.9298, 42.8704, 43.6358, 44.5194, 45.773, 46.6772, 47.6174, 
48.4888, 49.3304,
+        50.2506, 51.4996, 52.3824, 53.3078, 54.3984, 55.5838, 56.6618, 
57.2174, 58.3514, 59.0802,
+        60.1482, 61.0376, 62.3598, 62.8078, 63.9744, 64.914, 65.781, 67.1806, 
68.0594, 68.8446,
+        69.7928, 70.8248, 71.8324, 72.8598, 73.6246, 74.7014, 75.393, 76.6708, 
77.2394,
+    ],
+    &[
+        23.0, 23.1194, 23.8208, 24.2318, 24.77, 25.2436, 25.7774, 26.2848, 
26.8224, 27.3742,
+        27.9336, 28.503, 29.0494, 29.6292, 30.2124, 30.798, 31.367, 31.9728, 
32.5944, 33.217,
+        33.8438, 34.3696, 35.0956, 35.7044, 36.324, 37.0668, 37.6698, 38.3644, 
39.049, 39.6918,
+        40.4146, 41.082, 41.687, 42.5398, 43.2462, 43.857, 44.6606, 45.4168, 
46.1248, 46.9222,
+        47.6804, 48.447, 49.3454, 49.9594, 50.7636, 51.5776, 52.331, 53.19, 
53.9676, 54.7564,
+        55.5314, 56.4442, 57.3708, 57.9774, 58.9624, 59.8796, 60.755, 61.472, 
62.2076, 63.1024,
+        63.8908, 64.7338, 65.7728, 66.629, 67.413, 68.3266, 69.1524, 70.2642, 
71.1806, 72.0566,
+        72.9192, 73.7598, 74.3516, 75.5802, 76.4386, 77.4916, 78.1524, 
79.1892, 79.8414, 80.8798,
+        81.8376, 82.4698, 83.7656, 84.331, 85.5914, 86.6012, 87.7016, 88.5582, 
89.3394, 90.3544,
+        91.4912, 92.308, 93.3552, 93.9746, 95.2052, 95.727, 97.1322, 98.3944, 
98.7588, 100.242,
+        101.1914, 102.2538, 102.8776, 103.6292, 105.1932, 105.9152, 107.0868, 
107.6728, 108.7144,
+        110.3114, 110.8716, 111.245, 112.7908, 113.7064, 114.636, 115.7464, 
116.1788, 117.7464,
+        118.4896, 119.6166, 120.5082, 121.7798, 122.9028, 123.4426, 124.8854, 
125.705, 126.4652,
+        128.3464, 128.3462, 130.0398, 131.0342, 131.0042, 132.4766, 133.511, 
134.7252, 135.425,
+        136.5172, 138.0572, 138.6694, 139.3712, 140.8598, 141.4594, 142.554, 
143.4006, 144.7374,
+        146.1634, 146.8994, 147.605, 147.9304, 149.1636, 150.2468, 151.5876, 
152.2096, 153.7032,
+        154.7146, 155.807, 156.9228, 157.0372, 158.5852,
+    ],
+    &[
+        46.0, 46.1902, 47.271, 47.8358, 48.8142, 49.2854, 50.317, 51.354, 
51.8924, 52.9436,
+        53.4596, 54.5262, 55.6248, 56.1574, 57.2822, 57.837, 58.9636, 60.074, 
60.7042, 61.7976,
+        62.4772, 63.6564, 64.7942, 65.5004, 66.686, 67.291, 68.5672, 69.8556, 
70.4982, 71.8204,
+        72.4252, 73.7744, 75.0786, 75.8344, 77.0294, 77.8098, 79.0794, 
80.5732, 81.1878, 82.5648,
+        83.2902, 84.6784, 85.3352, 86.8946, 88.3712, 89.0852, 90.499, 91.2686, 
92.6844, 94.2234,
+        94.9732, 96.3356, 97.2286, 98.7262, 100.3284, 101.1048, 102.5962, 
103.3562, 105.1272,
+        106.4184, 107.4974, 109.0822, 109.856, 111.48, 113.2834, 114.0208, 
115.637, 116.5174,
+        118.0576, 119.7476, 120.427, 122.1326, 123.2372, 125.2788, 126.6776, 
127.7926, 129.1952,
+        129.9564, 131.6454, 133.87, 134.5428, 136.2, 137.0294, 138.6278, 
139.6782, 141.792,
+        143.3516, 144.2832, 146.0394, 147.0748, 148.4912, 150.849, 151.696, 
153.5404, 154.073,
+        156.3714, 157.7216, 158.7328, 160.4208, 161.4184, 163.9424, 165.2772, 
166.411, 168.1308,
+        168.769, 170.9258, 172.6828, 173.7502, 175.706, 176.3886, 179.0186, 
180.4518, 181.927,
+        183.4172, 184.4114, 186.033, 188.5124, 189.5564, 191.6008, 192.4172, 
193.8044, 194.997,
+        197.4548, 198.8948, 200.2346, 202.3086, 203.1548, 204.8842, 206.6508, 
206.6772, 209.7254,
+        210.4752, 212.7228, 214.6614, 215.1676, 217.793, 218.0006, 219.9052, 
221.66, 223.5588,
+        225.1636, 225.6882, 227.7126, 229.4502, 231.1978, 232.9756, 233.1654, 
236.727, 238.1974,
+        237.7474, 241.1346, 242.3048, 244.1948, 245.3134, 246.879, 249.1204, 
249.853, 252.6792,
+        253.857, 254.4486, 257.2362, 257.9534, 260.0286, 260.5632, 262.663, 
264.723, 265.7566,
+        267.2566, 267.1624, 270.62, 272.8216, 273.2166, 275.2056, 276.2202, 
278.3726, 280.3344,
+        281.9284, 283.9728, 284.1924, 286.4872, 287.587, 289.807, 291.1206, 
292.769, 294.8708,
+        296.665, 297.1182, 299.4012, 300.6352, 302.1354, 304.1756, 306.1606, 
307.3462, 308.5214,
+        309.4134, 310.8352, 313.9684, 315.837, 316.7796, 318.9858,
+    ],
+    &[
+        92.0, 93.4934, 94.9758, 96.4574, 97.9718, 99.4954, 101.5302, 103.0756, 
104.6374, 106.1782,
+        107.7888, 109.9522, 111.592, 113.2532, 114.9086, 116.5938, 118.9474, 
120.6796, 122.4394,
+        124.2176, 125.9768, 128.4214, 130.2528, 132.0102, 133.8658, 135.7278, 
138.3044, 140.1316,
+        142.093, 144.0032, 145.9092, 148.6306, 150.5294, 152.5756, 154.6508, 
156.662, 159.552,
+        161.3724, 163.617, 165.5754, 167.7872, 169.8444, 172.7988, 174.8606, 
177.2118, 179.3566,
+        181.4476, 184.5882, 186.6816, 189.0824, 191.0258, 193.6048, 196.4436, 
198.7274, 200.957,
+        203.147, 205.4364, 208.7592, 211.3386, 213.781, 215.8028, 218.656, 
221.6544, 223.996,
+        226.4718, 229.1544, 231.6098, 234.5956, 237.0616, 239.5758, 242.4878, 
244.5244, 248.2146,
+        250.724, 252.8722, 255.5198, 258.0414, 261.941, 264.9048, 266.87, 
269.4304, 272.028,
+        274.4708, 278.37, 281.0624, 283.4668, 286.5532, 289.4352, 293.2564, 
295.2744, 298.2118,
+        300.7472, 304.1456, 307.2928, 309.7504, 312.5528, 315.979, 318.2102, 
322.1834, 324.3494,
+        327.325, 330.6614, 332.903, 337.2544, 339.9042, 343.215, 345.2864, 
348.0814, 352.6764,
+        355.301, 357.139, 360.658, 363.1732, 366.5902, 369.9538, 373.0828, 
375.922, 378.9902,
+        382.7328, 386.4538, 388.1136, 391.2234, 394.0878, 396.708, 401.1556, 
404.1852, 406.6372,
+        409.6822, 412.7796, 416.6078, 418.4916, 422.131, 424.5376, 428.1988, 
432.211, 434.4502,
+        438.5282, 440.912, 444.0448, 447.7432, 450.8524, 453.7988, 456.7858, 
458.8868, 463.9886,
+        466.5064, 468.9124, 472.6616, 475.4682, 478.582, 481.304, 485.2738, 
488.6894, 490.329,
+        496.106, 497.6908, 501.1374, 504.5322, 506.8848, 510.3324, 513.4512, 
516.179, 520.4412,
+        522.6066, 526.167, 528.7794, 533.379, 536.067, 538.46, 542.9116, 
545.692, 547.9546,
+        552.493, 555.2722, 557.335, 562.449, 564.2014, 569.0738, 571.0974, 
574.8564, 578.2996,
+        581.409, 583.9704, 585.8098, 589.6528, 594.5998, 595.958, 600.068, 
603.3278, 608.2016,
+        609.9632, 612.864, 615.43, 620.7794, 621.272, 625.8644, 629.206, 
633.219, 634.5154,
+        638.6102,
+    ],
+    &[
+        184.2152, 187.2454, 190.2096, 193.6652, 196.6312, 199.6822, 203.249, 
206.3296, 210.0038,
+        213.2074, 216.4612, 220.27, 223.5178, 227.4412, 230.8032, 234.1634, 
238.1688, 241.6074,
+        245.6946, 249.2664, 252.8228, 257.0432, 260.6824, 264.9464, 268.6268, 
272.2626, 276.8376,
+        280.4034, 284.8956, 288.8522, 292.7638, 297.3552, 301.3556, 305.7526, 
309.9292, 313.8954,
+        318.8198, 322.7668, 327.298, 331.6688, 335.9466, 340.9746, 345.1672, 
349.3474, 354.3028,
+        358.8912, 364.114, 368.4646, 372.9744, 378.4092, 382.6022, 387.843, 
392.5684, 397.1652,
+        402.5426, 407.4152, 412.5388, 417.3592, 422.1366, 427.486, 432.3918, 
437.5076, 442.509,
+        447.3834, 453.3498, 458.0668, 463.7346, 469.1228, 473.4528, 479.7, 
484.644, 491.0518,
+        495.5774, 500.9068, 506.432, 512.1666, 517.434, 522.6644, 527.4894, 
533.6312, 538.3804,
+        544.292, 550.5496, 556.0234, 562.8206, 566.6146, 572.4188, 579.117, 
583.6762, 590.6576,
+        595.7864, 601.509, 607.5334, 612.9204, 619.772, 624.2924, 630.8654, 
636.1836, 642.745,
+        649.1316, 655.0386, 660.0136, 666.6342, 671.6196, 678.1866, 684.4282, 
689.3324, 695.4794,
+        702.5038, 708.129, 713.528, 720.3204, 726.463, 732.7928, 739.123, 
744.7418, 751.2192,
+        756.5102, 762.6066, 769.0184, 775.2224, 781.4014, 787.7618, 794.1436, 
798.6506, 805.6378,
+        811.766, 819.7514, 824.5776, 828.7322, 837.8048, 843.6302, 849.9336, 
854.4798, 861.3388,
+        867.9894, 873.8196, 880.3136, 886.2308, 892.4588, 899.0816, 905.4076, 
912.0064, 917.3878,
+        923.619, 929.998, 937.3482, 943.9506, 947.991, 955.1144, 962.203, 
968.8222, 975.7324,
+        981.7826, 988.7666, 994.2648, 1000.3128, 1007.4082, 1013.7536, 
1020.3376, 1026.7156,
+        1031.7478, 1037.4292, 1045.393, 1051.2278, 1058.3434, 1062.8726, 
1071.884, 1076.806,
+        1082.9176, 1089.1678, 1095.5032, 1102.525, 1107.2264, 1115.315, 
1120.93, 1127.252,
+        1134.1496, 1139.0408, 1147.5448, 1153.3296, 1158.1974, 1166.5262, 
1174.3328, 1175.657,
+        1184.4222, 1190.9172, 1197.1292, 1204.4606, 1210.4578, 1218.8728, 
1225.3336, 1226.6592,
+        1236.5768, 1241.363, 1249.4074, 1254.6566, 1260.8014, 1266.5454, 
1274.5192,
+    ],
+    &[
+        369.0, 374.8294, 381.2452, 387.6698, 394.1464, 400.2024, 406.8782, 
413.6598, 420.462,
+        427.2826, 433.7102, 440.7416, 447.9366, 455.1046, 462.285, 469.0668, 
476.306, 483.8448,
+        491.301, 498.9886, 506.2422, 513.8138, 521.7074, 529.7428, 537.8402, 
545.1664, 553.3534,
+        561.594, 569.6886, 577.7876, 585.65, 594.228, 602.8036, 611.1666, 
620.0818, 628.0824,
+        637.2574, 646.302, 655.1644, 664.0056, 672.3802, 681.7192, 690.5234, 
700.2084, 708.831,
+        718.485, 728.1112, 737.4764, 746.76, 756.3368, 766.5538, 775.5058, 
785.2646, 795.5902,
+        804.3818, 814.8998, 824.9532, 835.2062, 845.2798, 854.4728, 864.9582, 
875.3292, 886.171,
+        896.781, 906.5716, 916.7048, 927.5322, 937.875, 949.3972, 958.3464, 
969.7274, 980.2834,
+        992.1444, 1003.4264, 1013.0166, 1024.018, 1035.0438, 1046.34, 
1057.6856, 1068.9836,
+        1079.0312, 1091.677, 1102.3188, 1113.4846, 1124.4424, 1135.739, 
1147.1488, 1158.9202,
+        1169.406, 1181.5342, 1193.2834, 1203.8954, 1216.3286, 1226.2146, 
1239.6684, 1251.9946,
+        1262.123, 1275.4338, 1285.7378, 1296.076, 1308.9692, 1320.4964, 
1333.0998, 1343.9864,
+        1357.7754, 1368.3208, 1380.4838, 1392.7388, 1406.0758, 1416.9098, 
1428.9728, 1440.9228,
+        1453.9292, 1462.617, 1476.05, 1490.2996, 1500.6128, 1513.7392, 
1524.5174, 1536.6322,
+        1548.2584, 1562.3766, 1572.423, 1587.1232, 1596.5164, 1610.5938, 
1622.5972, 1633.1222,
+        1647.7674, 1658.5044, 1671.57, 1683.7044, 1695.4142, 1708.7102, 
1720.6094, 1732.6522,
+        1747.841, 1756.4072, 1769.9786, 1782.3276, 1797.5216, 1808.3186, 
1819.0694, 1834.354,
+        1844.575, 1856.2808, 1871.1288, 1880.7852, 1893.9622, 1906.3418, 
1920.6548, 1932.9302,
+        1945.8584, 1955.473, 1968.8248, 1980.6446, 1995.9598, 2008.349, 
2019.8556, 2033.0334,
+        2044.0206, 2059.3956, 2069.9174, 2082.6084, 2093.7036, 2106.6108, 
2118.9124, 2132.301,
+        2144.7628, 2159.8422, 2171.0212, 2183.101, 2193.5112, 2208.052, 
2221.3194, 2233.3282,
+        2247.295, 2257.7222, 2273.342, 2286.5638, 2299.6786, 2310.8114, 
2322.3312, 2335.516,
+        2349.874, 2363.5968, 2373.865, 2387.1918, 2401.8328, 2414.8496, 
2424.544, 2436.7592,
+        2447.1682, 2464.1958, 2474.3438, 2489.0006, 2497.4526, 2513.6586, 
2527.19, 2540.7028,
+        2553.768,
+    ],
+    &[
+        738.1256, 750.4234, 763.1064, 775.4732, 788.4636, 801.0644, 814.488, 
827.9654, 841.0832,
+        854.7864, 868.1992, 882.2176, 896.5228, 910.1716, 924.7752, 938.899, 
953.6126, 968.6492,
+        982.9474, 998.5214, 1013.1064, 1028.6364, 1044.2468, 1059.4588, 
1075.3832, 1091.0584,
+        1106.8606, 1123.3868, 1139.5062, 1156.1862, 1172.463, 1189.339, 
1206.1936, 1223.1292,
+        1240.1854, 1257.2908, 1275.3324, 1292.8518, 1310.5204, 1328.4854, 
1345.9318, 1364.552,
+        1381.4658, 1400.4256, 1419.849, 1438.152, 1456.8956, 1474.8792, 
1494.118, 1513.62,
+        1532.5132, 1551.9322, 1570.7726, 1590.6086, 1610.5332, 1630.5918, 
1650.4294, 1669.7662,
+        1690.4106, 1710.7338, 1730.9012, 1750.4486, 1770.1556, 1791.6338, 
1812.7312, 1833.6264,
+        1853.9526, 1874.8742, 1896.8326, 1918.1966, 1939.5594, 1961.07, 
1983.037, 2003.1804,
+        2026.071, 2047.4884, 2070.0848, 2091.2944, 2114.333, 2135.9626, 
2158.2902, 2181.0814,
+        2202.0334, 2224.4832, 2246.39, 2269.7202, 2292.1714, 2314.2358, 
2338.9346, 2360.891,
+        2384.0264, 2408.3834, 2430.1544, 2454.8684, 2476.9896, 2501.4368, 
2522.8702, 2548.0408,
+        2570.6738, 2593.5208, 2617.0158, 2640.2302, 2664.0962, 2687.4986, 
2714.2588, 2735.3914,
+        2759.6244, 2781.8378, 2808.0072, 2830.6516, 2856.2454, 2877.2136, 
2903.4546, 2926.785,
+        2951.2294, 2976.468, 3000.867, 3023.6508, 3049.91, 3073.5984, 
3098.162, 3121.5564,
+        3146.2328, 3170.9484, 3195.5902, 3221.3346, 3242.7032, 3271.6112, 
3296.5546, 3317.7376,
+        3345.072, 3369.9518, 3394.326, 3418.1818, 3444.6926, 3469.086, 
3494.2754, 3517.8698,
+        3544.248, 3565.3768, 3588.7234, 3616.979, 3643.7504, 3668.6812, 
3695.72, 3719.7392,
+        3742.6224, 3770.4456, 3795.6602, 3819.9058, 3844.002, 3869.517, 
3895.6824, 3920.8622,
+        3947.1364, 3973.985, 3995.4772, 4021.62, 4046.628, 4074.65, 4096.2256, 
4121.831, 4146.6406,
+        4173.276, 4195.0744, 4223.9696, 4251.3708, 4272.9966, 4300.8046, 
4326.302, 4353.1248,
+        4374.312, 4403.0322, 4426.819, 4450.0598, 4478.5206, 4504.8116, 
4528.8928, 4553.9584,
+        4578.8712, 4603.8384, 4632.3872, 4655.5128, 4675.821, 4704.6222, 
4731.9862, 4755.4174,
+        4781.2628, 4804.332, 4832.3048, 4862.8752, 4883.4148, 4906.9544, 
4935.3516, 4954.3532,
+        4984.0248, 5011.217, 5035.3258, 5057.3672, 5084.1828,
+    ],
+    &[
+        1477.0, 1501.6014, 1526.5802, 1551.7942, 1577.3042, 1603.2062, 
1629.8402, 1656.2292,
+        1682.9462, 1709.9926, 1737.3026, 1765.4252, 1793.0578, 1821.6092, 
1849.626, 1878.5568,
+        1908.527, 1937.5154, 1967.1874, 1997.3878, 2027.37, 2058.1972, 
2089.5728, 2120.1012,
+        2151.9668, 2183.292, 2216.0772, 2247.8578, 2280.6562, 2313.041, 
2345.714, 2380.3112,
+        2414.1806, 2447.9854, 2481.656, 2516.346, 2551.5154, 2586.8378, 
2621.7448, 2656.6722,
+        2693.5722, 2729.1462, 2765.4124, 2802.8728, 2838.898, 2876.408, 
2913.4926, 2951.4938,
+        2989.6776, 3026.282, 3065.7704, 3104.1012, 3143.7388, 3181.6876, 
3221.1872, 3261.5048,
+        3300.0214, 3339.806, 3381.409, 3421.4144, 3461.4294, 3502.2286, 
3544.651, 3586.6156,
+        3627.337, 3670.083, 3711.1538, 3753.5094, 3797.01, 3838.6686, 
3882.1678, 3922.8116,
+        3967.9978, 4009.9204, 4054.3286, 4097.5706, 4140.6014, 4185.544, 
4229.5976, 4274.583,
+        4316.9438, 4361.672, 4406.2786, 4451.8628, 4496.1834, 4543.505, 
4589.1816, 4632.5188,
+        4678.2294, 4724.8908, 4769.0194, 4817.052, 4861.4588, 4910.1596, 
4956.4344, 5002.5238,
+        5048.13, 5093.6374, 5142.8162, 5187.7894, 5237.3984, 5285.6078, 
5331.0858, 5379.1036,
+        5428.6258, 5474.6018, 5522.7618, 5571.5822, 5618.59, 5667.9992, 
5714.88, 5763.454,
+        5808.6982, 5860.3644, 5910.2914, 5953.571, 6005.9232, 6055.1914, 
6104.5882, 6154.5702,
+        6199.7036, 6251.1764, 6298.7596, 6350.0302, 6398.061, 6448.4694, 
6495.933, 6548.0474,
+        6597.7166, 6646.9416, 6695.9208, 6742.6328, 6793.5276, 6842.1934, 
6894.2372, 6945.3864,
+        6996.9228, 7044.2372, 7094.1374, 7142.2272, 7192.2942, 7238.8338, 
7288.9006, 7344.0908,
+        7394.8544, 7443.5176, 7490.4148, 7542.9314, 7595.6738, 7641.9878, 
7694.3688, 7743.0448,
+        7797.522, 7845.53, 7899.594, 7950.3132, 7996.455, 8050.9442, 
8092.9114, 8153.1374,
+        8197.4472, 8252.8278, 8301.8728, 8348.6776, 8401.4698, 8453.551, 
8504.6598, 8553.8944,
+        8604.1276, 8657.6514, 8710.3062, 8758.908, 8807.8706, 8862.1702, 
8910.4668, 8960.77,
+        9007.2766, 9063.164, 9121.0534, 9164.1354, 9218.1594, 9267.767, 
9319.0594, 9372.155,
+        9419.7126, 9474.3722, 9520.1338, 9572.368, 9622.7702, 9675.8448, 
9726.5396, 9778.7378,
+        9827.6554, 9878.1922, 9928.7782, 9978.3984, 10026.578, 10076.5626, 
10137.1618, 10177.5244,
+        10229.9176,
+    ],
+    &[
+        2954.0, 3003.4782, 3053.3568, 3104.3666, 3155.324, 3206.9598, 
3259.648, 3312.539,
+        3366.1474, 3420.2576, 3474.8376, 3530.6076, 3586.451, 3643.38, 
3700.4104, 3757.5638,
+        3815.9676, 3875.193, 3934.838, 3994.8548, 4055.018, 4117.1742, 
4178.4482, 4241.1294,
+        4304.4776, 4367.4044, 4431.8724, 4496.3732, 4561.4304, 4627.5326, 
4693.949, 4761.5532,
+        4828.7256, 4897.6182, 4965.5186, 5034.4528, 5104.865, 5174.7164, 
5244.6828, 5316.6708,
+        5387.8312, 5459.9036, 5532.476, 5604.8652, 5679.6718, 5753.757, 
5830.2072, 5905.2828,
+        5980.0434, 6056.6264, 6134.3192, 6211.5746, 6290.0816, 6367.1176, 
6447.9796, 6526.5576,
+        6606.1858, 6686.9144, 6766.1142, 6847.0818, 6927.9664, 7010.9096, 
7091.0816, 7175.3962,
+        7260.3454, 7344.018, 7426.4214, 7511.3106, 7596.0686, 7679.8094, 
7765.818, 7852.4248,
+        7936.834, 8022.363, 8109.5066, 8200.4554, 8288.5832, 8373.366, 
8463.4808, 8549.7682,
+        8642.0522, 8728.3288, 8820.9528, 8907.727, 9001.0794, 9091.2522, 
9179.988, 9269.852,
+        9362.6394, 9453.642, 9546.9024, 9640.6616, 9732.6622, 9824.3254, 
9917.7484, 10007.9392,
+        10106.7508, 10196.2152, 10289.8114, 10383.5494, 10482.3064, 
10576.8734, 10668.7872,
+        10764.7156, 10862.0196, 10952.793, 11049.9748, 11146.0702, 11241.4492, 
11339.2772,
+        11434.2336, 11530.741, 11627.6136, 11726.311, 11821.5964, 11918.837, 
12015.3724,
+        12113.0162, 12213.0424, 12306.9804, 12408.4518, 12504.8968, 12604.586, 
12700.9332,
+        12798.705, 12898.5142, 12997.0488, 13094.788, 13198.475, 13292.7764, 
13392.9698,
+        13486.8574, 13590.1616, 13686.5838, 13783.6264, 13887.2638, 
13992.0978, 14081.0844,
+        14189.9956, 14280.0912, 14382.4956, 14486.4384, 14588.1082, 
14686.2392, 14782.276,
+        14888.0284, 14985.1864, 15088.8596, 15187.0998, 15285.027, 15383.6694, 
15495.8266,
+        15591.3736, 15694.2008, 15790.3246, 15898.4116, 15997.4522, 
16095.5014, 16198.8514,
+        16291.7492, 16402.6424, 16499.1266, 16606.2436, 16697.7186, 
16796.3946, 16902.3376,
+        17005.7672, 17100.814, 17206.8282, 17305.8262, 17416.0744, 17508.4092, 
17617.0178,
+        17715.4554, 17816.758, 17920.1748, 18012.9236, 18119.7984, 18223.2248, 
18324.2482,
+        18426.6276, 18525.0932, 18629.8976, 18733.2588, 18831.0466, 
18940.1366, 19032.2696,
+        19131.729, 19243.4864, 19349.6932, 19442.866, 19547.9448, 19653.2798, 
19754.4034,
+        19854.0692, 19965.1224, 20065.1774, 20158.2212, 20253.353, 20366.3264, 
20463.22,
+    ],
+    &[
+        5908.5052, 6007.2672, 6107.347, 6208.5794, 6311.2622, 6414.5514, 
6519.3376, 6625.6952,
+        6732.5988, 6841.3552, 6950.5972, 7061.3082, 7173.5646, 7287.109, 
7401.8216, 7516.4344,
+        7633.3802, 7751.2962, 7870.3784, 7990.292, 8110.79, 8233.4574, 
8356.6036, 8482.2712,
+        8607.7708, 8735.099, 8863.1858, 8993.4746, 9123.8496, 9255.6794, 
9388.5448, 9522.7516,
+        9657.3106, 9792.6094, 9930.5642, 10068.794, 10206.7256, 10347.81, 
10490.3196, 10632.0778,
+        10775.9916, 10920.4662, 11066.124, 11213.073, 11358.0362, 11508.1006, 
11659.1716,
+        11808.7514, 11959.4884, 12112.1314, 12265.037, 12420.3756, 12578.933, 
12734.311,
+        12890.0006, 13047.2144, 13207.3096, 13368.5144, 13528.024, 13689.847, 
13852.7528,
+        14018.3168, 14180.5372, 14346.9668, 14513.5074, 14677.867, 14846.2186, 
15017.4186,
+        15184.9716, 15356.339, 15529.2972, 15697.3578, 15871.8686, 16042.187, 
16216.4094,
+        16389.4188, 16565.9126, 16742.3272, 16919.0042, 17094.7592, 17273.965, 
17451.8342,
+        17634.4254, 17810.5984, 17988.9242, 18171.051, 18354.7938, 18539.466, 
18721.0408,
+        18904.9972, 19081.867, 19271.9118, 19451.8694, 19637.9816, 19821.2922, 
20013.1292,
+        20199.3858, 20387.8726, 20572.9514, 20770.7764, 20955.1714, 21144.751, 
21329.9952,
+        21520.709, 21712.7016, 21906.3868, 22096.2626, 22286.0524, 22475.051, 
22665.5098,
+        22862.8492, 23055.5294, 23249.6138, 23437.848, 23636.273, 23826.093, 
24020.3296,
+        24213.3896, 24411.7392, 24602.9614, 24805.7952, 24998.1552, 
25193.9588, 25389.0166,
+        25585.8392, 25780.6976, 25981.2728, 26175.977, 26376.5252, 26570.1964, 
26773.387,
+        26962.9812, 27163.0586, 27368.164, 27565.0534, 27758.7428, 27961.1276, 
28163.2324,
+        28362.3816, 28565.7668, 28758.644, 28956.9768, 29163.4722, 29354.7026, 
29561.1186,
+        29767.9948, 29959.9986, 30164.0492, 30366.9818, 30562.5338, 
30762.9928, 30976.1592,
+        31166.274, 31376.722, 31570.3734, 31770.809, 31974.8934, 32179.5286, 
32387.5442,
+        32582.3504, 32794.076, 32989.9528, 33191.842, 33392.4684, 33595.659, 
33801.8672,
+        34000.3414, 34200.0922, 34402.6792, 34610.0638, 34804.0084, 35011.13, 
35218.669,
+        35418.6634, 35619.0792, 35830.6534, 36028.4966, 36229.7902, 
36438.6422, 36630.7764,
+        36833.3102, 37048.6728, 37247.3916, 37453.5904, 37669.3614, 
37854.5526, 38059.305,
+        38268.0936, 38470.2516, 38674.7064, 38876.167, 39068.3794, 39281.9144, 
39492.8566,
+        39684.8628, 39898.4108, 40093.1836, 40297.6858, 40489.7086, 40717.2424,
+    ],
+    &[
+        11817.475, 12015.0046, 12215.3792, 12417.7504, 12623.1814, 12830.0086, 
13040.0072,
+        13252.503, 13466.178, 13683.2738, 13902.0344, 14123.9798, 14347.394, 
14573.7784,
+        14802.6894, 15033.6824, 15266.9134, 15502.8624, 15741.4944, 
15980.7956, 16223.8916,
+        16468.6316, 16715.733, 16965.5726, 17217.204, 17470.666, 17727.8516, 
17986.7886,
+        18247.6902, 18510.9632, 18775.304, 19044.7486, 19314.4408, 19587.202, 
19862.2576,
+        20135.924, 20417.0324, 20697.9788, 20979.6112, 21265.0274, 21550.723, 
21841.6906,
+        22132.162, 22428.1406, 22722.127, 23020.5606, 23319.7394, 23620.4014, 
23925.2728,
+        24226.9224, 24535.581, 24845.505, 25155.9618, 25470.3828, 25785.9702, 
26103.7764,
+        26420.4132, 26742.0186, 27062.8852, 27388.415, 27714.6024, 28042.296, 
28365.4494,
+        28701.1526, 29031.8008, 29364.2156, 29704.497, 30037.1458, 30380.111, 
30723.8168,
+        31059.5114, 31404.9498, 31751.6752, 32095.2686, 32444.7792, 32794.767, 
33145.204,
+        33498.4226, 33847.6502, 34209.006, 34560.849, 34919.4838, 35274.9778, 
35635.1322,
+        35996.3266, 36359.1394, 36722.8266, 37082.8516, 37447.7354, 
37815.9606, 38191.0692,
+        38559.4106, 38924.8112, 39294.6726, 39663.973, 40042.261, 40416.2036, 
40779.2036,
+        41161.6436, 41540.9014, 41921.1998, 42294.7698, 42678.5264, 
43061.3464, 43432.375,
+        43818.432, 44198.6598, 44583.0138, 44970.4794, 45353.924, 45729.858, 
46118.2224,
+        46511.5724, 46900.7386, 47280.6964, 47668.1472, 48055.6796, 
48446.9436, 48838.7146,
+        49217.7296, 49613.7796, 50010.7508, 50410.0208, 50793.7886, 
51190.2456, 51583.1882,
+        51971.0796, 52376.5338, 52763.319, 53165.5534, 53556.5594, 53948.2702, 
54346.352,
+        54748.7914, 55138.577, 55543.4824, 55941.1748, 56333.7746, 56745.1552, 
57142.7944,
+        57545.2236, 57935.9956, 58348.5268, 58737.5474, 59158.5962, 
59542.6896, 59958.8004,
+        60349.3788, 60755.0212, 61147.6144, 61548.194, 61946.0696, 62348.6042, 
62763.603,
+        63162.781, 63560.635, 63974.3482, 64366.4908, 64771.5876, 65176.7346, 
65597.3916,
+        65995.915, 66394.0384, 66822.9396, 67203.6336, 67612.2032, 68019.0078, 
68420.0388,
+        68821.22, 69235.8388, 69640.0724, 70055.155, 70466.357, 70863.4266, 
71276.2482, 71677.0306,
+        72080.2006, 72493.0214, 72893.5952, 73314.5856, 73714.9852, 
74125.3022, 74521.2122,
+        74933.6814, 75341.5904, 75743.0244, 76166.0278, 76572.1322, 
76973.1028, 77381.6284,
+        77800.6092, 78189.328, 78607.0962, 79012.2508, 79407.8358, 79825.725, 
80238.701, 80646.891,
+        81035.6436, 81460.0448, 81876.3884,
+    ],
+    &[
+        23635.0036,
+        24030.8034,
+        24431.4744,
+        24837.1524,
+        25246.7928,
+        25661.326,
+        26081.3532,
+        26505.2806,
+        26933.9892,
+        27367.7098,
+        27805.318,
+        28248.799,
+        28696.4382,
+        29148.8244,
+        29605.5138,
+        30066.8668,
+        30534.2344,
+        31006.32,
+        31480.778,
+        31962.2418,
+        32447.3324,
+        32938.0232,
+        33432.731,
+        33930.728,
+        34433.9896,
+        34944.1402,
+        35457.5588,
+        35974.5958,
+        36497.3296,
+        37021.9096,
+        37554.326,
+        38088.0826,
+        38628.8816,
+        39171.3192,
+        39723.2326,
+        40274.5554,
+        40832.3142,
+        41390.613,
+        41959.5908,
+        42532.5466,
+        43102.0344,
+        43683.5072,
+        44266.694,
+        44851.2822,
+        45440.7862,
+        46038.0586,
+        46640.3164,
+        47241.064,
+        47846.155,
+        48454.7396,
+        49076.9168,
+        49692.542,
+        50317.4778,
+        50939.65,
+        51572.5596,
+        52210.2906,
+        52843.7396,
+        53481.3996,
+        54127.236,
+        54770.406,
+        55422.6598,
+        56078.7958,
+        56736.7174,
+        57397.6784,
+        58064.5784,
+        58730.308,
+        59404.9784,
+        60077.0864,
+        60751.9158,
+        61444.1386,
+        62115.817,
+        62808.7742,
+        63501.4774,
+        64187.5454,
+        64883.6622,
+        65582.7468,
+        66274.5318,
+        66976.9276,
+        67688.7764,
+        68402.138,
+        69109.6274,
+        69822.9706,
+        70543.6108,
+        71265.5202,
+        71983.3848,
+        72708.4656,
+        73433.384,
+        74158.4664,
+        74896.4868,
+        75620.9564,
+        76362.1434,
+        77098.3204,
+        77835.7662,
+        78582.6114,
+        79323.9902,
+        80067.8658,
+        80814.9246,
+        81567.0136,
+        82310.8536,
+        83061.9952,
+        83821.4096,
+        84580.8608,
+        85335.547,
+        86092.5802,
+        86851.6506,
+        87612.311,
+        88381.2016,
+        89146.3296,
+        89907.8974,
+        90676.846,
+        91451.4152,
+        92224.5518,
+        92995.8686,
+        93763.5066,
+        94551.2796,
+        95315.1944,
+        96096.1806,
+        96881.0918,
+        97665.679,
+        98442.68,
+        99229.3002,
+        100011.0994,
+        100790.6386,
+        101580.1564,
+        102377.7484,
+        103152.1392,
+        103944.2712,
+        104730.216,
+        105528.6336,
+        106324.9398,
+        107117.6706,
+        107890.3988,
+        108695.2266,
+        109485.238,
+        110294.7876,
+        111075.0958,
+        111878.0496,
+        112695.2864,
+        113464.5486,
+        114270.0474,
+        115068.608,
+        115884.3626,
+        116673.2588,
+        117483.3716,
+        118275.097,
+        119085.4092,
+        119879.2808,
+        120687.5868,
+        121499.9944,
+        122284.916,
+        123095.9254,
+        123912.5038,
+        124709.0454,
+        125503.7182,
+        126323.259,
+        127138.9412,
+        127943.8294,
+        128755.646,
+        129556.5354,
+        130375.3298,
+        131161.4734,
+        131971.1962,
+        132787.5458,
+        133588.1056,
+        134431.351,
+        135220.2906,
+        136023.398,
+        136846.6558,
+        137667.0004,
+        138463.663,
+        139283.7154,
+        140074.6146,
+        140901.3072,
+        141721.8548,
+        142543.2322,
+        143356.1096,
+        144173.7412,
+        144973.0948,
+        145794.3162,
+        146609.5714,
+        147420.003,
+        148237.9784,
+        149050.5696,
+        149854.761,
+        150663.1966,
+        151494.0754,
+        152313.1416,
+        153112.6902,
+        153935.7206,
+        154746.9262,
+        155559.547,
+        156401.9746,
+        157228.7036,
+        158008.7254,
+        158820.75,
+        159646.9184,
+        160470.4458,
+        161279.5348,
+        162093.3114,
+        162918.542,
+        163729.2842,
+    ],
+    &[
+        47271.0,
+        48062.3584,
+        48862.7074,
+        49673.152,
+        50492.8416,
+        51322.9514,
+        52161.03,
+        53009.407,
+        53867.6348,
+        54734.206,
+        55610.5144,
+        56496.2096,
+        57390.795,
+        58297.268,
+        59210.6448,
+        60134.665,
+        61068.0248,
+        62010.4472,
+        62962.5204,
+        63923.5742,
+        64895.0194,
+        65876.4182,
+        66862.6136,
+        67862.6968,
+        68868.8908,
+        69882.8544,
+        70911.271,
+        71944.0924,
+        72990.0326,
+        74040.692,
+        75100.6336,
+        76174.7826,
+        77252.5998,
+        78340.2974,
+        79438.2572,
+        80545.4976,
+        81657.2796,
+        82784.6336,
+        83915.515,
+        85059.7362,
+        86205.9368,
+        87364.4424,
+        88530.3358,
+        89707.3744,
+        90885.9638,
+        92080.197,
+        93275.5738,
+        94479.391,
+        95695.918,
+        96919.2236,
+        98148.4602,
+        99382.3474,
+        100625.6974,
+        101878.0284,
+        103141.6278,
+        104409.4588,
+        105686.2882,
+        106967.5402,
+        108261.6032,
+        109548.1578,
+        110852.0728,
+        112162.231,
+        113479.0072,
+        114806.2626,
+        116137.9072,
+        117469.5048,
+        118813.5186,
+        120165.4876,
+        121516.2556,
+        122875.766,
+        124250.5444,
+        125621.2222,
+        127003.2352,
+        128387.848,
+        129775.2644,
+        131181.7776,
+        132577.3086,
+        133979.9458,
+        135394.1132,
+        136800.9078,
+        138233.217,
+        139668.5308,
+        141085.212,
+        142535.2122,
+        143969.0684,
+        145420.2872,
+        146878.1542,
+        148332.7572,
+        149800.3202,
+        151269.66,
+        152743.6104,
+        154213.0948,
+        155690.288,
+        157169.4246,
+        158672.1756,
+        160160.059,
+        161650.6854,
+        163145.7772,
+        164645.6726,
+        166159.1952,
+        167682.1578,
+        169177.3328,
+        170700.0118,
+        172228.8964,
+        173732.6664,
+        175265.5556,
+        176787.799,
+        178317.111,
+        179856.6914,
+        181400.865,
+        182943.4612,
+        184486.742,
+        186033.4698,
+        187583.7886,
+        189148.1868,
+        190688.4526,
+        192250.1926,
+        193810.9042,
+        195354.2972,
+        196938.7682,
+        198493.5898,
+        200079.2824,
+        201618.912,
+        203205.5492,
+        204765.5798,
+        206356.1124,
+        207929.3064,
+        209498.7196,
+        211086.229,
+        212675.1324,
+        214256.7892,
+        215826.2392,
+        217412.8474,
+        218995.6724,
+        220618.6038,
+        222207.1166,
+        223781.0364,
+        225387.4332,
+        227005.7928,
+        228590.4336,
+        230217.8738,
+        231805.1054,
+        233408.9,
+        234995.3432,
+        236601.4956,
+        238190.7904,
+        239817.2548,
+        241411.2832,
+        243002.4066,
+        244640.1884,
+        246255.3128,
+        247849.3508,
+        249479.9734,
+        251106.8822,
+        252705.027,
+        254332.9242,
+        255935.129,
+        257526.9014,
+        259154.772,
+        260777.625,
+        262390.253,
+        264004.4906,
+        265643.59,
+        267255.4076,
+        268873.426,
+        270470.7252,
+        272106.4804,
+        273722.4456,
+        275337.794,
+        276945.7038,
+        278592.9154,
+        280204.3726,
+        281841.1606,
+        283489.171,
+        285130.1716,
+        286735.3362,
+        288364.7164,
+        289961.1814,
+        291595.5524,
+        293285.683,
+        294899.6668,
+        296499.3434,
+        298128.0462,
+        299761.8946,
+        301394.2424,
+        302997.6748,
+        304615.1478,
+        306269.7724,
+        307886.114,
+        309543.1028,
+        311153.2862,
+        312782.8546,
+        314421.2008,
+        316033.2438,
+        317692.9636,
+        319305.2648,
+        320948.7406,
+        322566.3364,
+        324228.4224,
+        325847.1542,
+    ],
+    &[
+        94542.0,
+        96125.811,
+        97728.019,
+        99348.558,
+        100987.9705,
+        102646.7565,
+        104324.5125,
+        106021.7435,
+        107736.7865,
+        109469.272,
+        111223.9465,
+        112995.219,
+        114787.432,
+        116593.152,
+        118422.71,
+        120267.2345,
+        122134.6765,
+        124020.937,
+        125927.2705,
+        127851.255,
+        129788.9485,
+        131751.016,
+        133726.8225,
+        135722.592,
+        137736.789,
+        139770.568,
+        141821.518,
+        143891.343,
+        145982.1415,
+        148095.387,
+        150207.526,
+        152355.649,
+        154515.6415,
+        156696.05,
+        158887.7575,
+        161098.159,
+        163329.852,
+        165569.053,
+        167837.4005,
+        170121.6165,
+        172420.4595,
+        174732.6265,
+        177062.77,
+        179412.502,
+        181774.035,
+        184151.939,
+        186551.6895,
+        188965.691,
+        191402.8095,
+        193857.949,
+        196305.0775,
+        198774.6715,
+        201271.2585,
+        203764.78,
+        206299.3695,
+        208818.1365,
+        211373.115,
+        213946.7465,
+        216532.076,
+        219105.541,
+        221714.5375,
+        224337.5135,
+        226977.5125,
+        229613.0655,
+        232270.2685,
+        234952.2065,
+        237645.3555,
+        240331.1925,
+        243034.517,
+        245756.0725,
+        248517.6865,
+        251232.737,
+        254011.3955,
+        256785.995,
+        259556.44,
+        262368.335,
+        265156.911,
+        267965.266,
+        270785.583,
+        273616.0495,
+        276487.4835,
+        279346.639,
+        282202.509,
+        285074.3885,
+        287942.2855,
+        290856.018,
+        293774.0345,
+        296678.5145,
+        299603.6355,
+        302552.6575,
+        305492.9785,
+        308466.8605,
+        311392.581,
+        314347.538,
+        317319.4295,
+        320285.9785,
+        323301.7325,
+        326298.3235,
+        329301.3105,
+        332301.987,
+        335309.791,
+        338370.762,
+        341382.923,
+        344431.1265,
+        347464.1545,
+        350507.28,
+        353619.2345,
+        356631.2005,
+        359685.203,
+        362776.7845,
+        365886.488,
+        368958.2255,
+        372060.6825,
+        375165.4335,
+        378237.935,
+        381328.311,
+        384430.5225,
+        387576.425,
+        390683.242,
+        393839.648,
+        396977.8425,
+        400101.9805,
+        403271.296,
+        406409.8425,
+        409529.5485,
+        412678.7,
+        415847.423,
+        419020.8035,
+        422157.081,
+        425337.749,
+        428479.6165,
+        431700.902,
+        434893.1915,
+        438049.582,
+        441210.5415,
+        444379.2545,
+        447577.356,
+        450741.931,
+        453959.548,
+        457137.0935,
+        460329.846,
+        463537.4815,
+        466732.3345,
+        469960.5615,
+        473164.681,
+        476347.6345,
+        479496.173,
+        482813.1645,
+        486025.6995,
+        489249.4885,
+        492460.1945,
+        495675.8805,
+        498908.0075,
+        502131.802,
+        505374.3855,
+        508550.9915,
+        511806.7305,
+        515026.776,
+        518217.0005,
+        521523.9855,
+        524705.9855,
+        527950.997,
+        531210.0265,
+        534472.497,
+        537750.7315,
+        540926.922,
+        544207.094,
+        547429.4345,
+        550666.3745,
+        553975.3475,
+        557150.7185,
+        560399.6165,
+        563662.697,
+        566916.7395,
+        570146.1215,
+        573447.425,
+        576689.6245,
+        579874.5745,
+        583202.337,
+        586503.0255,
+        589715.635,
+        592910.161,
+        596214.3885,
+        599488.035,
+        602740.92,
+        605983.0685,
+        609248.67,
+        612491.3605,
+        615787.912,
+        619107.5245,
+        622307.9555,
+        625577.333,
+        628840.4385,
+        632085.2155,
+        635317.6135,
+        638691.7195,
+        641887.467,
+        645139.9405,
+        648441.546,
+        651666.252,
+        654941.845,
+    ],
+    &[
+        189084.0,
+        192250.913,
+        195456.774,
+        198696.946,
+        201977.762,
+        205294.444,
+        208651.754,
+        212042.099,
+        215472.269,
+        218941.91,
+        222443.912,
+        225996.845,
+        229568.199,
+        233193.568,
+        236844.457,
+        240543.233,
+        244279.475,
+        248044.27,
+        251854.588,
+        255693.2,
+        259583.619,
+        263494.621,
+        267445.385,
+        271454.061,
+        275468.769,
+        279549.456,
+        283646.446,
+        287788.198,
+        291966.099,
+        296181.164,
+        300431.469,
+        304718.618,
+        309024.004,
+        313393.508,
+        317760.803,
+        322209.731,
+        326675.061,
+        331160.627,
+        335654.47,
+        340241.442,
+        344841.833,
+        349467.132,
+        354130.629,
+        358819.432,
+        363574.626,
+        368296.587,
+        373118.482,
+        377914.93,
+        382782.301,
+        387680.669,
+        392601.981,
+        397544.323,
+        402529.115,
+        407546.018,
+        412593.658,
+        417638.657,
+        422762.865,
+        427886.169,
+        433017.167,
+        438213.273,
+        443441.254,
+        448692.421,
+        453937.533,
+        459239.049,
+        464529.569,
+        469910.083,
+        475274.03,
+        480684.473,
+        486070.26,
+        491515.237,
+        496995.651,
+        502476.617,
+        507973.609,
+        513497.19,
+        519083.233,
+        524726.509,
+        530305.505,
+        535945.728,
+        541584.404,
+        547274.055,
+        552967.236,
+        558667.862,
+        564360.216,
+        570128.148,
+        575965.08,
+        581701.952,
+        587532.523,
+        593361.144,
+        599246.128,
+        605033.418,
+        610958.779,
+        616837.117,
+        622772.818,
+        628672.04,
+        634675.369,
+        640574.831,
+        646585.739,
+        652574.547,
+        658611.217,
+        664642.684,
+        670713.914,
+        676737.681,
+        682797.313,
+        688837.897,
+        694917.874,
+        701009.882,
+        707173.648,
+        713257.254,
+        719415.392,
+        725636.761,
+        731710.697,
+        737906.209,
+        744103.074,
+        750313.39,
+        756504.185,
+        762712.579,
+        768876.985,
+        775167.859,
+        781359.0,
+        787615.959,
+        793863.597,
+        800245.477,
+        806464.582,
+        812785.294,
+        819005.925,
+        825403.057,
+        831676.197,
+        837936.284,
+        844266.968,
+        850642.711,
+        856959.756,
+        863322.774,
+        869699.931,
+        876102.478,
+        882355.787,
+        888694.463,
+        895159.952,
+        901536.143,
+        907872.631,
+        914293.672,
+        920615.14,
+        927130.974,
+        933409.404,
+        939922.178,
+        946331.47,
+        952745.93,
+        959209.264,
+        965590.224,
+        972077.284,
+        978501.961,
+        984953.19,
+        991413.271,
+        997817.479,
+        1004222.658,
+        1010725.676,
+        1017177.138,
+        1023612.529,
+        1030098.236,
+        1036493.719,
+        1043112.207,
+        1049537.036,
+        1056008.096,
+        1062476.184,
+        1068942.337,
+        1075524.95,
+        1081932.864,
+        1088426.025,
+        1094776.005,
+        1101327.448,
+        1107901.673,
+        1114423.639,
+        1120884.602,
+        1127324.923,
+        1133794.24,
+        1140328.886,
+        1146849.376,
+        1153346.682,
+        1159836.502,
+        1166478.703,
+        1172953.304,
+        1179391.502,
+        1185950.982,
+        1192544.052,
+        1198913.41,
+        1205430.994,
+        1212015.525,
+        1218674.042,
+        1225121.683,
+        1231551.101,
+        1238126.379,
+        1244673.795,
+        1251260.649,
+        1257697.86,
+        1264320.983,
+        1270736.319,
+        1277274.694,
+        1283804.95,
+        1290211.514,
+        1296858.568,
+        1303455.691,
+    ],
+];
+
+/// Bias-correction values per precision (p = 4..=18), paired with 
`RAW_ESTIMATE_DATA`.
+#[rustfmt::skip]
+pub(crate) static BIAS_DATA: [&[f64]; 15] = [
+    &[
+        10.0, 9.717, 9.207, 8.7896, 8.2882, 7.8204, 7.3772, 6.9342, 6.5202, 
6.161, 5.7722,
+        5.4636, 5.0396, 4.6766, 4.3566, 4.0454, 3.7936, 3.4856, 3.2666, 
2.9946, 2.766, 2.4692,
+        2.3638, 2.0764, 1.7864, 1.7602, 1.4814, 1.433, 1.2926, 1.0664, 
0.999600000000001,
+        0.7956, 0.5366, 0.589399999999998, 0.573799999999999, 
0.269799999999996,
+        0.368200000000002, 0.0544000000000011, 0.234200000000001, 
0.0108000000000033,
+        -0.203400000000002, -0.0701999999999998, -0.129600000000003, 
-0.364199999999997,
+        -0.480600000000003, -0.226999999999997, -0.322800000000001, 
-0.382599999999996,
+        -0.511200000000002, -0.669600000000003, -0.749400000000001, 
-0.500399999999999,
+        -0.617600000000003, -0.6922, -0.601599999999998, -0.416200000000003,
+        -0.338200000000001, -0.782600000000002, -0.648600000000002, 
-0.919800000000002,
+        -0.851799999999997, -0.962400000000002, -0.6402, -1.1922, -1.0256, 
-1.086,
+        -1.21899999999999, -0.819400000000002, -0.940600000000003, -1.1554, 
-1.2072, -1.1752,
+        -1.16759999999999, -1.14019999999999, -1.3754, -1.29859999999999, 
-1.607, -1.3292,
+        -1.7606,
+    ],
+    &[
+        22.0, 21.1194, 20.8208, 20.2318, 19.77, 19.2436, 18.7774, 18.2848, 
17.8224, 17.3742,
+        16.9336, 16.503, 16.0494, 15.6292, 15.2124, 14.798, 14.367, 13.9728, 
13.5944, 13.217,
+        12.8438, 12.3696, 12.0956, 11.7044, 11.324, 11.0668, 10.6698, 10.3644, 
10.049, 9.6918,
+        9.4146, 9.082, 8.687, 8.5398, 8.2462, 7.857, 7.6606, 7.4168, 7.1248, 
6.9222, 6.6804,
+        6.447, 6.3454, 5.9594, 5.7636, 5.5776, 5.331, 5.19, 4.9676, 4.7564, 
4.5314, 4.4442,
+        4.3708, 3.9774, 3.9624, 3.8796, 3.755, 3.472, 3.2076, 3.1024, 2.8908, 
2.7338, 2.7728,
+        2.629, 2.413, 2.3266, 2.1524, 2.2642, 2.1806, 2.0566, 1.9192, 1.7598, 
1.3516, 1.5802,
+        1.43859999999999, 1.49160000000001, 1.1524, 1.1892, 0.841399999999993,
+        0.879800000000003, 0.837599999999995, 0.469800000000006, 
0.765600000000006,
+        0.331000000000003, 0.591399999999993, 0.601200000000006, 
0.701599999999999,
+        0.558199999999999, 0.339399999999998, 0.354399999999998, 
0.491200000000006,
+        0.308000000000007, 0.355199999999996, -0.0254000000000048, 
0.205200000000005,
+        -0.272999999999996, 0.132199999999997, 0.394400000000005, 
-0.241200000000006,
+        0.242000000000004, 0.191400000000002, 0.253799999999998, 
-0.122399999999999,
+        -0.370800000000003, 0.193200000000004, -0.0848000000000013, 
0.0867999999999967,
+        -0.327200000000005, -0.285600000000002, 0.311400000000006, 
-0.128399999999999,
+        -0.754999999999995, -0.209199999999996, -0.293599999999998, 
-0.364000000000004,
+        -0.253600000000006, -0.821200000000005, -0.253600000000006, 
-0.510400000000004,
+        -0.383399999999995, -0.491799999999998, -0.220200000000006, 
-0.0972000000000008,
+        -0.557400000000001, -0.114599999999996, -0.295000000000002, 
-0.534800000000004,
+        0.346399999999988, -0.65379999999999, 0.0398000000000138, 
0.0341999999999985,
+        -0.995800000000003, -0.523400000000009, -0.489000000000004, 
-0.274799999999999,
+        -0.574999999999989, -0.482799999999997, 0.0571999999999946, 
-0.330600000000004,
+        -0.628800000000012, -0.140199999999993, -0.540600000000012, 
-0.445999999999998,
+        -0.599400000000003, -0.262599999999992, 0.163399999999996, 
-0.100599999999986,
+        -0.39500000000001, -1.06960000000001, -0.836399999999998, 
-0.753199999999993,
+        -0.412399999999991, -0.790400000000005, -0.29679999999999, 
-0.28540000000001,
+        -0.193000000000012, -0.0772000000000048, -0.962799999999987, 
-0.414800000000014,
+    ],
+    &[
+        45.0, 44.1902, 43.271, 42.8358, 41.8142, 41.2854, 40.317, 39.354, 
38.8924, 37.9436,
+        37.4596, 36.5262, 35.6248, 35.1574, 34.2822, 33.837, 32.9636, 32.074, 
31.7042, 30.7976,
+        30.4772, 29.6564, 28.7942, 28.5004, 27.686, 27.291, 26.5672, 25.8556, 
25.4982, 24.8204,
+        24.4252, 23.7744, 23.0786, 22.8344, 22.0294, 21.8098, 21.0794, 
20.5732, 20.1878,
+        19.5648, 19.2902, 18.6784, 18.3352, 17.8946, 17.3712, 17.0852, 16.499, 
16.2686,
+        15.6844, 15.2234, 14.9732, 14.3356, 14.2286, 13.7262, 13.3284, 
13.1048, 12.5962,
+        12.3562, 12.1272, 11.4184, 11.4974, 11.0822, 10.856, 10.48, 10.2834, 
10.0208, 9.637,
+        9.51739999999999, 9.05759999999999, 8.74760000000001, 
8.42700000000001, 8.1326, 8.2372,
+        8.2788, 7.6776, 7.79259999999999, 7.1952, 6.9564, 6.6454, 6.87, 6.5428,
+        6.19999999999999, 6.02940000000001, 5.62780000000001, 5.6782, 5.792, 
5.35159999999999,
+        5.28319999999999, 5.0394, 5.07480000000001, 4.49119999999999, 
4.84899999999999, 4.696,
+        4.54040000000001, 4.07300000000001, 4.37139999999999, 3.7216, 3.7328, 
3.42080000000001,
+        3.41839999999999, 3.94239999999999, 3.27719999999999, 3.411, 
3.13079999999999,
+        2.76900000000001, 2.92580000000001, 2.68279999999999, 2.75020000000001,
+        2.70599999999999, 2.3886, 3.01859999999999, 2.45179999999999, 
2.92699999999999,
+        2.41720000000001, 2.41139999999999, 2.03299999999999, 
2.51240000000001, 2.5564,
+        2.60079999999999, 2.41720000000001, 1.80439999999999, 1.99700000000001,
+        2.45480000000001, 1.8948, 2.2346, 2.30860000000001, 2.15479999999999, 
1.88419999999999,
+        1.6508, 0.677199999999999, 1.72540000000001, 1.4752, 1.72280000000001,
+        1.66139999999999, 1.16759999999999, 1.79300000000001, 1.00059999999999,
+        0.905200000000008, 0.659999999999997, 1.55879999999999, 1.1636, 
0.688199999999995,
+        0.712600000000009, 0.450199999999995, 1.1978, 0.975599999999986, 
0.165400000000005,
+        1.727, 1.19739999999999, -0.252600000000001, 1.13460000000001, 1.3048,
+        1.19479999999999, 0.313400000000001, 0.878999999999991, 
1.12039999999999,
+        0.853000000000009, 1.67920000000001, 0.856999999999999, 
0.448599999999999, 1.2362,
+        0.953399999999988, 1.02859999999998, 0.563199999999995, 
0.663000000000011,
+        0.723000000000013, 0.756599999999992, 0.256599999999992, 
-0.837600000000009,
+        0.620000000000005, 0.821599999999989, 0.216600000000028, 
0.205600000000004,
+        0.220199999999977, 0.372599999999977, 0.334400000000016, 
0.928400000000011,
+        0.972800000000007, 0.192400000000021, 0.487199999999973, 
-0.413000000000011,
+        0.807000000000016, 0.120600000000024, 0.769000000000005, 
0.870799999999974,
+        0.66500000000002, 0.118200000000002, 0.401200000000017, 
0.635199999999998,
+        0.135400000000004, 0.175599999999974, 1.16059999999999, 
0.34620000000001,
+        0.521400000000028, -0.586599999999976, -1.16480000000001, 
0.968399999999974,
+        0.836999999999989, 0.779600000000016, 0.985799999999983,
+    ],
+    &[
+        91.0, 89.4934, 87.9758, 86.4574, 84.9718, 83.4954, 81.5302, 80.0756, 
78.6374, 77.1782,
+        75.7888, 73.9522, 72.592, 71.2532, 69.9086, 68.5938, 66.9474, 65.6796, 
64.4394,
+        63.2176, 61.9768, 60.4214, 59.2528, 58.0102, 56.8658, 55.7278, 
54.3044, 53.1316,
+        52.093, 51.0032, 49.9092, 48.6306, 47.5294, 46.5756, 45.6508, 44.662, 
43.552, 42.3724,
+        41.617, 40.5754, 39.7872, 38.8444, 37.7988, 36.8606, 36.2118, 35.3566, 
34.4476,
+        33.5882, 32.6816, 32.0824, 31.0258, 30.6048, 29.4436, 28.7274, 27.957, 
27.147, 26.4364,
+        25.7592, 25.3386, 24.781, 23.8028, 23.656, 22.6544, 21.996, 21.4718, 
21.1544, 20.6098,
+        19.5956, 19.0616, 18.5758, 18.4878, 17.5244, 17.2146, 16.724, 15.8722, 
15.5198,
+        15.0414, 14.941, 14.9048, 13.87, 13.4304, 13.028, 12.4708, 12.37, 
12.0624, 11.4668,
+        11.5532, 11.4352, 11.2564, 10.2744, 10.2118, 9.74720000000002, 
10.1456, 9.2928,
+        8.75040000000001, 8.55279999999999, 8.97899999999998, 8.21019999999999,
+        8.18340000000001, 7.3494, 7.32499999999999, 7.66140000000001, 
6.90300000000002,
+        7.25439999999998, 6.9042, 7.21499999999997, 6.28640000000001, 
6.08139999999997, 6.6764,
+        6.30099999999999, 5.13900000000001, 5.65800000000002, 5.17320000000001,
+        4.59019999999998, 4.9538, 5.08280000000002, 4.92200000000003, 
4.99020000000002, 4.7328,
+        5.4538, 4.11360000000002, 4.22340000000003, 4.08780000000002, 
3.70800000000003,
+        4.15559999999999, 4.18520000000001, 3.63720000000001, 3.68220000000002,
+        3.77960000000002, 3.6078, 2.49160000000001, 3.13099999999997, 2.5376, 
3.19880000000001,
+        3.21100000000001, 2.4502, 3.52820000000003, 2.91199999999998, 
3.04480000000001, 2.7432,
+        2.85239999999999, 2.79880000000003, 2.78579999999999, 1.88679999999999,
+        2.98860000000002, 2.50639999999999, 1.91239999999999, 2.66160000000002,
+        2.46820000000002, 1.58199999999999, 1.30399999999997, 2.27379999999999,
+        2.68939999999998, 1.32900000000001, 3.10599999999999, 1.69080000000002,
+        2.13740000000001, 2.53219999999999, 1.88479999999998, 1.33240000000001,
+        1.45119999999997, 1.17899999999997, 2.44119999999998, 1.60659999999996,
+        2.16700000000003, 0.77940000000001, 2.37900000000002, 2.06700000000001,
+        1.46000000000004, 2.91160000000002, 1.69200000000001, 
0.954600000000028,
+        2.49300000000005, 2.2722, 1.33500000000004, 2.44899999999996, 
1.20140000000004,
+        3.07380000000001, 2.09739999999999, 2.85640000000001, 2.29960000000005,
+        2.40899999999999, 1.97040000000004, 0.809799999999996, 
1.65279999999996,
+        2.59979999999996, 0.95799999999997, 2.06799999999998, 2.32780000000002,
+        4.20159999999998, 1.96320000000003, 1.86400000000003, 1.42999999999995,
+        3.77940000000001, 1.27200000000005, 1.86440000000005, 2.20600000000002,
+        3.21900000000005, 1.5154, 2.61019999999996,
+    ],
+    &[
+        183.2152, 180.2454, 177.2096, 173.6652, 170.6312, 167.6822, 164.249, 
161.3296,
+        158.0038, 155.2074, 152.4612, 149.27, 146.5178, 143.4412, 140.8032, 
138.1634, 135.1688,
+        132.6074, 129.6946, 127.2664, 124.8228, 122.0432, 119.6824, 116.9464, 
114.6268,
+        112.2626, 109.8376, 107.4034, 104.8956, 102.8522, 100.7638, 98.3552, 
96.3556, 93.7526,
+        91.9292, 89.8954, 87.8198, 85.7668, 83.298, 81.6688, 79.9466, 77.9746, 
76.1672,
+        74.3474, 72.3028, 70.8912, 69.114, 67.4646, 65.9744, 64.4092, 62.6022, 
60.843, 59.5684,
+        58.1652, 56.5426, 55.4152, 53.5388, 52.3592, 51.1366, 49.486, 48.3918, 
46.5076, 45.509,
+        44.3834, 43.3498, 42.0668, 40.7346, 40.1228, 38.4528, 37.7, 36.644, 
36.0518, 34.5774,
+        33.9068, 32.432, 32.1666, 30.434, 29.6644, 28.4894, 27.6312, 26.3804, 
26.292,
+        25.5496000000001, 25.0234, 24.8206, 22.6146, 22.4188, 22.117, 20.6762, 
20.6576,
+        19.7864, 19.509, 18.5334, 17.9204, 17.772, 16.2924, 16.8654, 15.1836, 
15.745, 15.1316,
+        15.0386, 14.0136, 13.6342, 12.6196, 12.1866, 12.4281999999999, 11.3324,
+        10.4794000000001, 11.5038, 10.129, 9.52800000000002, 10.3203999999999,
+        9.46299999999997, 9.79280000000006, 9.12300000000005, 
8.74180000000001, 9.2192,
+        7.51020000000005, 7.60659999999996, 7.01840000000004, 7.22239999999999,
+        7.40139999999997, 6.76179999999999, 7.14359999999999, 5.65060000000005,
+        5.63779999999997, 5.76599999999996, 6.75139999999999, 5.57759999999996,
+        3.73220000000003, 5.8048, 5.63019999999995, 4.93359999999996, 
3.47979999999995,
+        4.33879999999999, 3.98940000000005, 3.81960000000004, 3.31359999999995,
+        3.23080000000004, 3.4588, 3.08159999999998, 3.4076, 3.00639999999999, 
2.38779999999997,
+        2.61900000000003, 1.99800000000005, 3.34820000000002, 2.95060000000001,
+        0.990999999999985, 2.11440000000005, 2.20299999999997, 
2.82219999999995,
+        2.73239999999998, 2.7826, 3.76660000000004, 2.26480000000004, 
2.31280000000004,
+        2.40819999999997, 2.75360000000001, 3.33759999999995, 2.71559999999999,
+        1.7478000000001, 1.42920000000004, 2.39300000000003, 2.22779999999989,
+        2.34339999999997, 0.87259999999992, 3.88400000000001, 1.80600000000004,
+        1.91759999999999, 1.16779999999994, 1.50320000000011, 2.52500000000009,
+        0.226400000000012, 2.31500000000005, 0.930000000000064, 
1.25199999999995,
+        2.14959999999996, 0.0407999999999902, 2.5447999999999, 
1.32960000000003,
+        0.197400000000016, 2.52620000000002, 3.33279999999991, 
-1.34300000000007,
+        0.422199999999975, 0.917200000000093, 1.12920000000008, 
1.46060000000011,
+        1.45779999999991, 2.8728000000001, 3.33359999999993, -1.34079999999994,
+        1.57680000000005, 0.363000000000056, 1.40740000000005, 
0.656600000000026,
+        0.801400000000058, -0.454600000000028, 1.51919999999996,
+    ],
+    &[
+        368.0, 361.8294, 355.2452, 348.6698, 342.1464, 336.2024, 329.8782, 
323.6598, 317.462,
+        311.2826, 305.7102, 299.7416, 293.9366, 288.1046, 282.285, 277.0668, 
271.306, 265.8448,
+        260.301, 254.9886, 250.2422, 244.8138, 239.7074, 234.7428, 229.8402, 
225.1664,
+        220.3534, 215.594, 210.6886, 205.7876, 201.65, 197.228, 192.8036, 
188.1666, 184.0818,
+        180.0824, 176.2574, 172.302, 168.1644, 164.0056, 160.3802, 156.7192, 
152.5234,
+        149.2084, 145.831, 142.485, 139.1112, 135.4764, 131.76, 129.3368, 
126.5538, 122.5058,
+        119.2646, 116.5902, 113.3818, 110.8998, 107.9532, 105.2062, 102.2798, 
99.4728, 96.9582,
+        94.3292, 92.171, 89.7809999999999, 87.5716, 84.7048, 82.5322, 79.875, 
78.3972, 75.3464,
+        73.7274, 71.2834, 70.1444, 68.4263999999999, 66.0166, 64.018, 
62.0437999999999,
+        60.3399999999999, 58.6856, 57.9836, 55.0311999999999, 
54.6769999999999, 52.3188,
+        51.4846, 49.4423999999999, 47.739, 46.1487999999999, 44.9202, 
43.4059999999999,
+        42.5342000000001, 41.2834, 38.8954000000001, 38.3286000000001, 
36.2146, 36.6684,
+        35.9946, 33.123, 33.4338, 31.7378000000001, 29.076, 28.9692, 27.4964, 
27.0998, 25.9864,
+        26.7754, 24.3208, 23.4838, 22.7388000000001, 24.0758000000001, 
21.9097999999999,
+        20.9728, 19.9228000000001, 19.9292, 16.617, 17.05, 18.2996000000001, 
15.6128000000001,
+        15.7392, 14.5174, 13.6322, 12.2583999999999, 13.3766000000001, 11.423, 
13.1232,
+        9.51639999999998, 10.5938000000001, 9.59719999999993, 8.12220000000002,
+        9.76739999999995, 7.50440000000003, 7.56999999999994, 6.70440000000008,
+        6.41419999999994, 6.71019999999999, 5.60940000000005, 4.65219999999999,
+        6.84099999999989, 3.4072000000001, 3.97859999999991, 3.32760000000007,
+        5.52160000000003, 3.31860000000006, 2.06940000000009, 4.35400000000004,
+        1.57500000000005, 0.280799999999999, 2.12879999999996, 
-0.214799999999968,
+        -0.0378000000000611, -0.658200000000079, 0.654800000000023, 
-0.0697999999999865,
+        0.858400000000074, -2.52700000000004, -2.1751999999999, 
-3.35539999999992,
+        -1.04019999999991, -0.651000000000067, -2.14439999999991, 
-1.96659999999997,
+        -3.97939999999994, -0.604400000000169, -3.08260000000018, 
-3.39159999999993,
+        -5.29640000000018, -5.38920000000007, -5.08759999999984, 
-4.69900000000007,
+        -5.23720000000003, -3.15779999999995, -4.97879999999986, 
-4.89899999999989,
+        -7.48880000000008, -5.94799999999987, -5.68060000000014, 
-6.67180000000008,
+        -4.70499999999993, -7.27779999999984, -4.6579999999999, 
-4.4362000000001,
+        -4.32139999999981, -5.18859999999995, -6.66879999999992, 
-6.48399999999992,
+        -5.1260000000002, -4.4032000000002, -6.13500000000022, 
-5.80819999999994,
+        -4.16719999999987, -4.15039999999999, -7.45600000000013, 
-7.24080000000004,
+        -9.83179999999993, -5.80420000000004, -8.6561999999999, 
-6.99940000000015,
+        -10.5473999999999, -7.34139999999979, -6.80999999999995, 
-6.29719999999998,
+        -6.23199999999997,
+    ],
+    &[
+        737.1256, 724.4234, 711.1064, 698.4732, 685.4636, 673.0644, 660.488, 
647.9654,
+        636.0832, 623.7864, 612.1992, 600.2176, 588.5228, 577.1716, 565.7752, 
554.899,
+        543.6126, 532.6492, 521.9474, 511.5214, 501.1064, 490.6364, 480.2468, 
470.4588,
+        460.3832, 451.0584, 440.8606, 431.3868, 422.5062, 413.1862, 404.463, 
395.339, 386.1936,
+        378.1292, 369.1854, 361.2908, 353.3324, 344.8518, 337.5204, 329.4854, 
321.9318,
+        314.552, 306.4658, 299.4256, 292.849, 286.152, 278.8956, 271.8792, 
265.118, 258.62,
+        252.5132, 245.9322, 239.7726, 233.6086, 227.5332, 222.5918, 216.4294, 
210.7662,
+        205.4106, 199.7338, 194.9012, 188.4486, 183.1556, 178.6338, 173.7312, 
169.6264,
+        163.9526, 159.8742, 155.8326, 151.1966, 147.5594, 143.07, 140.037, 
134.1804, 131.071,
+        127.4884, 124.0848, 120.2944, 117.333, 112.9626, 110.2902, 107.0814, 
103.0334,
+        99.4832000000001, 96.3899999999999, 93.7202000000002, 90.1714000000002,
+        87.2357999999999, 85.9346, 82.8910000000001, 80.0264000000002, 
78.3834000000002,
+        75.1543999999999, 73.8683999999998, 70.9895999999999, 69.4367999999999,
+        64.8701999999998, 65.0408000000002, 61.6738, 59.5207999999998, 
57.0158000000001,
+        54.2302, 53.0962, 50.4985999999999, 52.2588000000001, 47.3914, 
45.6244000000002,
+        42.8377999999998, 43.0072, 40.6516000000001, 40.2453999999998, 
35.2136, 36.4546,
+        33.7849999999999, 33.2294000000002, 32.4679999999998, 30.8670000000002,
+        28.6507999999999, 28.9099999999999, 27.5983999999999, 26.1619999999998,
+        24.5563999999999, 23.2328000000002, 21.9484000000002, 21.5902000000001,
+        21.3346000000001, 17.7031999999999, 20.6111999999998, 19.5545999999999,
+        15.7375999999999, 17.0720000000001, 16.9517999999998, 15.326, 
13.1817999999998,
+        14.6925999999999, 13.0859999999998, 13.2754, 10.8697999999999, 11.248, 
7.3768,
+        4.72339999999986, 7.97899999999981, 8.7503999999999, 7.68119999999999, 
9.7199999999998,
+        7.73919999999998, 5.6224000000002, 7.44560000000001, 6.6601999999998, 
5.9058,
+        4.00199999999995, 4.51699999999983, 4.68240000000014, 3.86220000000003,
+        5.13639999999987, 5.98500000000013, 2.47719999999981, 2.61999999999989,
+        1.62800000000016, 4.65000000000009, 0.225599999999758, 
0.831000000000131,
+        -0.359400000000278, 1.27599999999984, -2.92559999999958, 
-0.0303999999996449,
+        2.37079999999969, -2.0033999999996, 0.804600000000391, 
0.30199999999968,
+        1.1247999999996, -2.6880000000001, 0.0321999999996478, 
-1.18099999999959, -3.9402,
+        -1.47940000000017, -0.188400000000001, -2.10720000000038, 
-2.04159999999956,
+        -3.12880000000041, -4.16160000000036, -0.612799999999879, 
-3.48719999999958,
+        -8.17900000000009, -5.37780000000021, -4.01379999999972, 
-5.58259999999973,
+        -5.73719999999958, -7.66799999999967, -5.69520000000011, 
-1.1247999999996,
+        -5.58520000000044, -8.04560000000038, -4.64840000000004, 
-11.6468000000004,
+        -7.97519999999986, -5.78300000000036, -7.67420000000038, 
-10.6328000000003,
+        -9.81720000000041,
+    ],
+    &[
+        1476.0, 1449.6014, 1423.5802, 1397.7942, 1372.3042, 1347.2062, 
1321.8402, 1297.2292,
+        1272.9462, 1248.9926, 1225.3026, 1201.4252, 1178.0578, 1155.6092, 
1132.626, 1110.5568,
+        1088.527, 1066.5154, 1045.1874, 1024.3878, 1003.37, 982.1972, 
962.5728, 942.1012,
+        922.9668, 903.292, 884.0772, 864.8578, 846.6562, 828.041, 809.714, 
792.3112, 775.1806,
+        757.9854, 740.656, 724.346, 707.5154, 691.8378, 675.7448, 659.6722, 
645.5722, 630.1462,
+        614.4124, 600.8728, 585.898, 572.408, 558.4926, 544.4938, 531.6776, 
517.282, 505.7704,
+        493.1012, 480.7388, 467.6876, 456.1872, 445.5048, 433.0214, 420.806, 
411.409, 400.4144,
+        389.4294, 379.2286, 369.651, 360.6156, 350.337, 342.083, 332.1538, 
322.5094, 315.01,
+        305.6686, 298.1678, 287.8116, 280.9978, 271.9204, 265.3286, 257.5706, 
249.6014,
+        242.544, 235.5976, 229.583, 220.9438, 214.672, 208.2786, 201.8628, 
195.1834, 191.505,
+        186.1816, 178.5188, 172.2294, 167.8908, 161.0194, 158.052, 151.4588, 
148.1596,
+        143.4344, 138.5238, 133.13, 127.6374, 124.8162, 118.7894, 117.3984, 
114.6078, 109.0858,
+        105.1036, 103.6258, 98.6018000000004, 95.7618000000002, 
93.5821999999998,
+        88.5900000000001, 86.9992000000002, 82.8800000000001, 80.4539999999997,
+        74.6981999999998, 74.3644000000004, 73.2914000000001, 65.5709999999999,
+        66.9232000000002, 65.1913999999997, 62.5882000000001, 61.5702000000001,
+        55.7035999999998, 56.1764000000003, 52.7596000000003, 53.0302000000001,
+        49.0609999999997, 48.4694, 44.933, 46.0474000000004, 44.7165999999997,
+        41.9416000000001, 39.9207999999999, 35.6328000000003, 35.5276000000003,
+        33.1934000000001, 33.2371999999996, 33.3864000000003, 33.9228000000003,
+        30.2371999999996, 29.1373999999996, 25.2272000000003, 24.2942000000003,
+        19.8338000000003, 18.9005999999999, 23.0907999999999, 21.8544000000002,
+        19.5176000000001, 15.4147999999996, 16.9314000000004, 18.6737999999996,
+        12.9877999999999, 14.3688000000002, 12.0447999999997, 15.5219999999999,
+        12.5299999999997, 14.5940000000001, 14.3131999999996, 9.45499999999993,
+        12.9441999999999, 3.91139999999996, 13.1373999999996, 5.44720000000052,
+        9.82779999999912, 7.87279999999919, 3.67760000000089, 5.46980000000076,
+        5.55099999999948, 5.65979999999945, 3.89439999999922, 3.1275999999998,
+        5.65140000000065, 6.3062000000009, 3.90799999999945, 1.87060000000019,
+        5.17020000000048, 2.46680000000015, 0.770000000000437, 
-3.72340000000077,
+        1.16400000000067, 8.05340000000069, 0.135399999999208, 
2.15940000000046,
+        0.766999999999825, 1.0594000000001, 3.15500000000065, 
-0.287399999999252,
+        2.37219999999979, -2.86620000000039, -1.63199999999961, 
-2.22979999999916,
+        -0.15519999999924, -1.46039999999994, -0.262199999999211, 
-2.34460000000036,
+        -2.8078000000005, -3.22179999999935, -5.60159999999996, 
-8.42200000000048,
+        -9.43740000000071, 0.161799999999857, -10.4755999999998, 
-10.0823999999993,
+    ],
+    &[
+        2953.0, 2900.4782, 2848.3568, 2796.3666, 2745.324, 2694.9598, 
2644.648, 2595.539,
+        2546.1474, 2498.2576, 2450.8376, 2403.6076, 2357.451, 2311.38, 
2266.4104, 2221.5638,
+        2176.9676, 2134.193, 2090.838, 2048.8548, 2007.018, 1966.1742, 
1925.4482, 1885.1294,
+        1846.4776, 1807.4044, 1768.8724, 1731.3732, 1693.4304, 1657.5326, 
1621.949, 1586.5532,
+        1551.7256, 1517.6182, 1483.5186, 1450.4528, 1417.865, 1385.7164, 
1352.6828, 1322.6708,
+        1291.8312, 1260.9036, 1231.476, 1201.8652, 1173.6718, 1145.757, 
1119.2072, 1092.2828,
+        1065.0434, 1038.6264, 1014.3192, 988.5746, 965.0816, 940.1176, 
917.9796, 894.5576,
+        871.1858, 849.9144, 827.1142, 805.0818, 783.9664, 763.9096, 742.0816, 
724.3962,
+        706.3454, 688.018, 667.4214, 650.3106, 633.0686, 613.8094, 597.818, 
581.4248, 563.834,
+        547.363, 531.5066, 520.455400000001, 505.583199999999, 488.366, 
476.480799999999,
+        459.7682, 450.0522, 434.328799999999, 423.952799999999, 
408.727000000001,
+        399.079400000001, 387.252200000001, 373.987999999999, 
360.852000000001, 351.6394,
+        339.642, 330.902400000001, 322.661599999999, 311.662200000001, 
301.3254, 291.7484,
+        279.939200000001, 276.7508, 263.215200000001, 254.811400000001, 
245.5494,
+        242.306399999999, 234.8734, 223.787200000001, 217.7156, 212.0196, 
200.793, 195.9748,
+        189.0702, 182.449199999999, 177.2772, 170.2336, 164.741, 
158.613600000001, 155.311,
+        147.5964, 142.837, 137.3724, 132.0162, 130.0424, 121.9804, 
120.451800000001, 114.8968,
+        111.585999999999, 105.933199999999, 101.705, 98.5141999999996, 
95.0488000000005,
+        89.7880000000005, 91.4750000000004, 83.7764000000006, 80.9698000000008,
+        72.8574000000008, 73.1615999999995, 67.5838000000003, 62.6263999999992,
+        63.2638000000006, 66.0977999999996, 52.0843999999997, 58.9956000000002,
+        47.0912000000008, 46.4956000000002, 48.4383999999991, 
47.1082000000006, 43.2392,
+        37.2759999999998, 40.0283999999992, 35.1864000000005, 
35.8595999999998, 32.0998,
+        28.027, 23.6694000000007, 33.8266000000003, 26.3736000000008, 
27.2008000000005,
+        21.3245999999999, 26.4115999999995, 23.4521999999997, 19.5013999999992,
+        19.8513999999996, 10.7492000000002, 18.6424000000006, 13.1265999999996,
+        18.2436000000016, 6.71860000000015, 3.39459999999963, 6.33759999999893,
+        7.76719999999841, 0.813999999998487, 3.82819999999992, 
0.826199999999517,
+        8.07440000000133, -1.59080000000176, 5.01780000000144, 
0.455399999998917,
+        -0.24199999999837, 0.174800000000687, -9.07640000000174, 
-4.20160000000033,
+        -3.77520000000004, -4.75179999999818, -5.3724000000002, 
-8.90680000000066,
+        -6.10239999999976, -5.74120000000039, -9.95339999999851, 
-3.86339999999836,
+        -13.7304000000004, -16.2710000000006, -7.51359999999841, 
-3.30679999999847,
+        -13.1339999999982, -10.0551999999989, -6.72019999999975, 
-8.59660000000076,
+        -10.9307999999983, -1.8775999999998, -4.82259999999951, -13.7788, 
-21.6470000000008,
+        -10.6735999999983, -15.7799999999988,
+    ],
+    &[
+        5907.5052, 5802.2672, 5697.347, 5593.5794, 5491.2622, 5390.5514, 
5290.3376, 5191.6952,
+        5093.5988, 4997.3552, 4902.5972, 4808.3082, 4715.5646, 4624.109, 
4533.8216, 4444.4344,
+        4356.3802, 4269.2962, 4183.3784, 4098.292, 4014.79, 3932.4574, 
3850.6036, 3771.2712,
+        3691.7708, 3615.099, 3538.1858, 3463.4746, 3388.8496, 3315.6794, 
3244.5448, 3173.7516,
+        3103.3106, 3033.6094, 2966.5642, 2900.794, 2833.7256, 2769.81, 
2707.3196, 2644.0778,
+        2583.9916, 2523.4662, 2464.124, 2406.073, 2347.0362, 2292.1006, 
2238.1716, 2182.7514,
+        2128.4884, 2077.1314, 2025.037, 1975.3756, 1928.933, 1879.311, 
1831.0006, 1783.2144,
+        1738.3096, 1694.5144, 1649.024, 1606.847, 1564.7528, 1525.3168, 
1482.5372, 1443.9668,
+        1406.5074, 1365.867, 1329.2186, 1295.4186, 1257.9716, 1225.339, 
1193.2972, 1156.3578,
+        1125.8686, 1091.187, 1061.4094, 1029.4188, 1000.9126, 972.3272, 
944.004199999999,
+        915.7592, 889.965, 862.834200000001, 840.4254, 812.598399999999, 
785.924200000001,
+        763.050999999999, 741.793799999999, 721.466, 699.040799999999, 
677.997200000002,
+        649.866999999998, 634.911800000002, 609.8694, 591.981599999999, 
570.2922,
+        557.129199999999, 538.3858, 521.872599999999, 502.951400000002, 
495.776399999999,
+        475.171399999999, 459.751, 439.995200000001, 426.708999999999, 
413.7016, 402.3868,
+        387.262599999998, 372.0524, 357.050999999999, 342.5098, 
334.849200000001,
+        322.529399999999, 311.613799999999, 295.848000000002, 289.273000000001,
+        274.093000000001, 263.329600000001, 251.389599999999, 245.7392, 
231.9614, 229.7952,
+        217.155200000001, 208.9588, 199.016599999999, 190.839199999999, 
180.6976,
+        176.272799999999, 166.976999999999, 162.5252, 151.196400000001, 
149.386999999999,
+        133.981199999998, 130.0586, 130.164000000001, 122.053400000001, 
110.7428, 108.1276,
+        106.232400000001, 100.381600000001, 98.7668000000012, 86.6440000000002,
+        79.9768000000004, 82.4722000000002, 68.7026000000005, 70.1186000000016,
+        71.9948000000004, 58.998599999999, 59.0492000000013, 56.9818000000014,
+        47.5338000000011, 42.9928, 51.1591999999982, 37.2740000000013, 
42.7220000000016,
+        31.3734000000004, 26.8090000000011, 25.8934000000008, 26.5286000000015,
+        29.5442000000003, 19.3503999999994, 26.0760000000009, 17.9527999999991,
+        14.8419999999969, 10.4683999999979, 8.65899999999965, 9.86720000000059,
+        4.34139999999752, -0.907800000000861, -3.32080000000133, 
-0.936199999996461,
+        -11.9916000000012, -8.87000000000262, -6.33099999999831, 
-11.3366000000024,
+        -15.9207999999999, -9.34659999999712, -15.5034000000014, 
-19.2097999999969,
+        -15.357799999998, -28.2235999999975, -30.6898000000001, 
-19.3271999999997,
+        -25.6083999999973, -24.409599999999, -13.6385999999984, 
-33.4473999999973,
+        -32.6949999999997, -28.9063999999998, -31.7483999999968, 
-32.2935999999972,
+        -35.8329999999987, -47.620600000002, -39.0855999999985, 
-33.1434000000008,
+        -46.1371999999974, -37.5892000000022, -46.8164000000033, 
-47.3142000000007,
+        -60.2914000000019, -37.7575999999972,
+    ],
+    &[
+        11816.475, 11605.0046, 11395.3792, 11188.7504, 10984.1814, 10782.0086, 
10582.0072,
+        10384.503, 10189.178, 9996.2738, 9806.0344, 9617.9798, 9431.394, 
9248.7784, 9067.6894,
+        8889.6824, 8712.9134, 8538.8624, 8368.4944, 8197.7956, 8031.8916, 
7866.6316, 7703.733,
+        7544.5726, 7386.204, 7230.666, 7077.8516, 6926.7886, 6778.6902, 
6631.9632, 6487.304,
+        6346.7486, 6206.4408, 6070.202, 5935.2576, 5799.924, 5671.0324, 
5541.9788, 5414.6112,
+        5290.0274, 5166.723, 5047.6906, 4929.162, 4815.1406, 4699.127, 
4588.5606, 4477.7394,
+        4369.4014, 4264.2728, 4155.9224, 4055.581, 3955.505, 3856.9618, 
3761.3828, 3666.9702,
+        3575.7764, 3482.4132, 3395.0186, 3305.8852, 3221.415, 3138.6024, 
3056.296, 2970.4494,
+        2896.1526, 2816.8008, 2740.2156, 2670.497, 2594.1458, 2527.111, 
2460.8168, 2387.5114,
+        2322.9498, 2260.6752, 2194.2686, 2133.7792, 2074.767, 2015.204, 
1959.4226, 1898.6502,
+        1850.006, 1792.849, 1741.4838, 1687.9778, 1638.1322, 1589.3266, 
1543.1394, 1496.8266,
+        1447.8516, 1402.7354, 1361.9606, 1327.0692, 1285.4106, 1241.8112, 
1201.6726, 1161.973,
+        1130.261, 1094.2036, 1048.2036, 1020.6436, 990.901400000002, 
961.199800000002,
+        924.769800000002, 899.526400000002, 872.346400000002, 834.375, 
810.432000000001,
+        780.659800000001, 756.013800000001, 733.479399999997, 
707.923999999999, 673.858,
+        652.222399999999, 636.572399999997, 615.738599999997, 586.696400000001,
+        564.147199999999, 541.679600000003, 523.943599999999, 505.714599999999,
+        475.729599999999, 461.779600000002, 449.750800000002, 
439.020799999998, 412.7886,
+        400.245600000002, 383.188199999997, 362.079599999997, 357.533799999997,
+        334.319000000003, 327.553399999997, 308.559399999998, 291.270199999999,
+        279.351999999999, 271.791400000002, 252.576999999997, 247.482400000001,
+        236.174800000001, 218.774599999997, 220.155200000001, 208.794399999999,
+        201.223599999998, 182.995600000002, 185.5268, 164.547400000003, 
176.5962,
+        150.689599999998, 157.8004, 138.378799999999, 134.021200000003, 
117.614399999999,
+        108.194000000003, 97.0696000000025, 89.6042000000016, 95.6030000000028,
+        84.7810000000027, 72.635000000002, 77.3482000000004, 59.4907999999996,
+        55.5875999999989, 50.7346000000034, 61.3916000000027, 50.9149999999936,
+        39.0384000000049, 58.9395999999979, 29.633600000001, 28.2032000000036,
+        26.0078000000067, 17.0387999999948, 9.22000000000116, 13.8387999999977,
+        8.07240000000456, 14.1549999999988, 15.3570000000036, 3.42660000000615,
+        6.24820000000182, -2.96940000000177, -8.79940000000352, 
-5.97860000000219,
+        -14.4048000000039, -3.4143999999942, -13.0148000000045, 
-11.6977999999945,
+        -25.7878000000055, -22.3185999999987, -24.409599999999, 
-31.9756000000052,
+        -18.9722000000038, -22.8678000000073, -30.8972000000067, 
-32.3715999999986,
+        -22.3907999999938, -43.6720000000059, -35.9038, -39.7492000000057, 
-54.1641999999993,
+        -45.2749999999942, -42.2989999999991, -44.1089999999967, 
-64.3564000000042,
+        -49.9551999999967, -42.6116000000038,
+    ],
+    &[
+        23634.0036, 23210.8034, 22792.4744, 22379.1524, 21969.7928, 21565.326, 
21165.3532,
+        20770.2806, 20379.9892, 19994.7098, 19613.318, 19236.799, 18865.4382, 
18498.8244,
+        18136.5138, 17778.8668, 17426.2344, 17079.32, 16734.778, 16397.2418, 
16063.3324,
+        15734.0232, 15409.731, 15088.728, 14772.9896, 14464.1402, 14157.5588, 
13855.5958,
+        13559.3296, 13264.9096, 12978.326, 12692.0826, 12413.8816, 12137.3192, 
11870.2326,
+        11602.5554, 11340.3142, 11079.613, 10829.5908, 10583.5466, 10334.0344, 
10095.5072,
+        9859.694, 9625.2822, 9395.7862, 9174.0586, 8957.3164, 8738.064, 
8524.155, 8313.7396,
+        8116.9168, 7913.542, 7718.4778, 7521.65, 7335.5596, 7154.2906, 
6968.7396, 6786.3996,
+        6613.236, 6437.406, 6270.6598, 6107.7958, 5945.7174, 5787.6784, 
5635.5784, 5482.308,
+        5337.9784, 5190.0864, 5045.9158, 4919.1386, 4771.817, 4645.7742, 
4518.4774, 4385.5454,
+        4262.6622, 4142.74679999999, 4015.5318, 3897.9276, 3790.7764, 
3685.13800000001,
+        3573.6274, 3467.9706, 3368.61079999999, 3271.5202, 3170.3848, 
3076.4656,
+        2982.38400000001, 2888.4664, 2806.4868, 2711.9564, 2634.1434, 
2551.3204, 2469.7662,
+        2396.61139999999, 2318.9902, 2243.8658, 2171.9246, 2105.01360000001, 
2028.8536,
+        1960.9952, 1901.4096, 1841.86079999999, 1777.54700000001, 1714.5802, 
1654.65059999999,
+        1596.311, 1546.2016, 1492.3296, 1433.8974, 1383.84600000001, 
1339.4152, 1293.5518,
+        1245.8686, 1193.50659999999, 1162.27959999999, 1107.19439999999, 
1069.18060000001,
+        1035.09179999999, 999.679000000004, 957.679999999993, 925.300199999998,
+        888.099400000006, 848.638600000006, 818.156400000007, 796.748399999997,
+        752.139200000005, 725.271200000003, 692.216, 671.633600000001, 
647.939799999993,
+        621.670599999998, 575.398799999995, 561.226599999995, 532.237999999998,
+        521.787599999996, 483.095799999996, 467.049599999998, 465.286399999997,
+        415.548599999995, 401.047399999996, 380.607999999993, 377.362599999993,
+        347.258799999996, 338.371599999999, 310.096999999994, 301.409199999995,
+        276.280799999993, 265.586800000005, 258.994399999996, 223.915999999997,
+        215.925399999993, 213.503800000006, 191.045400000003, 166.718200000003,
+        166.259000000005, 162.941200000001, 148.829400000002, 141.645999999993,
+        123.535399999993, 122.329800000007, 89.473399999988, 80.1962000000058,
+        77.5457999999926, 59.1056000000099, 83.3509999999951, 52.2906000000075,
+        36.3979999999865, 40.6558000000077, 42.0003999999899, 19.6630000000005,
+        19.7153999999864, -8.38539999999921, -0.692799999989802, 
0.854800000000978,
+        3.23219999999856, -3.89040000000386, -5.25880000001052, 
-24.9052000000083,
+        -22.6837999999989, -26.4286000000138, -34.997000000003, 
-37.0216000000073,
+        -43.430400000012, -58.2390000000014, -68.8034000000043, 
-56.9245999999985,
+        -57.8583999999973, -77.3097999999882, -73.2793999999994, 
-81.0738000000129,
+        -87.4530000000086, -65.0254000000132, -57.296399999992, 
-96.2746000000043, -103.25,
+        -96.081600000005, -91.5542000000132, -102.465200000006, 
-107.688599999994,
+        -101.458000000013, -109.715800000005,
+    ],
+    &[
+        47270.0, 46423.3584, 45585.7074, 44757.152, 43938.8416, 43130.9514, 
42330.03,
+        41540.407, 40759.6348, 39988.206, 39226.5144, 38473.2096, 37729.795, 
36997.268,
+        36272.6448, 35558.665, 34853.0248, 34157.4472, 33470.5204, 32793.5742, 
32127.0194,
+        31469.4182, 30817.6136, 30178.6968, 29546.8908, 28922.8544, 28312.271, 
27707.0924,
+        27114.0326, 26526.692, 25948.6336, 25383.7826, 24823.5998, 24272.2974, 
23732.2572,
+        23201.4976, 22674.2796, 22163.6336, 21656.515, 21161.7362, 20669.9368, 
20189.4424,
+        19717.3358, 19256.3744, 18795.9638, 18352.197, 17908.5738, 17474.391, 
17052.918,
+        16637.2236, 16228.4602, 15823.3474, 15428.6974, 15043.0284, 
14667.6278, 14297.4588,
+        13935.2882, 13578.5402, 13234.6032, 12882.1578, 12548.0728, 12219.231, 
11898.0072,
+        11587.2626, 11279.9072, 10973.5048, 10678.5186, 10392.4876, 
10105.2556, 9825.766,
+        9562.5444, 9294.2222, 9038.2352, 8784.848, 8533.2644, 8301.7776, 
8058.30859999999,
+        7822.94579999999, 7599.11319999999, 7366.90779999999, 7161.217, 
6957.53080000001,
+        6736.212, 6548.21220000001, 6343.06839999999, 6156.28719999999, 
5975.15419999999,
+        5791.75719999999, 5621.32019999999, 5451.66, 5287.61040000001, 
5118.09479999999,
+        4957.288, 4798.4246, 4662.17559999999, 4512.05900000001, 
4364.68539999999,
+        4220.77720000001, 4082.67259999999, 3957.19519999999, 
3842.15779999999, 3699.3328,
+        3583.01180000001, 3473.8964, 3338.66639999999, 3233.55559999999, 
3117.799, 3008.111,
+        2909.69140000001, 2814.86499999999, 2719.46119999999, 2624.742, 
2532.46979999999,
+        2444.7886, 2370.1868, 2272.45259999999, 2196.19260000001, 
2117.90419999999, 2023.2972,
+        1969.76819999999, 1885.58979999999, 1833.2824, 1733.91200000001, 
1682.54920000001,
+        1604.57980000001, 1556.11240000001, 1491.3064, 1421.71960000001, 
1371.22899999999,
+        1322.1324, 1264.7892, 1196.23920000001, 1143.8474, 1088.67240000001, 
1073.60380000001,
+        1023.11660000001, 959.036400000012, 927.433199999999, 906.792799999996,
+        853.433599999989, 841.873800000001, 791.1054, 756.899999999994, 
704.343200000003,
+        672.495599999995, 622.790399999998, 611.254799999995, 567.283200000005,
+        519.406599999988, 519.188400000014, 495.312800000014, 451.350799999986,
+        443.973399999988, 431.882199999993, 392.027000000002, 380.924200000009,
+        345.128999999986, 298.901400000002, 287.771999999997, 272.625, 
247.253000000026,
+        222.490600000019, 223.590000000026, 196.407599999977, 176.425999999978,
+        134.725199999986, 132.4804, 110.445599999977, 86.7939999999944, 
56.7038000000175,
+        64.915399999998, 38.3726000000024, 37.1606000000029, 46.170999999973, 
49.1716000000015,
+        15.3362000000197, 6.71639999997569, -34.8185999999987, 
-39.4476000000141,
+        12.6830000000191, -12.3331999999937, -50.6565999999875, 
-59.9538000000175,
+        -65.1054000000004, -70.7576000000117, -106.325200000021, 
-126.852200000023,
+        -110.227599999984, -132.885999999999, -113.897200000007, 
-142.713800000027,
+        -151.145399999979, -150.799200000009, -177.756200000003, 
-156.036399999983,
+        -182.735199999996, -177.259399999981, -198.663600000029, 
-174.577600000019,
+        -193.84580000001,
+    ],
+    &[
+        94541.0, 92848.811, 91174.019, 89517.558, 87879.9705, 86262.7565, 
84663.5125,
+        83083.7435, 81521.7865, 79977.272, 78455.9465, 76950.219, 75465.432, 
73994.152,
+        72546.71, 71115.2345, 69705.6765, 68314.937, 66944.2705, 65591.255, 
64252.9485,
+        62938.016, 61636.8225, 60355.592, 59092.789, 57850.568, 56624.518, 
55417.343,
+        54231.1415, 53067.387, 51903.526, 50774.649, 49657.6415, 48561.05, 
47475.7575,
+        46410.159, 45364.852, 44327.053, 43318.4005, 42325.6165, 41348.4595, 
40383.6265,
+        39436.77, 38509.502, 37594.035, 36695.939, 35818.6895, 34955.691, 
34115.8095,
+        33293.949, 32465.0775, 31657.6715, 30877.2585, 30093.78, 29351.3695, 
28594.1365,
+        27872.115, 27168.7465, 26477.076, 25774.541, 25106.5375, 24452.5135, 
23815.5125,
+        23174.0655, 22555.2685, 21960.2065, 21376.3555, 20785.1925, 20211.517, 
19657.0725,
+        19141.6865, 18579.737, 18081.3955, 17578.995, 17073.44, 16608.335, 
16119.911,
+        15651.266, 15194.583, 14749.0495, 14343.4835, 13925.639, 13504.509, 
13099.3885,
+        12691.2855, 12328.018, 11969.0345, 11596.5145, 11245.6355, 10917.6575, 
10580.9785,
+        10277.8605, 9926.58100000001, 9605.538, 9300.42950000003, 
8989.97850000003,
+        8728.73249999998, 8448.3235, 8175.31050000002, 7898.98700000002, 
7629.79100000003,
+        7413.76199999999, 7149.92300000001, 6921.12650000001, 6677.1545, 
6443.28000000003,
+        6278.23450000002, 6014.20049999998, 5791.20299999998, 5605.78450000001,
+        5438.48800000001, 5234.2255, 5059.6825, 4887.43349999998, 4682.935, 
4496.31099999999,
+        4322.52250000002, 4191.42499999999, 4021.24200000003, 3900.64799999999,
+        3762.84250000003, 3609.98050000001, 3502.29599999997, 3363.84250000003,
+        3206.54849999998, 3079.70000000001, 2971.42300000001, 2867.80349999998,
+        2727.08100000001, 2630.74900000001, 2496.6165, 2440.902, 
2356.19150000002,
+        2235.58199999999, 2120.54149999999, 2012.25449999998, 1933.35600000003,
+        1820.93099999998, 1761.54800000001, 1663.09350000002, 1578.84600000002,
+        1509.48149999999, 1427.3345, 1379.56150000001, 1306.68099999998, 
1212.63449999999,
+        1084.17300000001, 1124.16450000001, 1060.69949999999, 1007.48849999998,
+        941.194499999983, 879.880500000028, 836.007500000007, 782.802000000025,
+        748.385499999975, 647.991500000004, 626.730500000005, 570.776000000013,
+        484.000500000024, 513.98550000001, 418.985499999952, 386.996999999974,
+        370.026500000036, 355.496999999974, 356.731499999994, 255.92200000002,
+        259.094000000041, 205.434499999974, 165.374500000034, 197.347500000033,
+        95.718499999959, 67.6165000000037, 54.6970000000438, 31.7395000000251,
+        -15.8784999999916, 8.42500000004657, -26.3754999999655, 
-118.425500000012,
+        -66.6629999999423, -42.9745000000112, -107.364999999991, 
-189.839000000036,
+        -162.611499999999, -164.964999999967, -189.079999999958, 
-223.931499999948,
+        -235.329999999958, -269.639500000048, -249.087999999989, 
-206.475499999942,
+        -283.04449999996, -290.667000000016, -304.561499999953, 
-336.784499999951,
+        -380.386500000022, -283.280499999993, -364.533000000054, 
-389.059499999974,
+        -364.454000000027, -415.748000000021, -417.155000000028,
+    ],
+    &[
+        189083.0, 185696.913, 182348.774, 179035.946, 175762.762, 172526.444, 
169329.754,
+        166166.099, 163043.269, 159958.91, 156907.912, 153906.845, 150924.199, 
147996.568,
+        145093.457, 142239.233, 139421.475, 136632.27, 133889.588, 131174.2, 
128511.619,
+        125868.621, 123265.385, 120721.061, 118181.769, 115709.456, 
113252.446, 110840.198,
+        108465.099, 106126.164, 103823.469, 101556.618, 99308.004, 97124.508, 
94937.803,
+        92833.731, 90745.061, 88677.627, 86617.47, 84650.442, 82697.833, 
80769.132, 78879.629,
+        77014.432, 75215.626, 73384.587, 71652.482, 69895.93, 68209.301, 
66553.669, 64921.981,
+        63310.323, 61742.115, 60205.018, 58698.658, 57190.657, 55760.865, 
54331.169, 52908.167,
+        51550.273, 50225.254, 48922.421, 47614.533, 46362.049, 45098.569, 
43926.083, 42736.03,
+        41593.473, 40425.26, 39316.237, 38243.651, 37170.617, 36114.609, 
35084.19, 34117.233,
+        33206.509, 32231.505, 31318.728, 30403.404, 29540.0550000001, 
28679.236, 27825.862,
+        26965.216, 26179.148, 25462.08, 24645.952, 23922.523, 23198.144, 
22529.128,
+        21762.4179999999, 21134.779, 20459.117, 19840.818, 19187.04, 
18636.3689999999,
+        17982.831, 17439.7389999999, 16874.547, 16358.2169999999, 15835.684, 
15352.914,
+        14823.681, 14329.313, 13816.897, 13342.874, 12880.882, 12491.648, 
12021.254, 11625.392,
+        11293.7610000001, 10813.697, 10456.209, 10099.074, 9755.39000000001, 
9393.18500000006,
+        9047.57900000003, 8657.98499999999, 8395.85900000005, 8033.0, 
7736.95900000003,
+        7430.59699999995, 7258.47699999996, 6924.58200000005, 6691.29399999999,
+        6357.92500000005, 6202.05700000003, 5921.19700000004, 5628.28399999999,
+        5404.96799999999, 5226.71100000001, 4990.75600000005, 4799.77399999998,
+        4622.93099999998, 4472.478, 4171.78700000001, 3957.46299999999, 
3868.95200000005,
+        3691.14300000004, 3474.63100000005, 3341.67200000002, 3109.14000000001,
+        3071.97400000005, 2796.40399999998, 2756.17799999996, 2611.46999999997,
+        2471.93000000005, 2382.26399999997, 2209.22400000005, 2142.28399999999,
+        2013.96100000001, 1911.18999999994, 1818.27099999995, 1668.47900000005,
+        1519.65800000005, 1469.67599999998, 1367.13800000004, 1248.52899999998,
+        1181.23600000003, 1022.71900000004, 1088.20700000005, 959.03600000008,
+        876.095999999903, 791.183999999892, 703.337000000058, 731.949999999953,
+        586.86400000006, 526.024999999907, 323.004999999888, 320.448000000091,
+        340.672999999952, 309.638999999966, 216.601999999955, 102.922999999952,
+        19.2399999999907, -0.114000000059605, -32.6240000000689, 
-89.3179999999702,
+        -153.497999999905, -64.2970000000205, -143.695999999996, 
-259.497999999905,
+        -253.017999999924, -213.948000000091, -397.590000000084, 
-434.006000000052,
+        -403.475000000093, -297.958000000101, -404.317000000039, 
-528.898999999976,
+        -506.621000000043, -513.205000000075, -479.351000000024, 
-596.139999999898,
+        -527.016999999993, -664.681000000099, -680.306000000099, 
-704.050000000047,
+        -850.486000000034, -757.43200000003, -713.308999999892,
+    ],
+];
diff --git a/native/spark-expr/src/agg_funcs/mod.rs 
b/native/spark-expr/src/agg_funcs/mod.rs
index 79020dead2..9f7d0f5c07 100644
--- a/native/spark-expr/src/agg_funcs/mod.rs
+++ b/native/spark-expr/src/agg_funcs/mod.rs
@@ -20,6 +20,8 @@ mod avg;
 mod avg_decimal;
 mod correlation;
 mod covariance;
+mod hll_plus_plus;
+mod hll_plus_plus_const;
 mod percentile;
 mod quantile_summaries;
 mod stddev;
@@ -33,6 +35,7 @@ pub use avg::Avg;
 pub use avg_decimal::AvgDecimal;
 pub use correlation::Correlation;
 pub use covariance::Covariance;
+pub use hll_plus_plus::{hllpp_precision, HllPlusPlus};
 pub use percentile::SparkPercentile;
 pub use stddev::Stddev;
 pub use sum_decimal::SumDecimal;
diff --git a/native/spark-expr/src/hash_funcs/mod.rs 
b/native/spark-expr/src/hash_funcs/mod.rs
index 67014f5615..c6eba4a46b 100644
--- a/native/spark-expr/src/hash_funcs/mod.rs
+++ b/native/spark-expr/src/hash_funcs/mod.rs
@@ -20,4 +20,5 @@ pub(super) mod utils;
 mod xxhash64;
 
 pub use murmur3::spark_murmur3_hash;
+pub(crate) use xxhash64::create_xxhash64_hashes;
 pub use xxhash64::spark_xxhash64;
diff --git a/native/spark-expr/src/hash_funcs/xxhash64.rs 
b/native/spark-expr/src/hash_funcs/xxhash64.rs
index caf0b490ed..c9d0f93ef8 100644
--- a/native/spark-expr/src/hash_funcs/xxhash64.rs
+++ b/native/spark-expr/src/hash_funcs/xxhash64.rs
@@ -121,7 +121,7 @@ fn create_xxhash64_hashes_dictionary<K: 
ArrowDictionaryKeyType>(
 ///
 /// The number of rows to hash is determined by `hashes_buffer.len()`.
 /// `hashes_buffer` should be pre-sized appropriately
-fn create_xxhash64_hashes<'a>(
+pub(crate) fn create_xxhash64_hashes<'a>(
     arrays: &[ArrayRef],
     hashes_buffer: &'a mut [u64],
 ) -> Result<&'a mut [u64]> {
diff --git a/native/spark-expr/src/math_funcs/internal/mod.rs 
b/native/spark-expr/src/math_funcs/internal/mod.rs
index dff26146e8..98191f7819 100644
--- a/native/spark-expr/src/math_funcs/internal/mod.rs
+++ b/native/spark-expr/src/math_funcs/internal/mod.rs
@@ -24,5 +24,6 @@ mod unscaled_value;
 pub use checkoverflow::CheckOverflow;
 pub use decimal_rescale_check::DecimalRescaleCheckOverflow;
 pub use make_decimal::spark_make_decimal;
+pub(crate) use normalize_nan::normalize_float;
 pub use normalize_nan::NormalizeNaNAndZero;
 pub use unscaled_value::spark_unscaled_value;
diff --git a/native/spark-expr/src/math_funcs/internal/normalize_nan.rs 
b/native/spark-expr/src/math_funcs/internal/normalize_nan.rs
index 165ae3acc9..bb22e575a4 100644
--- a/native/spark-expr/src/math_funcs/internal/normalize_nan.rs
+++ b/native/spark-expr/src/math_funcs/internal/normalize_nan.rs
@@ -107,7 +107,7 @@ impl PhysicalExpr for NormalizeNaNAndZero {
 /// Normalize a floating point value by converting all NaN representations to 
a canonical NaN
 /// and negative zero to positive zero. This is used for Spark's comparison 
semantics.
 #[inline]
-fn normalize_float<T: num::Float>(v: T) -> T {
+pub(crate) fn normalize_float<T: num::Float>(v: T) -> T {
     if v.is_nan() {
         T::nan()
     } else if v == T::neg_zero() {
diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala 
b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
index e62c7eef56..f62db397cb 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -389,6 +389,7 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
    */
   val aggrSerdeMap: Map[Class[_], CometAggregateExpressionSerde[_]] = Map(
     classOf[ApproximatePercentile] -> CometApproxPercentile,
+    classOf[HyperLogLogPlusPlus] -> CometApproxCountDistinct,
     classOf[Average] -> CometAverage,
     classOf[BitAndAgg] -> CometBitAndAgg,
     classOf[BitOrAgg] -> CometBitOrAgg,
diff --git a/spark/src/main/scala/org/apache/comet/serde/aggregates.scala 
b/spark/src/main/scala/org/apache/comet/serde/aggregates.scala
index 1fb676366c..588d6a5907 100644
--- a/spark/src/main/scala/org/apache/comet/serde/aggregates.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/aggregates.scala
@@ -22,10 +22,10 @@ package org.apache.comet.serde
 import scala.jdk.CollectionConverters._
 
 import org.apache.spark.sql.catalyst.expressions.{Attribute, Cast, Literal}
-import 
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression, 
ApproximatePercentile, Average, BitAndAgg, BitOrAgg, BitXorAgg, 
BloomFilterAggregate, CentralMomentAgg, CollectSet, Corr, Count, Covariance, 
CovPopulation, CovSample, First, Last, Max, Min, Percentile, StddevPop, 
StddevSamp, Sum, VariancePop, VarianceSamp}
+import 
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression, 
ApproximatePercentile, Average, BitAndAgg, BitOrAgg, BitXorAgg, 
BloomFilterAggregate, CentralMomentAgg, CollectSet, Corr, Count, Covariance, 
CovPopulation, CovSample, First, HyperLogLogPlusPlus, Last, Max, Min, 
Percentile, StddevPop, StddevSamp, Sum, VariancePop, VarianceSamp}
 import org.apache.spark.sql.catalyst.util.ArrayData
 import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.types.{ByteType, DecimalType, DoubleType, 
FloatType, IntegerType, LongType, NumericType, ShortType, StringType}
+import org.apache.spark.sql.types.{BinaryType, BooleanType, ByteType, 
DataType, DateType, DecimalType, DoubleType, FloatType, IntegerType, LongType, 
NumericType, ShortType, StringType, TimestampNTZType, TimestampType}
 
 import org.apache.comet.CometConf.COMET_EXEC_STRICT_FLOATING_POINT
 import org.apache.comet.CometSparkSessionExtensions.{isSpark41Plus, 
withFallbackReason}
@@ -897,6 +897,69 @@ object CometCollectSet extends 
CometAggregateExpressionSerde[CollectSet] {
   }
 }
 
+object CometApproxCountDistinct extends 
CometAggregateExpressionSerde[HyperLogLogPlusPlus] {
+
+  // The register buffer uses Spark's identical packed-`Long` layout 
(`numWords` `Long` columns),
+  // matching Spark's `aggBufferSchema`, so a Comet partial and Spark final 
(or the reverse) can
+  // be mixed in one plan.
+  override def supportsMixedPartialFinal(fn: HyperLogLogPlusPlus): Boolean = 
true
+
+  // Types that Comet's native `xxhash64` hashes identically to Spark's 
`XxHash64Function`.
+  // `StringType` here is the default UTF8_BINARY collation; a collated 
`StringType(collationId)`
+  // falls through to the `case _` because Spark hashes those via the 
collation sort key.
+  // `DecimalType` is limited to precision <= 18, since Spark hashes wider 
decimals through
+  // `BigDecimal` (two's-complement big-endian bytes), which the native `i128` 
path does not match.
+  private def hashableType(dt: DataType): Boolean = dt match {
+    case BooleanType => true
+    case ByteType | ShortType | IntegerType | LongType => true
+    case FloatType | DoubleType => true
+    case d: DecimalType if d.precision <= 18 => true
+    case DateType | TimestampType | TimestampNTZType => true
+    case StringType | BinaryType => true
+    case _ => false
+  }
+
+  override def getUnsupportedReasons(): Seq[String] = Seq(
+    "Input type must be boolean, integral, floating-point, decimal, date/time, 
string, or binary",
+    "`DecimalType` with precision > 18 is not supported",
+    "Collated (non-UTF8_BINARY) strings are not supported")
+
+  override def getSupportLevel(expr: HyperLogLogPlusPlus): SupportLevel = {
+    if (!hashableType(expr.child.dataType)) {
+      Unsupported(Some(s"Unsupported input data type: ${expr.child.dataType}"))
+    } else {
+      Compatible()
+    }
+  }
+
+  override def convert(
+      aggExpr: AggregateExpression,
+      expr: HyperLogLogPlusPlus,
+      inputs: Seq[Attribute],
+      binding: Boolean,
+      conf: SQLConf): Option[ExprOuterClass.AggExpr] = {
+    val childExpr = exprToProto(expr.child, inputs, binding)
+
+    if (childExpr.isDefined) {
+      // Precision `p` is derived from `relativeSD` with the exact formula 
Spark uses in
+      // `HyperLogLogPlusPlusHelper`, so the native register layout matches 
Spark.
+      val p = Math.ceil(2.0d * Math.log(1.106d / expr.relativeSD) / 
Math.log(2.0d)).toInt
+      val builder = ExprOuterClass.HllPlusPlus.newBuilder()
+      builder.setChild(childExpr.get)
+      builder.setPrecision(p)
+
+      Some(
+        ExprOuterClass.AggExpr
+          .newBuilder()
+          .setHllpp(builder)
+          .build())
+    } else {
+      withFallbackReason(aggExpr, expr.child)
+      None
+    }
+  }
+}
+
 object AggSerde {
   import org.apache.spark.sql.types._
 
diff --git 
a/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct.sql
 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct.sql
new file mode 100644
index 0000000000..c08a83da3f
--- /dev/null
+++ 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct.sql
@@ -0,0 +1,158 @@
+-- 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.
+
+-- approx_count_distinct estimates the number of distinct values using 
HyperLogLog++.
+-- Comet ports Spark's HyperLogLogPlusPlus exactly, so results are compared 
for equality.
+
+-- ============================================================
+-- A table with a range of cardinalities across many magnitudes
+-- ============================================================
+
+statement
+CREATE TABLE acd_big USING parquet AS
+SELECT
+  id,
+  CAST(id % 10 AS INT) AS c10,
+  CAST(id % 500 AS INT) AS c500,
+  CAST(id % 5000 AS INT) AS c5000,
+  CAST(id % 8 AS INT) AS grp
+FROM range(50000)
+
+-- full cardinality (50000 distinct)
+query
+SELECT approx_count_distinct(id) FROM acd_big
+
+-- a spread of cardinalities exercising linear counting, bias correction, and 
plain HLL
+query
+SELECT
+  approx_count_distinct(c10),
+  approx_count_distinct(c500),
+  approx_count_distinct(c5000)
+FROM acd_big
+
+-- grouped
+query
+SELECT grp, approx_count_distinct(id) FROM acd_big GROUP BY grp ORDER BY grp
+
+-- ============================================================
+-- relativeSD argument (changes the precision p)
+-- ============================================================
+
+query
+SELECT approx_count_distinct(id, 0.01) FROM acd_big
+
+query
+SELECT approx_count_distinct(id, 0.1) FROM acd_big
+
+query
+SELECT approx_count_distinct(c5000, 0.02) FROM acd_big
+
+-- ============================================================
+-- Small cardinality and NULL handling across many types
+-- ============================================================
+
+statement
+CREATE TABLE acd_types(i int, l bigint, s string, f float, d double, dec 
decimal(10,2), dt date, ts timestamp, b boolean, bin binary, grp string) USING 
parquet
+
+statement
+INSERT INTO acd_types VALUES
+  (1, 100, 'a', 1.5, 1.25, 1.10, DATE '2024-01-01', TIMESTAMP '2024-01-01 
00:00:00', true,  X'CAFE', 'g1'),
+  (2, 200, 'b', 2.5, 2.25, 2.20, DATE '2024-02-01', TIMESTAMP '2024-02-01 
00:00:00', false, X'BABE', 'g1'),
+  (1, 100, 'a', 1.5, 1.25, 1.10, DATE '2024-01-01', TIMESTAMP '2024-01-01 
00:00:00', true,  X'CAFE', 'g1'),
+  (3, 300, 'c', 3.5, 3.25, 3.30, DATE '2024-03-01', TIMESTAMP '2024-03-01 
00:00:00', true,  X'FEED', 'g2'),
+  (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'g2')
+
+query
+SELECT
+  approx_count_distinct(i),
+  approx_count_distinct(l),
+  approx_count_distinct(s),
+  approx_count_distinct(f),
+  approx_count_distinct(d),
+  approx_count_distinct(dec),
+  approx_count_distinct(dt),
+  approx_count_distinct(ts),
+  approx_count_distinct(b),
+  approx_count_distinct(bin)
+FROM acd_types
+
+query
+SELECT grp, approx_count_distinct(i) FROM acd_types GROUP BY grp ORDER BY grp
+
+-- ============================================================
+-- Empty table returns 0 (not NULL)
+-- ============================================================
+
+statement
+CREATE TABLE acd_empty(i int) USING parquet
+
+query
+SELECT approx_count_distinct(i) FROM acd_empty
+
+-- all-NULL column returns 0
+query
+SELECT approx_count_distinct(i) FROM acd_types WHERE i IS NULL
+
+-- ============================================================
+-- Decimal precision boundary: <= 18 hashes via the unscaled long (native),
+-- > 18 hashes via BigDecimal in Spark, which the native path does not match 
(fallback)
+-- ============================================================
+
+statement
+CREATE TABLE acd_dec18(d decimal(18,4)) USING parquet
+
+statement
+INSERT INTO acd_dec18
+SELECT CAST(id AS decimal(18,4)) + 0.0001 FROM range(20000)
+
+-- decimal precision 18 (the boundary): runs natively and matches Spark
+query
+SELECT approx_count_distinct(d) FROM acd_dec18
+
+statement
+CREATE TABLE acd_dec38(d decimal(38,4)) USING parquet
+
+statement
+INSERT INTO acd_dec38
+SELECT CAST(id AS decimal(38,4)) + 0.0001 FROM range(20000)
+
+-- decimal precision > 18: Spark hashes via BigDecimal, so Comet must fall back
+query expect_fallback(Unsupported input data type)
+SELECT approx_count_distinct(d) FROM acd_dec38
+
+-- ============================================================
+-- Float/double normalization: -0.0 == 0.0 and all NaN are equal
+-- ============================================================
+
+statement
+CREATE TABLE acd_float(d double) USING parquet
+
+statement
+INSERT INTO acd_float VALUES
+  (0.0), (-0.0), (CAST('NaN' AS DOUBLE)), (CAST('NaN' AS DOUBLE)), (1.0)
+
+-- distinct values are {0.0, NaN, 1.0} = 3
+query
+SELECT approx_count_distinct(d) FROM acd_float
+
+-- ============================================================
+-- Combined with other aggregates
+-- ============================================================
+
+query
+SELECT grp, approx_count_distinct(id), count(*), count(DISTINCT grp)
+FROM acd_big GROUP BY grp ORDER BY grp
diff --git 
a/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_collation.sql
 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_collation.sql
new file mode 100644
index 0000000000..5a26c27db6
--- /dev/null
+++ 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_collation.sql
@@ -0,0 +1,37 @@
+-- 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.
+
+-- Spark hashes a collated (non-UTF8_BINARY) string via its collation sort 
key, which Comet's
+-- plain-bytes native xxhash64 does not reproduce, so approx_count_distinct on 
a collated column
+-- must fall back to Spark. Default UTF8_BINARY strings still run natively. 
Collation syntax
+-- requires Spark 4.0+.
+
+-- MinSparkVersion: 4.0
+
+statement
+CREATE TABLE acd_coll(s string COLLATE UTF8_LCASE, s_bin string) USING parquet
+
+statement
+INSERT INTO acd_coll VALUES ('a', 'a'), ('A', 'A'), ('b', 'b')
+
+-- default UTF8_BINARY collation: runs natively and matches Spark ({a, A, b} = 
3)
+query
+SELECT approx_count_distinct(s_bin) FROM acd_coll
+
+-- UTF8_LCASE collation: Spark hashes via the collation sort key, so Comet 
falls back
+query expect_fallback(Unsupported input data type)
+SELECT approx_count_distinct(s) FROM acd_coll
diff --git 
a/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_timezone.sql
 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_timezone.sql
new file mode 100644
index 0000000000..63e8f92c3a
--- /dev/null
+++ 
b/spark/src/test/resources/sql-tests/expressions/aggregate/approx_count_distinct_timezone.sql
@@ -0,0 +1,40 @@
+-- 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.
+
+-- approx_count_distinct on TimestampType must be timezone-independent: the 
hashed value is the
+-- stored UTC microseconds, so results match Spark under a non-UTC session 
time zone.
+
+-- Config: spark.sql.session.timeZone=America/Los_Angeles
+
+statement
+CREATE TABLE acd_tz(ts timestamp, grp int) USING parquet
+
+statement
+INSERT INTO acd_tz VALUES
+  (TIMESTAMP '2024-01-01 00:00:00', 0),
+  (TIMESTAMP '2024-06-01 12:30:00', 0),
+  (TIMESTAMP '2024-01-01 00:00:00', 1),
+  (TIMESTAMP '2024-12-31 23:59:59', 1),
+  (NULL, 1)
+
+-- global aggregation under a non-UTC session time zone
+query
+SELECT approx_count_distinct(ts) FROM acd_tz
+
+-- grouped aggregation under a non-UTC session time zone
+query
+SELECT grp, approx_count_distinct(ts) FROM acd_tz GROUP BY grp ORDER BY grp
diff --git 
a/spark/src/test/scala/org/apache/comet/rules/CometExecRuleSuite.scala 
b/spark/src/test/scala/org/apache/comet/rules/CometExecRuleSuite.scala
index 603e9033a4..93af47a605 100644
--- a/spark/src/test/scala/org/apache/comet/rules/CometExecRuleSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/rules/CometExecRuleSuite.scala
@@ -492,6 +492,54 @@ class CometExecRuleSuite extends CometTestBase {
     }
   }
 
+  test("CometExecRule should allow approx_count_distinct mixed Comet partial 
and Spark final") {
+    withTempView("test_data") {
+      createTestDataFrame.createOrReplaceTempView("test_data")
+
+      // approx_count_distinct stores its registers in Spark's identical 
packed-Long buffer, so
+      // it is mixed-safe: the Comet partial can feed a Spark final.
+      val sparkPlan =
+        createSparkPlan(
+          spark,
+          "SELECT approx_count_distinct(id) FROM test_data GROUP BY (id % 3)")
+
+      val originalHashAggCount = countOperators(sparkPlan, 
classOf[HashAggregateExec])
+      assert(originalHashAggCount == 2)
+
+      withSQLConf(
+        CometConf.COMET_ENABLE_FINAL_HASH_AGGREGATE.key -> "false",
+        CometConf.COMET_EXEC_LOCAL_TABLE_SCAN_ENABLED.key -> "true") {
+        val transformedPlan = applyCometExecRule(sparkPlan)
+
+        assert(countOperators(transformedPlan, classOf[HashAggregateExec]) == 
1) // final only
+        assert(countOperators(transformedPlan, 
classOf[CometHashAggregateExec]) == 1) // partial
+      }
+    }
+  }
+
+  test("CometExecRule should allow approx_count_distinct mixed Spark partial 
and Comet final") {
+    withTempView("test_data") {
+      createTestDataFrame.createOrReplaceTempView("test_data")
+
+      val sparkPlan =
+        createSparkPlan(
+          spark,
+          "SELECT approx_count_distinct(id) FROM test_data GROUP BY (id % 3)")
+
+      val originalHashAggCount = countOperators(sparkPlan, 
classOf[HashAggregateExec])
+      assert(originalHashAggCount == 2)
+
+      withSQLConf(
+        CometConf.COMET_ENABLE_PARTIAL_HASH_AGGREGATE.key -> "false",
+        CometConf.COMET_EXEC_LOCAL_TABLE_SCAN_ENABLED.key -> "true") {
+        val transformedPlan = applyCometExecRule(sparkPlan)
+
+        assert(countOperators(transformedPlan, classOf[HashAggregateExec]) == 
1) // partial only
+        assert(countOperators(transformedPlan, 
classOf[CometHashAggregateExec]) == 1) // final
+      }
+    }
+  }
+
   test("CometExecRule should not convert hash aggregate when grouping key 
contains map type") {
     // Spark 3.4/3.5 reject `array<map<...>>` as a grouping key in the 
analyzer (not orderable),
     // so the plan never reaches CometExecRule on those versions. The guard 
we're exercising
diff --git 
a/spark/src/test/scala/org/apache/spark/sql/benchmark/CometAggregateExpressionBenchmark.scala
 
b/spark/src/test/scala/org/apache/spark/sql/benchmark/CometAggregateExpressionBenchmark.scala
index ab7c731a6c..7aa66fb0a9 100644
--- 
a/spark/src/test/scala/org/apache/spark/sql/benchmark/CometAggregateExpressionBenchmark.scala
+++ 
b/spark/src/test/scala/org/apache/spark/sql/benchmark/CometAggregateExpressionBenchmark.scala
@@ -125,6 +125,21 @@ object CometAggregateExpressionBenchmark extends 
CometBenchmarkBase {
       "percentile_double_high_card",
       "SELECT percentile(c_double, 0.5) FROM parquetV1Table GROUP BY 
high_card_grp"))
 
+  // approx_count_distinct (Spark's HyperLogLogPlusPlus). c_int has ~10000 
distinct values.
+  private val approxCountDistinctAggregates = List(
+    AggExprConfig(
+      "approx_count_distinct_int",
+      "SELECT approx_count_distinct(c_int) FROM parquetV1Table GROUP BY grp"),
+    AggExprConfig(
+      "approx_count_distinct_string",
+      "SELECT approx_count_distinct(CAST(c_int AS STRING)) FROM parquetV1Table 
GROUP BY grp"),
+    AggExprConfig(
+      "approx_count_distinct_global",
+      "SELECT approx_count_distinct(c_int) FROM parquetV1Table"),
+    AggExprConfig(
+      "approx_count_distinct_high_card",
+      "SELECT approx_count_distinct(c_int) FROM parquetV1Table GROUP BY 
high_card_grp"))
+
   // Approximate percentile (Greenwald-Khanna). All numeric input types and the
   // scalar, array, and explicit-accuracy forms run natively.
   private val approxPercentileAggregates = List(
@@ -176,7 +191,8 @@ object CometAggregateExpressionBenchmark extends 
CometBenchmarkBase {
 
           val allAggregates = basicAggregates ++ statisticalAggregates ++ 
bitwiseAggregates ++
             multiKeyAggregates ++ multiAggregates ++ decimalAggregates ++
-            highCardinalityAggregates ++ percentileAggregates ++ 
approxPercentileAggregates
+            highCardinalityAggregates ++ percentileAggregates ++ 
approxPercentileAggregates ++
+            approxCountDistinctAggregates
 
           allAggregates.foreach { config =>
             runExpressionBenchmark(config.name, v, config.query, 
config.extraCometConfigs)


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


Reply via email to