余弦相似度本质是向量夹角余弦值,公式为 dot(a,b)/(norm(a)*norm(b));需检查向量等长、避免零向量除零、用 double 计算并显式处理分母为 0 情况。

用 std::inner_product 和 std::sqrt 手动实现最直接
余弦相似度本质是向量夹角的余弦值,公式为:dot(a,b) / (norm(a) * norm(b))。C++ 标准库没提供现成函数,但 std::inner_product 可算点积,std::sqrt 和 std::accumulate 配合能算模长。注意:两个向量必须等长,否则行为未定义。
实操建议:
- 先检查
a.size() == b.size(),避免越界或静默错误 - 用
double类型计算,避免float累加误差导致分母接近 0 时结果异常(如nan或inf) - 点积和模长平方可一次遍历完成,但标准算法清晰优先,除非性能敏感场景
处理零向量必须显式判断,否则会除零
当任一向量所有元素全为 0 时,其模长为 0,直接除会导致 nan 或浮点异常。标准余弦相似度对零向量无定义,但工程中常约定返回 0 或 -1,需按业务决定。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 输入
{0,0,0}和{1,2,3},程序输出nan而非报错或合理默认值 - 用
std::isnormal()检查结果前,没先验证分母是否为 0
推荐做法:计算前加判断
if (norm_a == 0.0 || norm_b == 0.0) return 0.0; // 或抛异常、返回 NaN,视需求而定
用 Eigen 库时别忘了归一化和类型匹配
如果项目已引入 Eigen,可用 .dot() 和 .norm(),但要注意默认不自动归一化,且 Eigen::VectorXf 与 Eigen::VectorXd 混用会编译失败。
使用场景:
- 批量计算多个向量对时,
Eigen的向量化优化比手写循环快 - 矩阵运算密集场景(如 embedding 相似度检索),建议统一用
VectorXd避免隐式转换开销 -
.normalized()返回新对象,原向量不变;若反复调用,可提前缓存归一化结果
性能关键路径上避免重复计算模长
当一个向量要和成百上千个其他向量比相似度(如推荐系统召回),它的模长只需算一次。手写循环时容易每轮都重算 norm_a,浪费 CPU。
参数差异与优化点:
- 传入
const std::vector<double>&</double>而非值拷贝,避免构造临时对象 - 把
norm_a提到外层循环外,内层只算dot(a,b)和norm_b - 若向量维度固定(如 768 维 embedding),考虑用
std::array<double></double>替代vector,启用编译器向量化
复杂点往往不在公式本身,而在零向量边界、浮点精度累积、以及高频调用时的内存访问模式——这些地方不加日志很难复现问题。


















