pub fn cosine(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_vectors_return_one() {
let v = vec![1.0_f32, 2.0, 3.0];
let s = cosine(&v, &v);
assert!((s - 1.0).abs() < 1e-5, "expected ~1.0, got {s}");
}
#[test]
fn orthogonal_vectors_return_zero() {
let a = vec![1.0_f32, 0.0, 0.0];
let b = vec![0.0_f32, 1.0, 0.0];
let s = cosine(&a, &b);
assert!(s.abs() < 1e-5, "expected ~0.0, got {s}");
}
#[test]
fn opposite_vectors_return_minus_one() {
let a = vec![1.0_f32, 0.0];
let b = vec![-1.0_f32, 0.0];
let s = cosine(&a, &b);
assert!((s + 1.0).abs() < 1e-5, "expected ~-1.0, got {s}");
}
#[test]
fn zero_vector_returns_zero() {
let a = vec![0.0_f32, 0.0];
let b = vec![1.0_f32, 2.0];
assert_eq!(cosine(&a, &b), 0.0);
assert_eq!(cosine(&b, &a), 0.0);
}
#[test]
fn similar_vectors_score_high() {
let a = vec![1.0_f32, 1.0, 0.0];
let b = vec![1.0_f32, 0.9, 0.1];
let s = cosine(&a, &b);
assert!(s > 0.98, "expected > 0.98, got {s}");
}
}