use core::fmt::{Debug, Display};
use num_traits::float::FloatCore;
use num_traits::{PrimInt, ToPrimitive};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Primant(u32);
impl Primant {
pub const MIN: Primant = Primant(0);
pub const ZERO: Primant = Primant(0);
pub const MAX: Primant = Primant(u32::MAX);
pub fn from_raw(value: u32) -> Self {
Primant(value)
}
pub fn to_raw(self) -> u32 {
self.0
}
}
impl TryFrom<f32> for Primant {
type Error = ();
fn try_from(value: f32) -> Result<Self, Self::Error> {
if !(0.0..=1.0).contains(&value) {
Err(())
} else {
Ok(Primant((value * u32::MAX as f32) as u32))
}
}
}
impl TryFrom<f64> for Primant {
type Error = ();
fn try_from(value: f64) -> Result<Self, Self::Error> {
if !(0.0..=1.0).contains(&value) {
Err(())
} else {
Ok(Primant((value * u32::MAX as f64) as u32))
}
}
}
impl From<Primant> for f32 {
fn from(value: Primant) -> Self {
value.0 as f32 / u32::MAX as f32
}
}
impl From<Primant> for f64 {
fn from(value: Primant) -> Self {
value.0 as f64 / u32::MAX as f64
}
}
impl Primant {
pub fn from_float<T: FloatCore>(value: T) -> Self {
assert!(value >= T::zero() && value <= T::one(), "value must be in the range 0.0..=1.0");
Primant((value * T::from(u32::MAX).unwrap()).to_u32().unwrap())
}
pub fn try_from_float<T: FloatCore>(value: T) -> Option<Self> {
if value < T::zero() || value > T::one() { return None; }
let value = (value * T::from(u32::MAX)?).to_u32()?;
Some(Primant(value))
}
pub fn from_float_saturating<T: FloatCore>(value: T) -> Self {
Primant((value.clamp(T::zero(), T::one()) * T::from(u32::MAX).unwrap()).to_u32().unwrap())
}
pub fn into_float<T: FloatCore>(self) -> T {
T::from(self.0).unwrap() / T::from(u32::MAX).unwrap()
}
}
impl Primant {
pub fn from_ratio<T: PrimInt + Debug>(numerator: T, denominator: T) -> Self {
assert_ne!(denominator, T::zero(), "denominator must not be zero");
assert!(numerator <= denominator, "numerator must not be greater than the denominator");
let numerator = numerator.to_u64().unwrap();
let denominator = denominator.to_u64().unwrap();
let value = numerator * u32::MAX as u64 / denominator;
Primant(value.to_u32().unwrap())
}
pub fn try_from_ratio<T: PrimInt + Debug>(numerator: T, denominator: T) -> Option<Self> {
if denominator.is_zero() || numerator > denominator { return None; }
Some(Self::from_ratio(numerator, denominator))
}
pub fn from_ratio_saturating(numerator: u32, denominator: u32) -> Self {
assert_ne!(denominator, 0, "denominator must not be zero");
Primant(numerator.saturating_mul(u32::MAX / denominator))
}
}
impl Primant {
pub fn to_percentage<T: FloatCore>(self) -> T {
self.into_float::<T>() * T::from(100).unwrap()
}
pub fn from_percentage<T: FloatCore>(percentage: T) -> Self {
Self::from_float(percentage / T::from(100).unwrap())
}
pub fn try_from_percentage<T: FloatCore>(percentage: T) -> Option<Self> {
Self::try_from_float(percentage / T::from(100)?)
}
pub fn from_percentage_saturating<T: FloatCore>(percentage: T) -> Self {
let value = percentage / T::from(100).unwrap();
Self::from_float(value.clamp(T::zero(), T::one()))
}
}
impl Debug for Primant {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Primant({})", f64::from(*self))
}
}
impl Display for Primant {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:.2}%", f64::from(*self) * 100.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
extern crate alloc;
use alloc::format;
use core::str::FromStr;
use std::println;
const EPSILON: f64 = 1e-6;
fn assert_approx_eq<T: Into<f64>>(a: T, b: T) {
assert!((a.into() - b.into()).abs() < EPSILON);
}
#[test]
fn test_f64_conversion() {
let fraction = Primant::try_from(0.5f64).unwrap();
let f: f64 = fraction.into();
assert_approx_eq(f, 0.5);
}
#[test]
fn test_f32_conversion() {
let fraction = Primant::try_from(0.5f32).unwrap();
let f: f32 = fraction.into();
assert_approx_eq(f, 0.5);
}
#[test]
fn test_into_percent() {
let fraction = Primant::try_from(0.5f64).unwrap();
let percent = fraction.to_percentage();
assert_approx_eq(percent, 50.0);
}
#[test]
fn test_from_percent() {
let fraction = Primant::try_from_percentage(50.0).unwrap();
assert_approx_eq(fraction.into_float(), 0.5);
}
#[test]
fn test_from_ratio() {
let fraction = Primant::try_from_ratio(1u32, 2u32).unwrap();
assert_approx_eq(fraction.into_float(), 0.5);
}
#[test]
fn test_debug() {
let fraction = Primant::try_from(0.5f64).unwrap();
let output = format!("{:?}", fraction);
println!("{}", output);
assert_eq!(&output[0..8], "Primant(");
assert_eq!(&output[output.len() - 1..], ")");
let value_str = &output[8..output.len() - 1];
assert_approx_eq(f64::from_str(value_str).unwrap(), 0.5);
}
#[test]
fn test_display() {
let fraction = Primant::try_from(0.5f64).unwrap();
let output = format!("{}", fraction);
println!("{}", output);
assert_eq!(format!("{}", fraction), "50.00%");
}
}