use std::fmt;
use std::ops::{Add, Div, Mul, Sub};
#[cfg(feature = "fixed-point")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "fixed-point")]
use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
pub trait StatNumeric:
Clone
+ Copy
+ PartialEq
+ PartialOrd
+ fmt::Debug
+ fmt::Display
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Default
{
fn zero() -> Self;
fn from_int(i: i64) -> Self;
fn from_f64(f: f64) -> Self;
fn to_f64(self) -> f64;
fn clamp(self, min: Self, max: Self) -> Self;
}
impl StatNumeric for f64 {
fn zero() -> Self {
0.0
}
fn from_int(i: i64) -> Self {
i as f64
}
fn from_f64(f: f64) -> Self {
f
}
fn to_f64(self) -> f64 {
self
}
fn clamp(self, min: Self, max: Self) -> Self {
self.clamp(min, max)
}
}
#[cfg(feature = "fixed-point")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FixedPoint {
value: i64,
scale: u8,
}
#[cfg(feature = "fixed-point")]
impl FixedPoint {
pub const DEFAULT_SCALE: u8 = 4;
pub fn new(value: i64, scale: u8) -> Self {
Self { value, scale }
}
pub fn from_f64_with_scale(f: f64, scale: u8) -> Self {
let multiplier = 10_i64.pow(scale as u32);
let value = (f * multiplier as f64).round() as i64;
Self { value, scale }
}
pub fn value(self) -> i64 {
self.value
}
pub fn scale(self) -> u8 {
self.scale
}
fn normalize(self, other: Self) -> (i64, i64, u8) {
let common_scale = self.scale.max(other.scale);
let scale_diff1 = common_scale as i32 - self.scale as i32;
let scale_diff2 = common_scale as i32 - other.scale as i32;
let value1 = if scale_diff1 > 0 {
self.value * 10_i64.pow(scale_diff1 as u32)
} else {
self.value
};
let value2 = if scale_diff2 > 0 {
other.value * 10_i64.pow(scale_diff2 as u32)
} else {
other.value
};
(value1, value2, common_scale)
}
}
#[cfg(feature = "fixed-point")]
impl Default for FixedPoint {
fn default() -> Self {
Self {
value: 0,
scale: Self::DEFAULT_SCALE,
}
}
}
#[cfg(feature = "fixed-point")]
impl From<f64> for FixedPoint {
fn from(f: f64) -> Self {
Self::from_f64(f)
}
}
#[cfg(feature = "fixed-point")]
impl From<FixedPoint> for f64 {
fn from(fp: FixedPoint) -> Self {
fp.to_f64()
}
}
#[cfg(feature = "fixed-point")]
impl Add for FixedPoint {
type Output = Self;
fn add(self, other: Self) -> Self {
let (v1, v2, scale) = self.normalize(other);
Self {
value: v1 + v2,
scale,
}
}
}
#[cfg(feature = "fixed-point")]
impl Sub for FixedPoint {
type Output = Self;
fn sub(self, other: Self) -> Self {
let (v1, v2, scale) = self.normalize(other);
Self {
value: v1 - v2,
scale,
}
}
}
#[cfg(feature = "fixed-point")]
impl Mul for FixedPoint {
type Output = Self;
fn mul(self, other: Self) -> Self {
let (v1, v2, scale) = self.normalize(other);
let result = ((v1 as i128 * v2 as i128) / 10_i128.pow(scale as u32)) as i64;
Self {
value: result,
scale,
}
}
}
#[cfg(feature = "fixed-point")]
impl Div for FixedPoint {
type Output = Self;
fn div(self, other: Self) -> Self {
let (v1, v2, scale) = self.normalize(other);
let result = ((v1 as i128 * 10_i128.pow(scale as u32)) / v2 as i128) as i64;
Self {
value: result,
scale,
}
}
}
#[cfg(feature = "fixed-point")]
impl AddAssign for FixedPoint {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
#[cfg(feature = "fixed-point")]
impl SubAssign for FixedPoint {
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
#[cfg(feature = "fixed-point")]
impl MulAssign for FixedPoint {
fn mul_assign(&mut self, other: Self) {
*self = *self * other;
}
}
#[cfg(feature = "fixed-point")]
impl DivAssign for FixedPoint {
fn div_assign(&mut self, other: Self) {
*self = *self / other;
}
}
#[cfg(feature = "fixed-point")]
impl StatNumeric for FixedPoint {
fn zero() -> Self {
Self {
value: 0,
scale: Self::DEFAULT_SCALE,
}
}
fn from_int(i: i64) -> Self {
Self {
value: i * 10_i64.pow(Self::DEFAULT_SCALE as u32),
scale: Self::DEFAULT_SCALE,
}
}
fn from_f64(f: f64) -> Self {
Self::from_f64_with_scale(f, Self::DEFAULT_SCALE)
}
fn to_f64(self) -> f64 {
let divisor = 10_f64.powi(self.scale as i32);
self.value as f64 / divisor
}
fn clamp(self, min: Self, max: Self) -> Self {
if self < min {
min
} else if self > max {
max
} else {
self
}
}
}
#[cfg(feature = "fixed-point")]
impl fmt::Display for FixedPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4}", StatNumeric::to_f64(*self))
}
}
#[cfg(feature = "fixed-point")]
pub type StatValue = FixedPoint;
#[cfg(not(feature = "fixed-point"))]
pub type StatValue = f64;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "fixed-point")]
#[test]
fn test_fixed_point_creation() {
let fp = FixedPoint::new(12345, 4);
assert_eq!(fp.value(), 12345);
assert_eq!(fp.scale(), 4);
}
#[cfg(feature = "fixed-point")]
#[test]
fn test_fixed_point_from_f64() {
let fp = FixedPoint::from_f64(1.2345);
assert!((fp.to_f64() - 1.2345).abs() < 0.0001);
}
#[cfg(feature = "fixed-point")]
#[test]
fn test_fixed_point_arithmetic() {
let a = FixedPoint::from_f64(10.0);
let b = FixedPoint::from_f64(5.0);
assert!((a.add(b).to_f64() - 15.0).abs() < 0.0001);
assert!((a.sub(b).to_f64() - 5.0).abs() < 0.0001);
assert!((a.mul(b).to_f64() - 50.0).abs() < 0.1); assert!((a.div(b).to_f64() - 2.0).abs() < 0.1); }
#[cfg(feature = "fixed-point")]
#[test]
fn test_fixed_point_different_scales() {
let a = FixedPoint::new(12345, 4); let b = FixedPoint::new(123456, 5);
let sum = a + b;
assert_eq!(sum.scale(), 5);
}
#[cfg(feature = "fixed-point")]
#[test]
fn test_fixed_point_overflow_safety() {
let a = FixedPoint::new(300_000_000_000, 4); let b = FixedPoint::new(30_000_000_000, 4); let result = a * b;
assert_eq!(result.value(), 900_000_000_000_000_000);
}
#[test]
fn test_stat_numeric_trait() {
#[cfg(not(feature = "fixed-point"))]
{
let zero: f64 = StatNumeric::zero();
assert_eq!(zero, 0.0);
}
#[cfg(feature = "fixed-point")]
{
let zero: FixedPoint = StatNumeric::zero();
assert_eq!(zero.value(), 0);
}
}
}