Skip to main content

zeph_common/
math.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Mathematical utilities for vector operations.
5
6/// Compute cosine similarity between two equal-length f32 vectors.
7///
8/// Returns `0.0` if the vectors have different lengths, are empty, or if
9/// either vector is a zero vector.
10///
11/// Uses a single-pass loop for efficiency.
12#[inline]
13#[must_use]
14pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
15    if a.len() != b.len() || a.is_empty() {
16        return 0.0;
17    }
18    debug_assert_eq!(a.len(), b.len(), "cosine_similarity: length mismatch");
19
20    let mut dot = 0.0_f32;
21    let mut norm_a = 0.0_f32;
22    let mut norm_b = 0.0_f32;
23
24    for (x, y) in a.iter().zip(b.iter()) {
25        dot += x * y;
26        norm_a += x * x;
27        norm_b += y * y;
28    }
29
30    let denom = norm_a.sqrt() * norm_b.sqrt();
31    if denom == 0.0 {
32        return 0.0;
33    }
34
35    dot / denom
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn identical_vectors() {
44        let v = vec![1.0_f32, 2.0, 3.0];
45        assert!((cosine_similarity(&v, &v) - 1.0).abs() < 1e-6);
46    }
47
48    #[test]
49    fn orthogonal_vectors() {
50        let a = vec![1.0_f32, 0.0];
51        let b = vec![0.0_f32, 1.0];
52        assert!(cosine_similarity(&a, &b).abs() < 1e-6);
53    }
54
55    #[test]
56    fn opposite_vectors() {
57        let a = vec![1.0_f32, 0.0];
58        let b = vec![-1.0_f32, 0.0];
59        assert!((cosine_similarity(&a, &b) + 1.0).abs() < 1e-6);
60    }
61
62    #[test]
63    fn zero_vector() {
64        let a = vec![0.0_f32, 0.0];
65        let b = vec![1.0_f32, 0.0];
66        assert!(cosine_similarity(&a, &b).abs() <= f32::EPSILON);
67    }
68
69    #[test]
70    fn different_lengths() {
71        let a = vec![1.0_f32];
72        let b = vec![1.0_f32, 0.0];
73        assert!(cosine_similarity(&a, &b).abs() <= f32::EPSILON);
74    }
75
76    #[test]
77    fn empty_vectors() {
78        assert!(cosine_similarity(&[], &[]).abs() <= f32::EPSILON);
79    }
80
81    #[test]
82    fn parallel_vectors() {
83        let a = vec![2.0_f32, 0.0];
84        let b = vec![5.0_f32, 0.0];
85        assert!((cosine_similarity(&a, &b) - 1.0).abs() < 1e-6);
86    }
87
88    #[test]
89    fn normalized_vectors() {
90        let s = 1.0_f32 / 2.0_f32.sqrt();
91        let a = vec![s, s];
92        let b = vec![s, s];
93        assert!((cosine_similarity(&a, &b) - 1.0).abs() < 1e-6);
94    }
95}