use num_traits::{AsPrimitive, Float};
use crate::reg::RegInt;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FixedPoint<P, const I: isize, const F: isize> {
val: P,
}
impl<P, const I: isize, const F: isize> core::fmt::Debug for FixedPoint<P, I, F>
where
P: RegInt + AsPrimitive<f64>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use core::fmt::Write as _;
let mut name: heapless::String<32> = heapless::String::new();
write!(&mut name, "FixedPoint<{I},{F}>")
.expect("Fixedpoint type name should fit in small buffer");
f.debug_struct(&name)
.field("int", &self.val)
.field("real", &self.to_f64())
.finish()
}
}
impl<P, const I: isize, const F: isize> FixedPoint<P, I, F>
where
P: RegInt,
{
#[must_use]
pub fn from_bits(bits: P) -> Self {
const {
assert!(
I + F <= 8 * core::mem::size_of::<P>().cast_signed(),
"The primitive integer type is not wide enough for this fixed-point representation"
);
assert!(I + F > 0, "The fixed-point bit width must be positive");
}
assert!(
(bits <= Self::max_bits()) && (bits >= Self::min_bits()),
"The provided bits overflow this fixed-point representation"
);
Self { val: bits }
}
#[must_use]
pub const fn to_bits(self) -> P {
self.val
}
#[must_use]
pub const fn intwidth() -> isize {
I
}
#[must_use]
pub const fn fracwidth() -> isize {
F
}
#[must_use]
pub const fn width() -> usize {
(I + F).cast_unsigned()
}
#[must_use]
pub fn is_signed() -> bool {
P::min_value() < P::zero()
}
#[must_use]
pub fn zero() -> Self {
Self::from_bits(P::zero())
}
#[must_use]
fn max_bits() -> P {
let unused_bits = core::mem::size_of::<P>() * 8 - Self::width();
P::max_value().shr(unused_bits)
}
#[must_use]
fn min_bits() -> P {
let unused_bits = core::mem::size_of::<P>() * 8 - Self::width();
P::min_value().shr(unused_bits)
}
#[must_use]
pub fn max_value() -> Self {
Self::from_bits(Self::max_bits())
}
#[must_use]
pub fn min_value() -> Self {
Self::from_bits(Self::min_bits())
}
#[must_use]
pub fn resolution() -> Self {
Self::from_bits(P::one())
}
pub fn quantize<T>(value: T) -> T
where
T: Float + 'static,
P: AsPrimitive<T>,
{
Self::from_float(value).to_float()
}
#[must_use]
pub fn from_f32(value: f32) -> Self
where
P: AsPrimitive<f32>,
{
Self::from_float(value)
}
#[must_use]
pub fn from_f64(value: f64) -> Self
where
P: AsPrimitive<f64>,
{
Self::from_float(value)
}
#[must_use]
fn from_float<T>(value: T) -> Self
where
T: Float + 'static,
P: AsPrimitive<T>,
{
assert!(!value.is_nan(), "Can't convert NaN to FixedPoint");
#[allow(clippy::cast_possible_truncation)]
let scale = T::from(2)
.expect("two can be represented by any float type")
.powi(F as i32);
let scaled_value = value * scale;
if scaled_value >= P::max_value().as_() {
Self::from_bits(P::max_value())
} else if scaled_value <= P::min_value().as_() {
Self::from_bits(P::min_value())
} else {
Self::from_bits(
P::from(scaled_value.round()).expect("shouldn't be NaN or out of range"),
)
}
}
#[must_use]
pub fn to_f32(self) -> f32
where
P: AsPrimitive<f32>,
{
self.to_float()
}
#[must_use]
pub fn to_f64(self) -> f64
where
P: AsPrimitive<f64>,
{
self.to_float()
}
#[must_use]
fn to_float<T>(self) -> T
where
T: Float + 'static,
P: AsPrimitive<T>,
{
#[allow(clippy::cast_possible_truncation)]
let scale = T::from(2)
.expect("two can be represented by any float type")
.powi(-F as i32);
self.val.as_() * scale
}
}
impl<T, P, const I: isize, const F: isize> From<T> for FixedPoint<P, I, F>
where
T: Float + 'static,
P: RegInt + AsPrimitive<T>,
{
fn from(value: T) -> Self {
Self::from_float(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_float() {
assert_eq!(FixedPoint::<u16, 8, 2>::from_float(2.25).to_bits(), 9);
assert_eq!(FixedPoint::<i16, 8, 4>::from_float(-1.5).to_bits(), -24);
assert_eq!(FixedPoint::<u8, 4, 4>::from_float(0.0625).to_bits(), 1);
assert_eq!(FixedPoint::<u8, 4, 4>::from_float(0.03124).to_bits(), 0); assert_eq!(FixedPoint::<u8, 4, 4>::from_float(0.03125).to_bits(), 1); assert_eq!(FixedPoint::<u8, 4, 4>::from_float(0.03126).to_bits(), 1); assert_eq!(FixedPoint::<i8, 4, 4>::from_float(-0.03124).to_bits(), 0); assert_eq!(FixedPoint::<i8, 4, 4>::from_float(-0.03125).to_bits(), -1); assert_eq!(FixedPoint::<i8, 4, 4>::from_float(-0.03126).to_bits(), -1);
assert_eq!(
FixedPoint::<u8, 4, 4>::from_float(100.0),
FixedPoint::<u8, 4, 4>::max_value()
);
assert_eq!(
FixedPoint::<i8, 4, 4>::from_float(100.0),
FixedPoint::<i8, 4, 4>::max_value()
);
assert_eq!(
FixedPoint::<u8, 4, 4>::from_float(-100.0),
FixedPoint::<u8, 4, 4>::min_value()
);
assert_eq!(
FixedPoint::<i8, 4, 4>::from_float(-100.0),
FixedPoint::<i8, 4, 4>::min_value()
);
}
#[test]
#[should_panic(expected = "Can't convert NaN to FixedPoint")]
fn test_from_float_nan_panic() {
let _ = FixedPoint::<u8, 4, 4>::from_float(f64::NAN);
}
#[test]
#[should_panic(expected = "The provided bits overflow this fixed-point representation")]
fn test_positive_overflow1() {
let _ = FixedPoint::<u8, 2, 4>::from_bits(64);
}
#[test]
#[should_panic(expected = "The provided bits overflow this fixed-point representation")]
fn test_negative_overflow() {
let _ = FixedPoint::<i8, 2, 4>::from_bits(-33);
}
}