use std::{
fmt::Display,
iter::Sum,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
};
use num_traits::Float;
pub trait FloatNumber:
Sized
+ Copy
+ Display
+ PartialOrd
+ Float
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ Sum
{
#[must_use]
fn from_u8(value: u8) -> Self;
#[must_use]
fn from_u16(value: u16) -> Self;
#[must_use]
fn from_u32(value: u32) -> Self;
#[must_use]
fn from_usize(value: usize) -> Self;
#[must_use]
fn from_f32(value: f32) -> Self;
#[must_use]
fn from_f64(value: f64) -> Self;
#[must_use]
fn trunc_to_u8(&self) -> u8;
#[must_use]
fn trunc_to_u32(&self) -> u32;
#[must_use]
fn trunc_to_usize(&self) -> usize;
}
impl FloatNumber for f32 {
#[inline]
fn from_u8(value: u8) -> Self {
value as f32
}
#[inline]
fn from_u16(value: u16) -> Self {
value as f32
}
#[inline]
fn from_u32(value: u32) -> Self {
value as f32
}
#[inline]
fn from_usize(value: usize) -> Self {
value as f32
}
#[inline]
fn from_f32(value: f32) -> Self {
value
}
#[inline]
fn from_f64(value: f64) -> Self {
value as f32
}
#[inline]
fn trunc_to_u8(&self) -> u8 {
*self as u8
}
#[inline]
fn trunc_to_u32(&self) -> u32 {
*self as u32
}
#[inline]
fn trunc_to_usize(&self) -> usize {
*self as usize
}
}
impl FloatNumber for f64 {
#[inline]
fn from_u8(value: u8) -> Self {
value as f64
}
#[inline]
fn from_u16(value: u16) -> Self {
value as f64
}
#[inline]
fn from_u32(value: u32) -> Self {
value as f64
}
#[inline]
fn from_usize(value: usize) -> Self {
value as f64
}
#[inline]
fn from_f32(value: f32) -> Self {
value as f64
}
#[inline]
fn from_f64(value: f64) -> Self {
value
}
#[inline]
fn trunc_to_u8(&self) -> u8 {
*self as u8
}
#[inline]
fn trunc_to_u32(&self) -> u32 {
*self as u32
}
#[inline]
fn trunc_to_usize(&self) -> usize {
*self as usize
}
}
#[inline]
#[must_use]
pub fn normalize<T>(value: T, min: T, max: T) -> T
where
T: FloatNumber,
{
debug_assert!(min < max, "min must be less than max");
(value - min) / (max - min)
}
#[inline]
#[must_use]
pub fn denormalize<T>(value: T, min: T, max: T) -> T
where
T: FloatNumber,
{
debug_assert!(min < max, "min must be less than max");
value * (max - min) + min
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[test]
fn test_float_number_f32() {
assert_eq!(f32::from_u8(128), 128.0);
assert_eq!(f32::from_u16(256), 256.0);
assert_eq!(f32::from_u32(1024), 1024.0);
assert_eq!(f32::from_usize(4096), 4096.0);
assert_eq!(f32::from_f32(0.5), 0.5);
assert_eq!(f32::from_f64(0.125), 0.125);
assert_eq!(0.125.trunc_to_u8(), 0);
assert_eq!(12.5.trunc_to_u32(), 12);
assert_eq!(10.24.trunc_to_usize(), 10);
}
#[test]
fn test_float_number_f64() {
assert_eq!(f64::from_u8(128), 128.0);
assert_eq!(f64::from_u16(256), 256.0);
assert_eq!(f64::from_u32(1024), 1024.0);
assert_eq!(f64::from_usize(4096), 4096.0);
assert_eq!(f64::from_f32(0.5), 0.5);
assert_eq!(f64::from_f64(0.125), 0.125);
assert_eq!(0.125.trunc_to_u8(), 0);
assert_eq!(12.5.trunc_to_u32(), 12);
assert_eq!(10.24.trunc_to_usize(), 10);
}
#[rstest]
#[case(0.0, 0.0)]
#[case(16.0, 0.125)]
#[case(64.0, 0.5)]
#[case(128.0, 1.0)]
fn test_normalize(#[case] value: f32, #[case] expected: f32) {
let actual = normalize(value, 0.0, 128.0);
assert_eq!(actual, expected);
}
#[cfg(debug_assertions)]
#[rstest]
#[should_panic]
#[case(128.0, 0.0)]
#[should_panic]
#[case(128.0, 128.0)]
fn test_normalize_panic(#[case] min: f32, #[case] max: f32) {
let _ = normalize(0.5, min, max);
}
#[rstest]
#[case(0.0, 0.0)]
#[case(0.125, 16.0)]
#[case(0.5, 64.0)]
#[case(1.0, 128.0)]
fn test_denormalize(#[case] value: f32, #[case] expected: f32) {
let actual = denormalize(value, 0.0, 128.0);
assert_eq!(actual, expected);
}
#[cfg(debug_assertions)]
#[rstest]
#[should_panic]
#[case(0.1, 0.0)]
#[should_panic]
#[case(0.0, 0.0)]
fn test_denormalize_panic(#[case] min: f32, #[case] max: f32) {
let _ = denormalize(0.5, min, max);
}
}