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