#![forbid(unsafe_code)]
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use num_traits::cast;
pub use super::ValueConversionError;
pub(in crate::geometry::util) fn safe_cast_to_f64<T: CoordinateScalar>(
value: T,
coordinate_index: usize,
) -> Result<f64, CoordinateConversionError> {
if !value.is_finite_generic() {
return Err(CoordinateConversionError::NonFiniteValue {
coordinate_index,
coordinate_value: format!("{value:?}"),
});
}
cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed {
coordinate_index,
coordinate_value: format!("{value:?}"),
from_type: std::any::type_name::<T>(),
to_type: "f64",
})
}
pub(in crate::geometry::util) fn safe_cast_from_f64<T: CoordinateScalar>(
value: f64,
coordinate_index: usize,
) -> Result<T, CoordinateConversionError> {
if !value.is_finite() {
return Err(CoordinateConversionError::NonFiniteValue {
coordinate_index,
coordinate_value: format!("{value:?}"),
});
}
cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed {
coordinate_index,
coordinate_value: format!("{value:?}"),
from_type: "f64",
to_type: std::any::type_name::<T>(),
})
}
pub fn safe_coords_to_f64<T: CoordinateScalar, const D: usize>(
coords: &[T; D],
) -> Result<[f64; D], CoordinateConversionError> {
let mut result = [0.0_f64; D];
for (i, &coord) in coords.iter().enumerate() {
result[i] = safe_cast_to_f64(coord, i)?;
}
Ok(result)
}
pub fn safe_coords_from_f64<T: CoordinateScalar, const D: usize>(
coords: &[f64; D],
) -> Result<[T; D], CoordinateConversionError> {
let mut result = [T::zero(); D];
for (i, &coord) in coords.iter().enumerate() {
result[i] = safe_cast_from_f64(coord, i)?;
}
Ok(result)
}
pub fn safe_scalar_to_f64<T: CoordinateScalar>(value: T) -> Result<f64, CoordinateConversionError> {
safe_cast_to_f64(value, 0)
}
pub fn safe_scalar_from_f64<T: CoordinateScalar>(
value: f64,
) -> Result<T, CoordinateConversionError> {
safe_cast_from_f64(value, 0)
}
pub fn safe_usize_to_scalar<T: CoordinateScalar>(
value: usize,
) -> Result<T, CoordinateConversionError> {
const F64_MANTISSA_BITS: u32 = 53;
let t_mantissa_bits: u32 = T::mantissa_digits();
let max_precise_bits = core::cmp::min(F64_MANTISSA_BITS, t_mantissa_bits);
let max_precise_u128: u128 = (1u128 << max_precise_bits) - 1;
let value_u64 =
u64::try_from(value).map_err(|_| CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("{value}"),
from_type: "usize",
to_type: std::any::type_name::<T>(),
})?;
if u128::from(value_u64) > max_precise_u128 {
return Err(CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("{value}"),
from_type: "usize",
to_type: std::any::type_name::<T>(),
});
}
let f64_value: f64 =
cast(value).ok_or_else(|| CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: format!("{value}"),
from_type: "usize",
to_type: "f64",
})?;
safe_scalar_from_f64(f64_value)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::traits::coordinate::CoordinateConversionError;
use crate::geometry::util::{CircumcenterError, RandomPointGenerationError};
use approx::assert_relative_eq;
use num_traits::cast;
#[test]
fn test_safe_usize_to_scalar_basic_success() {
let small_value = 42_usize;
let result: Result<f64, _> = safe_usize_to_scalar(small_value);
assert!(result.is_ok());
assert_relative_eq!(result.unwrap(), 42.0f64, epsilon = 1e-15);
let result_f32: Result<f32, _> = safe_usize_to_scalar(small_value);
assert!(result_f32.is_ok());
assert_relative_eq!(result_f32.unwrap(), 42.0f32, epsilon = 1e-6);
}
#[test]
fn test_safe_usize_to_scalar_within_f64_precision() {
let safe_large_values = [
usize::try_from(1_u64 << 50).unwrap_or(usize::MAX), usize::try_from(1_u64 << 51).unwrap_or(usize::MAX), usize::try_from((1_u64 << 53) - 2).unwrap_or(usize::MAX), ];
for &value in &safe_large_values {
let result: Result<f64, _> = safe_usize_to_scalar(value);
assert!(result.is_ok(), "Failed to convert safe large value {value}");
let converted = result.unwrap();
let back_converted: usize =
cast(converted).expect("f64 should convert back to usize exactly");
assert_eq!(
back_converted, value,
"Conversion was not exact for {value}"
);
}
}
#[test]
fn test_safe_usize_to_scalar_precision_boundary() {
const MAX_PRECISE_USIZE_IN_F64: u64 = (1_u64 << 53) - 1;
if usize::try_from(MAX_PRECISE_USIZE_IN_F64).is_ok() {
let boundary_value = usize::try_from(MAX_PRECISE_USIZE_IN_F64).unwrap();
let result: Result<f64, _> = safe_usize_to_scalar(boundary_value);
assert!(
result.is_ok(),
"Boundary value 2^53-1 should be convertible"
);
let converted = result.unwrap();
let back_converted: usize =
cast(converted).expect("Boundary f64 should convert back to usize exactly");
assert_eq!(
back_converted, boundary_value,
"Boundary conversion should be exact"
);
}
}
#[test]
fn test_safe_usize_to_scalar_precision_loss_detection() {
const MAX_PRECISE_USIZE_IN_F64: u64 = (1_u64 << 53) - 1;
if std::mem::size_of::<usize>() >= 8 {
let precision_loss_values = [
usize::try_from(MAX_PRECISE_USIZE_IN_F64 + 1).unwrap_or(usize::MAX), usize::try_from(MAX_PRECISE_USIZE_IN_F64 + 100).unwrap_or(usize::MAX), ];
for &value in &precision_loss_values {
let value_u64 = u64::try_from(value).unwrap_or(u64::MAX);
if value_u64 > u64::try_from(usize::MAX).unwrap_or(u64::MAX) {
continue;
}
let result: Result<f64, _> = safe_usize_to_scalar(value);
assert!(
result.is_err(),
"Value {value} should fail conversion due to precision loss"
);
if let Err(CoordinateConversionError::ConversionFailed {
coordinate_index,
coordinate_value,
from_type,
to_type,
}) = result
{
assert_eq!(coordinate_index, 0);
assert_eq!(coordinate_value, format!("{value}"));
assert_eq!(from_type, "usize");
assert_eq!(to_type, "f64");
} else {
panic!("Expected ConversionFailed error for value {value}");
}
}
} else {
println!("Skipping precision loss test on 32-bit platform");
}
}
#[test]
fn test_safe_usize_to_scalar_error_message_format() {
const MAX_PRECISE_USIZE_IN_F64: u64 = (1_u64 << 53) - 1;
if std::mem::size_of::<usize>() >= 8 {
let large_value = usize::try_from(MAX_PRECISE_USIZE_IN_F64 + 1).unwrap_or(usize::MAX);
if u64::try_from(large_value).unwrap_or(u64::MAX)
<= u64::try_from(usize::MAX).unwrap_or(u64::MAX)
{
let result: Result<f64, _> = safe_usize_to_scalar(large_value);
assert!(result.is_err());
let error_message = format!("{}", result.unwrap_err());
assert!(error_message.contains("Failed to convert"));
assert!(error_message.contains(&format!("{large_value}")));
assert!(error_message.contains("usize"));
assert!(error_message.contains("f64"));
}
}
}
#[test]
fn test_safe_usize_to_scalar_f32_precision_limit() {
const F32_MAX_EXACT_INT: usize = 16_777_215;
const F32_FIRST_INEXACT_INT: usize = 16_777_216;
let result_within: Result<f32, _> = safe_usize_to_scalar(F32_MAX_EXACT_INT);
assert!(
result_within.is_ok(),
"Value {F32_MAX_EXACT_INT} should be within f32 precision"
);
let expected_f32: f32 =
cast(F32_MAX_EXACT_INT).expect("This value should be exactly representable in f32");
assert_relative_eq!(result_within.unwrap(), expected_f32, epsilon = 1e-6);
let result_beyond: Result<f32, _> = safe_usize_to_scalar(F32_FIRST_INEXACT_INT);
assert!(
result_beyond.is_err(),
"Value {F32_FIRST_INEXACT_INT} should exceed f32 precision"
);
let result_f64: Result<f64, _> = safe_usize_to_scalar(F32_FIRST_INEXACT_INT);
assert!(
result_f64.is_ok(),
"Value {F32_FIRST_INEXACT_INT} should be within f64 precision"
);
let expected_f64: f64 =
cast(F32_FIRST_INEXACT_INT).expect("This value should be exactly representable in f64");
assert_relative_eq!(result_f64.unwrap(), expected_f64, epsilon = 1e-15);
}
#[test]
fn test_safe_usize_to_scalar_consistency_with_direct_cast() {
let safe_values = [0, 1, 42, 100, 1000, 10_000, 100_000];
for &value in &safe_values {
let safe_result: Result<f64, _> = safe_usize_to_scalar(value);
assert!(safe_result.is_ok());
let safe_converted = safe_result.unwrap();
let direct_cast: f64 = cast(value).expect("Small values should convert to f64 safely");
assert_relative_eq!(safe_converted, direct_cast, epsilon = 1e-15);
}
}
#[test]
fn test_safe_usize_to_scalar_platform_independence() {
println!(
"Testing on platform with usize size: {} bytes",
std::mem::size_of::<usize>()
);
println!("usize::MAX = {}", usize::MAX);
println!("2^53-1 = {}", (1_u64 << 53) - 1);
let universal_safe_values = [0, 1, 100, 10000];
for &value in &universal_safe_values {
let result: Result<f64, _> = safe_usize_to_scalar(value);
assert!(
result.is_ok(),
"Universal safe value {value} should convert on any platform"
);
}
let _usize_max_u64 = u64::try_from(usize::MAX).unwrap_or(u64::MAX);
}
#[test]
fn test_safe_cast_to_f64_non_finite() {
let result = safe_cast_to_f64(f64::NAN, 0);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result = safe_cast_to_f64(f64::INFINITY, 1);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result = safe_cast_to_f64(f64::NEG_INFINITY, 2);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result = safe_cast_to_f64(42.5f64, 0);
assert!(result.is_ok());
assert_relative_eq!(result.unwrap(), 42.5);
}
#[test]
fn test_safe_cast_from_f64_non_finite() {
let result: Result<f64, _> = safe_cast_from_f64(f64::NAN, 0);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result: Result<f64, _> = safe_cast_from_f64(f64::INFINITY, 1);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result: Result<f64, _> = safe_cast_from_f64(f64::NEG_INFINITY, 2);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result: Result<f32, _> = safe_cast_from_f64(42.5f64, 0);
assert!(result.is_ok());
}
#[test]
fn test_safe_coords_conversion_with_non_finite() {
let coords_nan = [1.0f32, f32::NAN, 3.0f32];
let result = safe_coords_to_f64(&coords_nan);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let coords_inf = [1.0f32, 2.0f32, f32::INFINITY];
let result = safe_coords_to_f64(&coords_inf);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let coords_valid = [1.0f32, 2.0f32, 3.0f32];
let result = safe_coords_to_f64(&coords_valid);
assert!(result.is_ok());
assert_relative_eq!(
result.unwrap().as_slice(),
[1.0f64, 2.0f64, 3.0f64].as_slice()
);
}
#[test]
fn test_safe_coords_from_f64_with_non_finite() {
let coords_nan = [1.0f64, f64::NAN, 3.0f64];
let result: Result<[f32; 3], _> = safe_coords_from_f64(&coords_nan);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let coords_inf = [1.0f64, 2.0f64, f64::INFINITY];
let result: Result<[f32; 3], _> = safe_coords_from_f64(&coords_inf);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let coords_valid = [1.0f64, 2.0f64, 3.0f64];
let result: Result<[f32; 3], _> = safe_coords_from_f64(&coords_valid);
assert!(result.is_ok());
}
#[test]
fn test_safe_scalar_conversion_edge_cases() {
let result = safe_scalar_to_f64(f64::NAN);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result: Result<f32, _> = safe_scalar_from_f64(f64::NAN);
assert!(matches!(
result,
Err(CoordinateConversionError::NonFiniteValue { .. })
));
let result = safe_scalar_to_f64(42.5f32);
assert!(result.is_ok());
assert_relative_eq!(result.unwrap(), 42.5f64);
let result: Result<f32, _> = safe_scalar_from_f64(42.5f64);
assert!(result.is_ok());
}
#[test]
fn test_safe_usize_to_scalar_precision_boundary_extended() {
const MAX_PRECISE: u64 = (1_u64 << 53) - 1;
let result: Result<f64, _> =
safe_usize_to_scalar(usize::try_from(MAX_PRECISE).unwrap_or(usize::MAX));
assert!(result.is_ok());
if std::mem::size_of::<usize>() >= 8 {
let large_value = usize::try_from(MAX_PRECISE + 1).unwrap_or(usize::MAX);
let result: Result<f64, _> = safe_usize_to_scalar(large_value);
assert!(matches!(
result,
Err(CoordinateConversionError::ConversionFailed { .. })
));
}
let result: Result<f64, _> = safe_usize_to_scalar(42_usize);
assert!(result.is_ok());
assert_relative_eq!(result.unwrap(), 42.0);
let result: Result<f64, _> = safe_usize_to_scalar(0_usize);
assert!(result.is_ok());
assert_relative_eq!(result.unwrap(), 0.0);
}
#[test]
fn test_error_types_display() {
let value_error = ValueConversionError::ConversionFailed {
value: "42".to_string(),
from_type: "i32",
to_type: "u32",
details: "overflow".to_string(),
};
let display = format!("{value_error}");
assert!(display.contains("Cannot convert 42 from i32 to u32"));
let range_error = RandomPointGenerationError::InvalidRange {
min: "10.0".to_string(),
max: "5.0".to_string(),
};
let display = format!("{range_error}");
assert!(display.contains("Invalid coordinate range"));
let gen_error = RandomPointGenerationError::RandomGenerationFailed {
min: "0.0".to_string(),
max: "1.0".to_string(),
details: "test error".to_string(),
};
let display = format!("{gen_error}");
assert!(display.contains("Failed to generate random value"));
let count_error = RandomPointGenerationError::InvalidPointCount { n_points: -5 };
let display = format!("{count_error}");
assert!(display.contains("Invalid number of points: -5"));
let empty_error = CircumcenterError::EmptyPointSet;
let display = format!("{empty_error}");
assert!(display.contains("Empty point set"));
let simplex_error = CircumcenterError::InvalidSimplex {
actual: 2,
expected: 3,
dimension: 2,
};
let display = format!("{simplex_error}");
assert!(display.contains("Points do not form a valid simplex"));
}
}