1#[derive(Debug, Clone, thiserror::Error)]
6pub enum MathError {
7 #[error("vector length mismatch: {0} vs {1}")]
9 LengthMismatch(usize, usize),
10}
11
12pub fn cosine_similarity(a: &[f32], b: &[f32]) -> Result<f32, MathError> {
31 if a.len() != b.len() {
32 return Err(MathError::LengthMismatch(a.len(), b.len()));
33 }
34
35 let dot_product: f64 = a
37 .iter()
38 .zip(b.iter())
39 .map(|(x, y)| (*x as f64) * (*y as f64))
40 .sum();
41 let norm_a: f64 = a
42 .iter()
43 .map(|x| (*x as f64) * (*x as f64))
44 .sum::<f64>()
45 .sqrt();
46 let norm_b: f64 = b
47 .iter()
48 .map(|x| (*x as f64) * (*x as f64))
49 .sum::<f64>()
50 .sqrt();
51
52 if norm_a < f64::EPSILON || norm_b < f64::EPSILON {
54 return Ok(0.0);
55 }
56
57 Ok((dot_product / (norm_a * norm_b)) as f32)
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_identical_vectors() {
66 let v = vec![1.0, 2.0, 3.0];
67 let sim = cosine_similarity(&v, &v).unwrap();
68 assert!((sim - 1.0).abs() < 1e-6);
69 }
70
71 #[test]
72 fn test_orthogonal_vectors() {
73 let a = vec![1.0, 0.0, 0.0];
74 let b = vec![0.0, 1.0, 0.0];
75 let sim = cosine_similarity(&a, &b).unwrap();
76 assert!((sim - 0.0).abs() < 1e-6);
77 }
78
79 #[test]
80 fn test_opposite_vectors() {
81 let a = vec![1.0, 0.0];
82 let b = vec![-1.0, 0.0];
83 let sim = cosine_similarity(&a, &b).unwrap();
84 assert!((sim - (-1.0)).abs() < 1e-6);
85 }
86
87 #[test]
88 fn test_different_lengths_returns_error() {
89 let a = vec![1.0, 2.0];
90 let b = vec![1.0];
91 assert!(cosine_similarity(&a, &b).is_err());
92 }
93
94 #[test]
95 fn test_zero_vector() {
96 let a = vec![0.0, 0.0];
97 let b = vec![1.0, 2.0];
98 assert_eq!(cosine_similarity(&a, &b).unwrap(), 0.0);
99 }
100}