#![forbid(unsafe_code)]
#[must_use]
pub fn squared_norm<const D: usize>(coords: &[f64; D]) -> f64 {
coords.iter().fold(0.0, |acc, &x| x.mul_add(x, acc))
}
#[must_use]
pub fn hypot<const D: usize>(coords: &[f64; D]) -> f64 {
match D {
0 => 0.0,
1 => coords[0].abs(),
2 => {
coords[0].hypot(coords[1])
}
_ => {
let max_abs = coords
.iter()
.map(|&x| x.abs())
.fold(0.0, |acc, x| if x > acc { x } else { acc });
if max_abs == 0.0 {
return 0.0;
}
let sum_of_scaled_squares = coords.iter().fold(0.0, |acc, &x| {
let scaled = x / max_abs;
scaled.mul_add(scaled, acc)
});
max_abs * sum_of_scaled_squares.sqrt()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_hypot_2d() {
let distance = hypot(&[3.0, 4.0]);
assert_relative_eq!(distance, 5.0, epsilon = 1e-10);
let distance_zero = hypot(&[0.0, 0.0]);
assert_relative_eq!(distance_zero, 0.0, epsilon = 1e-10);
let distance_neg = hypot(&[-3.0, 4.0]);
assert_relative_eq!(distance_neg, 5.0, epsilon = 1e-10);
}
#[test]
fn test_hypot_3d() {
let distance = hypot(&[1.0, 2.0, 2.0]);
assert_relative_eq!(distance, 3.0, epsilon = 1e-10);
let distance_unit = hypot(&[1.0, 0.0, 0.0]);
assert_relative_eq!(distance_unit, 1.0, epsilon = 1e-10);
let distance_equal = hypot(&[1.0, 1.0, 1.0]);
assert_relative_eq!(distance_equal, 3.0_f64.sqrt(), epsilon = 1e-10);
}
#[test]
fn test_hypot_4d() {
let distance = hypot(&[1.0, 1.0, 1.0, 1.0]);
assert_relative_eq!(distance, 2.0, epsilon = 1e-10);
let distance_zero = hypot(&[0.0, 0.0, 0.0, 0.0]);
assert_relative_eq!(distance_zero, 0.0, epsilon = 1e-10);
}
#[test]
fn test_hypot_edge_cases() {
let distance_0d = hypot::<0>(&[]);
assert_relative_eq!(distance_0d, 0.0, epsilon = 1e-10);
let distance_1d_pos = hypot(&[5.0]);
assert_relative_eq!(distance_1d_pos, 5.0, epsilon = 1e-10);
let distance_1d_neg = hypot(&[-5.0]);
assert_relative_eq!(distance_1d_neg, 5.0, epsilon = 1e-10);
let distance_large = hypot(&[1e200, 1e200]);
assert!(distance_large.is_finite());
assert!(distance_large > 0.0);
}
#[test]
fn test_squared_norm_dimensions_2_to_5() {
let norm_2d = squared_norm(&[3.0, 4.0]);
assert_relative_eq!(norm_2d, 25.0);
let norm_3d = squared_norm(&[1.0, 2.0, 2.0]);
assert_relative_eq!(norm_3d, 9.0);
let norm_4d = squared_norm(&[1.0, 1.0, 1.0, 1.0]);
assert_relative_eq!(norm_4d, 4.0);
let norm_5d = squared_norm(&[1.0, 1.0, 1.0, 1.0, 1.0]);
assert_relative_eq!(norm_5d, 5.0);
let norm_zero = squared_norm(&[0.0, 0.0, 0.0]);
assert_relative_eq!(norm_zero, 0.0);
}
#[test]
fn test_hypot_dimensions_2_to_5() {
let distance_2d = hypot(&[3.0, 4.0]);
assert_relative_eq!(distance_2d, 5.0, epsilon = 1e-10);
let distance_3d = hypot(&[1.0, 2.0, 2.0]);
assert_relative_eq!(distance_3d, 3.0, epsilon = 1e-10);
let distance_4d = hypot(&[1.0, 1.0, 1.0, 1.0]);
assert_relative_eq!(distance_4d, 2.0, epsilon = 1e-10);
let distance_5d = hypot(&[1.0, 1.0, 1.0, 1.0, 1.0]);
assert_relative_eq!(distance_5d, 5.0_f64.sqrt(), epsilon = 1e-10);
let distance_mixed = hypot(&[1e10, 1e-10, 1e5]);
assert!(distance_mixed.is_finite());
assert!(distance_mixed > 0.0);
}
}