#![allow(clippy::cast_precision_loss)]
#![allow(clippy::similar_names)]
#[inline]
#[must_use]
pub fn fast_rsqrt(x: f32) -> f32 {
let i = x.to_bits();
let i = 0x5f37_5a86_u32.wrapping_sub(i >> 1);
let y = f32::from_bits(i);
let half_x = 0.5 * x;
y * (1.5 - half_x * y * y)
}
#[inline]
#[must_use]
pub(crate) fn cosine_finish_fast(dot: f32, norm_a_sq: f32, norm_b_sq: f32) -> f32 {
let denom_sq = norm_a_sq * norm_b_sq;
if denom_sq < f32::EPSILON * f32::EPSILON {
return 0.0;
}
(dot / denom_sq.sqrt()).clamp(-1.0, 1.0)
}
#[inline]
#[must_use]
pub fn cosine_similarity_fast(a: &[f32], b: &[f32]) -> f32 {
assert_eq!(a.len(), b.len(), "Vector dimensions must match");
let mut dot = 0.0_f32;
let mut norm_a_sq = 0.0_f32;
let mut norm_b_sq = 0.0_f32;
for (x, y) in a.iter().zip(b.iter()) {
dot += x * y;
norm_a_sq += x * x;
norm_b_sq += y * y;
}
if norm_a_sq == 0.0 || norm_b_sq == 0.0 {
return 0.0;
}
dot * fast_rsqrt(norm_a_sq) * fast_rsqrt(norm_b_sq)
}
#[inline]
pub(crate) fn cosine_scalar(a: &[f32], b: &[f32]) -> f32 {
let mut dot = 0.0_f32;
let mut norm_a_sq = 0.0_f32;
let mut norm_b_sq = 0.0_f32;
for (x, y) in a.iter().zip(b.iter()) {
dot += x * y;
norm_a_sq += x * x;
norm_b_sq += y * y;
}
let norm_a = norm_a_sq.sqrt();
let norm_b = norm_b_sq.sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
(dot / (norm_a * norm_b)).clamp(-1.0, 1.0)
}
#[inline]
pub(super) fn hamming_scalar(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b.iter())
.filter(|(&x, &y)| (x > 0.5) != (y > 0.5))
.count() as f32
}
#[inline]
pub(super) fn jaccard_scalar(a: &[f32], b: &[f32]) -> f32 {
let (intersection, union) = jaccard_scalar_accum(a, b);
if union == 0.0 {
1.0
} else {
intersection / union
}
}
#[inline]
pub(super) fn jaccard_scalar_accum(a: &[f32], b: &[f32]) -> (f32, f32) {
a.iter()
.zip(b.iter())
.fold((0.0_f32, 0.0_f32), |(inter, uni), (x, y)| {
(inter + x.min(*y), uni + x.max(*y))
})
}
#[inline]
pub(crate) fn hamming_binary_scalar(a: &[u64], b: &[u64]) -> u32 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x ^ y).count_ones())
.sum()
}