1#[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}