{"record":{"id":"9a39e5b839f26c92","repo":"pola-rs/polars","slug":"should-be-f64-scalar","errorCode":null,"errorMessage":"should be f64 scalar","messagePattern":"should be f64 scalar","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/polars-core/src/series/implementations/decimal.rs","lineNumber":43,"sourceCode":"\n    fn apply_physical<T, F: Fn(&Int128Chunked) -> T>(&self, f: F) -> T {\n        f(self.0.physical())\n    }\n\n    fn scale_factor(&self) -> u128 {\n        10u128.pow(self.0.scale() as u32)\n    }\n\n    fn apply_scale(&self, mut scalar: Scalar) -> Scalar {\n        if scalar.is_null() {\n            return scalar;\n        }\n\n        debug_assert_eq!(scalar.dtype(), &DataType::Float64);\n        let v = scalar\n            .value()\n            .try_extract::<f64>()\n            .expect(\"should be f64 scalar\");\n        scalar.update((v / self.scale_factor() as f64).into());\n        scalar\n    }\n\n    fn agg_helper<F: Fn(&Int128Chunked) -> Series>(&self, f: F, precision: usize) -> Series {\n        let agg_s = f(self.0.physical());\n        let scale = self.0.scale();\n        match agg_s.dtype() {\n            DataType::Int128 => {\n                let ca = agg_s.i128().unwrap();\n                let ca = ca.as_ref().clone();\n                ca.into_decimal_unchecked(precision, scale).into_series()\n            },\n            DataType::List(dtype) if matches!(dtype.as_ref(), DataType::Int128) => {\n                let dtype = self.0.dtype();\n                let ca = agg_s.list().unwrap();\n                let arr = ca.downcast_iter().next().unwrap();\n                // SAFETY: dtype is passed correctly","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/pola-rs/polars/blob/68506541d2de983056c9eb244e1ea05fab377dfc/crates/polars-core/src/series/implementations/decimal.rs#L25-L61","documentation":"The DecimalChunked aggregation helper rescales result scalars by dividing by 10^scale, and assumes the scalar carries an f64 value (as produced by mean-style aggregations). If the scalar holds another dtype, try_extract::<f64>() fails and it panics 'should be f64 scalar'. This is an internal invariant between decimal aggregations and their scalar outputs.","triggerScenarios":"Aggregations over Decimal columns (mean, sum-then-normalize paths) where the aggregation returns a scalar whose dtype is not Float64 - typically from custom/user-defined aggregations plugged into the decimal path, or a mismatch between polars crate versions.","commonSituations":"Mixing polars-core and polars-lazy/plan versions so decimal aggregation helpers receive unexpected scalar types; custom plugin aggregations on decimal columns; encountered after upgrading one polars crate but not the others.","solutions":["Align all polars crates on one exact version (cargo update / lockfile pin) so decimal aggregation helpers match","Upgrade polars to the latest release; decimal aggregation invariants are actively fixed","As a workaround, cast decimals to Float64 before the aggregation, then re-cast if needed"],"exampleFix":"# before\npl.col(\"price\").mean()  # decimal mean path panics on mismatched versions\n\n# after\npl.col(\"price\").cast(pl.Float64).mean()  # bypass decimal helper until upgraded","handlingStrategy":"fallback","validationCode":"fn is_decimal(s: &Series) -> bool {\n    matches!(s.dtype(), DataType::Decimal(_, _))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep every polars-* crate on the same version via the lockfile","Cast decimal columns to Float64 before aggregations when running mixed/older versions","Pin the polars release in CI and add decimal aggregation regression tests"],"tags":["polars","decimal","aggregation","internal-api","panic","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"68506541d2de983056c9eb244e1ea05fab377dfc","analyzedAt":"2026-08-19T12:15:06.350Z","contentChangedAt":"2026-08-19T12:15:06.350Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}