c++中如何计算标准差与方差_c++数学计算统计函数

最稳妥方式是用std::accumulate两遍遍历:先求均值,再算平方偏差均值;样本方差除以n-1,总体方差除以n;需预检查NaN/inf、空容器及分母为零。

std::accumulate 手动计算方差和标准差最稳妥

标准库没有内置的方差或标准差函数,std::valarray 虽有 sum() 但不支持直接求均值平方差;依赖第三方库(如 Boost.Math)会增加构建复杂度。实际项目中,用 std::accumulate 两遍遍历是最可控的方式:第一遍算均值,第二遍算平方偏差均值。

  • 必须先求平均值 mean,再遍历计算 (x - mean) * (x - mean),不能合并成单次 accumulate —— 否则会因浮点精度丢失导致方差为负(尤其数据量大、数值集中时)
  • 样本方差用 n-1 作分母(贝塞尔校正),总体方差用 n;C++ 里需显式判断并传入 ddof = 01
  • 输入容器应为 std::vector 或类似可迭代浮点序列,避免整数除法截断
#include 
#include 
#include 

double variance(const std::vector& data, int ddof = 0) { if (data.empty()) return 0.0; double mean = std::accumulate(data.begin(), data.end(), 0.0) / data.size(); double sum_sq_diff = std::accumulate(data.begin(), data.end(), 0.0, [mean](double acc, double x) { return acc + (x - mean) * (x - mean); }); return sum_sq_diff / (data.size() - ddof); }

double stddev(const std::vector& data, int ddof = 0) { return std::sqrt(variance(data, ddof)); }

std::valarray 快速原型但慎用于生产

std::valarray 支持向量化运算,写起来简洁,但存在隐式拷贝开销、不支持迭代器、且部分老编译器(如 MSVC 2015 前)实现不全。仅建议在小规模数据、快速验证公式时使用。

  • valarraysum() 返回 double,但中间运算可能触发 promotion 规则,若原始类型是 float,结果仍可能是 float,导致精度不足
  • 无法直接对 valarray 做“减去标量均值”操作而不生成临时对象,内存效率不如手写循环
  • 以下写法看似短,但每次 - mean 都构造新 valarray,不适用于大数据集
#include 
#include 

double variance_valarray(const std::valarray& v) { if (v.size() == 0) return 0.0; double mean = v.sum() / v.size(); std::valarray diff = v - mean; return (diff * diff).sum() / v.size(); }

遇到 nan 或负方差?检查输入和溢出路径

调用后得到 nanvariance 返回负值,几乎一定是以下原因:

  • 输入含 NaNinf:用 std::isnan(x)std::isinf(x) 预过滤,否则 (x - mean) 可能传播 nan
  • 数据范围过大导致 (x - mean) * (x - mean) 溢出 double(如 x ≈ 1e155),此时应改用 Welford 在线算法避免大数相减
  • 容器大小为 1 且 ddof = 1 → 分母为 0 → 返回 inf;需在函数开头加 if (data.size()

性能敏感场景用 Welford 算法单趟完成

当数据来自流式输入(如传感器、文件逐行读取)、不能存全量或内存受限时,Welford 方法可在一次遍历中累积计算方差,且数值稳定性优于两遍法。

  • 核心是维护 Mk(当前均值)和 Sk(平方和修正项),递推更新,无须存储全部数据
  • 最终方差为 S / (n - ddof),其中 S 是递推得到的 Sk
  • 注意:初始 M = 0.0, S = 0.0, n = 0,每来一个 x 更新一次,n 从 1 开始计数
struct Welford {
    double M = 0.0, S = 0.0;
    size_t n = 0;
void add(double x) {
    n++;
    double delta = x - M;
    M += delta / n;
    S += delta * (x - M);
}

double variance(int ddof = 0) const {
    return n <= static_castzuojiankuohaophpcnsize_tyoujiankuohaophpcn(ddof) ? 0.0 : S / (n - ddof);
}

double stddev(int ddof = 0) const {
    return std::sqrt(variance(ddof));
}

};

Welford 算法的数值稳定性常被低估——它真正难处理的是极端情况:比如所有数都接近 1e308,此时 delta 计算仍可能失真。这种时候,要么换更高精度类型(long double),要么做预平移(减去估计均值再算)。