#![forbid(unsafe_code)]
use super::conversions::{safe_scalar_from_f64, safe_scalar_to_f64};
use crate::geometry::traits::coordinate::CoordinateScalar;
use num_traits::Float;
pub(in crate::geometry::util) fn scaled_hypot_2d<T: CoordinateScalar>(x: T, y: T) -> T {
let max_abs = Float::abs(x).max(Float::abs(y));
if max_abs == T::zero() {
return T::zero();
}
let x_scaled = x / max_abs;
let y_scaled = y / max_abs;
max_abs * Float::sqrt(x_scaled * x_scaled + y_scaled * y_scaled)
}
pub fn squared_norm<T, const D: usize>(coords: &[T; D]) -> T
where
T: CoordinateScalar,
{
coords.iter().fold(T::zero(), |acc, &x| acc + x * x)
}
pub fn hypot<T, const D: usize>(coords: &[T; D]) -> T
where
T: CoordinateScalar,
{
match D {
0 => T::zero(),
1 => Float::abs(coords[0]),
2 => {
if let (Ok(a_f64), Ok(b_f64)) =
(safe_scalar_to_f64(coords[0]), safe_scalar_to_f64(coords[1]))
{
let result_f64 = a_f64.hypot(b_f64);
safe_scalar_from_f64(result_f64).unwrap_or_else(|_| {
scaled_hypot_2d(coords[0], coords[1])
})
} else {
scaled_hypot_2d(coords[0], coords[1])
}
}
_ => {
let max_abs = coords
.iter()
.map(|&x| Float::abs(x))
.fold(T::zero(), |acc, x| if x > acc { x } else { acc });
if max_abs == T::zero() {
return T::zero();
}
let sum_of_scaled_squares = coords
.iter()
.map(|&x| {
let scaled = x / max_abs;
scaled * scaled
})
.fold(T::zero(), |acc, x| acc + x);
max_abs * Float::sqrt(sum_of_scaled_squares)
}
}
}
#[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::<f64, 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_scaled_hypot_2d() {
let result = scaled_hypot_2d(3.0, 4.0);
assert_relative_eq!(result, 5.0, epsilon = 1e-10);
let zero_result = scaled_hypot_2d(0.0, 0.0);
assert_relative_eq!(zero_result, 0.0, epsilon = 1e-10);
let neg_result = scaled_hypot_2d(-3.0, 4.0);
assert_relative_eq!(neg_result, 5.0, epsilon = 1e-10);
let large_result = scaled_hypot_2d(1e10, 1e10);
let expected = (2.0_f64).sqrt() * 1e10;
assert_relative_eq!(large_result, expected, epsilon = 1e-5);
}
#[test]
fn test_scaled_hypot_2d_edge_cases() {
let result = scaled_hypot_2d(0.0, 0.0);
assert_relative_eq!(result, 0.0);
let result = scaled_hypot_2d(0.0, 5.0);
assert_relative_eq!(result, 5.0);
let result = scaled_hypot_2d(3.0, 4.0);
assert_relative_eq!(result, 5.0, epsilon = 1e-10);
let result = scaled_hypot_2d(-3.0, -4.0);
assert_relative_eq!(result, 5.0, epsilon = 1e-10);
let large_val = 1e100;
let result = scaled_hypot_2d(large_val, large_val);
assert!(result.is_finite());
assert!(result > 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);
}
}