Source
stdlib/statistics/statistics.hpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#pragma once5
/**6
* @file statistics.hpp7
* @brief cheatah `statistics` — descriptive statistics over numeric sequences,8
* mirroring Python's `statistics` module. `import statistics` to use it.9
*10
* Header-only: every function is a template constrained by the `NumericRange`11
* concept, so it accepts any iterable of arithmetic values (`list[float]`,12
* `array[int]`, …) and is instantiated at the call site. Unit tests:13
* `stdlib/tests/statistics_test.cpp`; the suite runs under AddressSanitizer (the14
* `asan` preset) and Valgrind (`security/run-valgrind.sh`) on every QA-gate run.15
*16
* @note `n` below is the element count. Single-pass reductions are O(n) and17
* allocation-free; `median` is the exception (it materializes and sorts a18
* copy). Results are scalar `double`/`std::size_t` — no heap on return.19
*/20
#include <algorithm>21
#include <cmath>22
#include <cstddef>23
#include <ranges>24
#include <type_traits>25
#include <vector>27
namespace cheatah::statistics {29
/// NumericRange<R>: an iterable of arithmetic values (list[float], array[int], …).30
template <typename R>31
concept NumericRange =32
std::ranges::input_range<R> && std::is_arithmetic_v<std::ranges::range_value_t<R>>;34
/**35
* Sum of the elements.36
*37
* Accumulates every element into a `double`, so an empty range sums to 0.0 and38
* integer inputs are widened before adding (no integer overflow).39
* @param data the numeric range.40
* @return Σ@p data as `double`.41
* @complexity O(n) single pass.42
* @alloc none.43
* @test CheatahStatistics.SumCountMean44
* @crtest StatisticsCompileRun.Sum45
* @systest StdlibE2E.Statistics46
*/47
template <NumericRange R>48
double sum(const R& data) {49
double s = 0.0;50
for (const auto& x : data) s += static_cast<double>(x);51
return s;52
}54
/**55
* Element count.56
* @param data the numeric range.57
* @return the number of elements.58
* @complexity O(n) single pass.59
* @alloc none.60
* @test CheatahStatistics.SumCountMean61
* @crtest StatisticsCompileRun.Count62
* @systest StdlibE2E.Statistics63
*/64
template <NumericRange R>65
std::size_t count(const R& data) {66
std::size_t n = 0;67
for (const auto& x : data) {68
(void)x;69
++n;70
}71
return n;72
}74
/**75
* Arithmetic mean.76
*77
* Computes sum/count, but guards division by zero: an empty range returns 0.078
* rather than NaN.79
* @param data the numeric range.80
* @return the mean, or 0.0 if empty.81
* @complexity O(n) (two passes: count + sum).82
* @alloc none.83
* @test CheatahStatistics.SumCountMean84
* @crtest StatisticsCompileRun.Mean85
* @systest StdlibE2E.Statistics86
*/87
template <NumericRange R>88
double mean(const R& data) {89
const std::size_t n = count(data);90
return n == 0 ? 0.0 : sum(data) / static_cast<double>(n);91
}93
/**94
* Population variance (divide by N).95
*96
* Mean of the squared deviations from the mean, dividing by N (treats @p data as97
* the entire population). Returns 0.0 for an empty or single-element range.98
* @param data the numeric range.99
* @return the variance, or 0.0 if empty.100
* @complexity O(n).101
* @alloc none.102
* @test CheatahStatistics.PopulationVarianceAndStdev103
* @crtest StatisticsCompileRun.Pvariance104
* @systest StdlibE2E.Statistics105
*/106
template <NumericRange R>107
double pvariance(const R& data) {108
const double m = mean(data);109
double s = 0.0;110
std::size_t n = 0;111
for (const auto& x : data) {112
const double d = static_cast<double>(x) - m;113
s += d * d;114
++n;115
}116
return n == 0 ? 0.0 : s / static_cast<double>(n);117
}118
/**119
* Population standard deviation.120
*121
* Square root of @ref pvariance, so it is 0.0 for empty or single-element ranges122
* and never negative.123
* @param data the numeric range.124
* @return √pvariance(@p data).125
* @complexity O(n).126
* @alloc none.127
* @test CheatahStatistics.PopulationVarianceAndStdev128
* @crtest StatisticsCompileRun.Pstdev129
* @systest StdlibE2E.Statistics130
*/131
template <NumericRange R>132
double pstdev(const R& data) { return std::sqrt(pvariance(data)); }134
/**135
* Sample variance (divide by N−1).136
*137
* Sum of squared deviations from the mean divided by N−1 (Bessel's correction,138
* estimating the variance of the wider population from a sample). Requires at139
* least two elements; an empty or single-element range returns 0.0 rather than140
* dividing by zero.141
* @param data the numeric range.142
* @return the variance, or 0.0 if fewer than 2 elements.143
* @complexity O(n).144
* @alloc none.145
* @test CheatahStatistics.SampleVarianceAndStdev146
* @crtest StatisticsCompileRun.Variance147
* @systest StdlibE2E.Statistics148
*/149
template <NumericRange R>150
double variance(const R& data) {151
const double m = mean(data);152
double s = 0.0;153
std::size_t n = 0;154
for (const auto& x : data) {155
const double d = static_cast<double>(x) - m;156
s += d * d;157
++n;158
}159
return n > 1 ? s / static_cast<double>(n - 1) : 0.0;160
}161
/**162
* Sample standard deviation.163
*164
* Square root of @ref variance, so it is 0.0 when there are fewer than two165
* elements.166
* @param data the numeric range.167
* @return √variance(@p data).168
* @complexity O(n).169
* @alloc none.170
* @test CheatahStatistics.SampleVarianceAndStdev171
* @crtest StatisticsCompileRun.Stdev172
* @systest StdlibE2E.Statistics173
*/174
template <NumericRange R>175
double stdev(const R& data) { return std::sqrt(variance(data)); }177
/**178
* Median (mean of the two middle values when the count is even).179
*180
* Copies the elements into a `double` vector, sorts ascending, and returns the181
* middle value (averaging the two central values when the count is even).182
* Returns 0.0 for an empty range.183
* @param data the numeric range.184
* @return the median, or 0.0 if empty.185
* @complexity O(n log n) — copies the elements into a vector and sorts.186
* @alloc allocates a temporary `std::vector<double>`.187
* @test CheatahStatistics.MedianOddAndEven188
* @crtest StatisticsCompileRun.Median189
* @systest StdlibE2E.Statistics190
*/191
template <NumericRange R>192
double median(const R& data) {193
std::vector<double> v;194
for (const auto& x : data) v.push_back(static_cast<double>(x));195
if (v.empty()) return 0.0;196
std::sort(v.begin(), v.end());197
const std::size_t n = v.size();198
return (n % 2 == 1) ? v[n / 2] : (v[n / 2 - 1] + v[n / 2]) / 2.0;199
}201
} // namespace cheatah::statistics