use std::fmt;
use std::ops::{Add, Mul, Neg, Sub};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Rational16 {
pub(crate) numer: i16,
pub(crate) denom: i16,
}
impl Rational16 {
pub const ZERO: Rational16 = Rational16 { numer: 0, denom: 1 };
pub const ONE: Rational16 = Rational16 { numer: 1, denom: 1 };
pub const fn new(numer: i16, denom: i16) -> Self {
if denom == 0 {
panic!("denominator cannot be zero");
}
if numer == 0 {
return Rational16 { numer: 0, denom: 1 };
}
let mut n = numer as i32;
let mut d = denom as i32;
if d < 0 {
n = -n;
d = -d;
}
rational16_from_i32(n, d)
}
pub fn checked_new(numer: i16, denom: i16) -> Result<Self, crate::error::UnitError> {
if denom == 0 {
return Err(crate::error::UnitError::ZeroDenominator);
}
Ok(Self::new(numer, denom))
}
pub const fn numer(&self) -> i16 {
self.numer
}
pub const fn denom(&self) -> i16 {
self.denom
}
pub const fn is_zero(&self) -> bool {
self.numer == 0
}
pub fn to_f64(self) -> f64 {
self.numer as f64 / self.denom as f64
}
pub const fn const_add(self, rhs: Self) -> Self {
let numer = self.numer as i32 * rhs.denom as i32 + rhs.numer as i32 * self.denom as i32;
let denom = self.denom as i32 * rhs.denom as i32;
rational16_from_i32(numer, denom)
}
pub const fn const_neg(self) -> Self {
rational16_from_i32(-(self.numer as i32), self.denom as i32)
}
pub const fn const_sub(self, rhs: Self) -> Self {
self.const_add(rhs.const_neg())
}
pub const fn const_mul(self, rhs: Self) -> Self {
let numer = self.numer as i32 * rhs.numer as i32;
let denom = self.denom as i32 * rhs.denom as i32;
rational16_from_i32(numer, denom)
}
}
const fn gcd_i32(mut a: i32, mut b: i32) -> i32 {
if a < 0 {
a = -a;
}
if b < 0 {
b = -b;
}
while b != 0 {
let t = b;
b = a % b;
a = t;
}
if a == 0 {
1
} else {
a
}
}
const fn rational16_from_i32(numer: i32, denom: i32) -> Rational16 {
if denom == 0 {
panic!("denominator cannot be zero in dimensional arithmetic");
}
let (numer, denom) = if denom < 0 {
(-numer, -denom)
} else {
(numer, denom)
};
let g = gcd_i32(numer, denom);
let numer = numer / g;
let denom = denom / g;
if numer < i16::MIN as i32 || numer > i16::MAX as i32 {
panic!("dimension exponent overflow: numerator does not fit in i16");
}
if denom < i16::MIN as i32 || denom > i16::MAX as i32 {
panic!("dimension exponent overflow: denominator does not fit in i16");
}
Rational16 {
numer: numer as i16,
denom: denom as i16,
}
}
impl Add for Rational16 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let numer = self.numer as i32 * rhs.denom as i32 + rhs.numer as i32 * self.denom as i32;
let denom = self.denom as i32 * rhs.denom as i32;
rational16_from_i32(numer, denom)
}
}
impl Sub for Rational16 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
self + (-rhs)
}
}
impl Neg for Rational16 {
type Output = Self;
fn neg(self) -> Self {
Rational16::new(-self.numer, self.denom)
}
}
impl Mul for Rational16 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let numer = self.numer as i32 * rhs.numer as i32;
let denom = self.denom as i32 * rhs.denom as i32;
rational16_from_i32(numer, denom)
}
}
impl Mul<i8> for Rational16 {
type Output = Self;
fn mul(self, rhs: i8) -> Self {
Rational16::new(self.numer * rhs as i16, self.denom)
}
}
impl fmt::Debug for Rational16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.denom == 1 {
write!(f, "{}", self.numer)
} else {
write!(f, "{}/{}", self.numer, self.denom)
}
}
}
impl fmt::Display for Rational16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.denom == 1 {
write!(f, "{}", self.numer)
} else {
write!(f, "{}/{}", self.numer, self.denom)
}
}
}
impl From<i8> for Rational16 {
fn from(n: i8) -> Self {
Rational16::new(n as i16, 1)
}
}
impl From<i16> for Rational16 {
fn from(n: i16) -> Self {
Rational16::new(n, 1)
}
}
impl From<i32> for Rational16 {
fn from(n: i32) -> Self {
let numer = i16::try_from(n)
.unwrap_or_else(|_| panic!("value {} does not fit in Rational16 (i16 range)", n));
Rational16::new(numer, 1)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Dimension {
pub length: Rational16,
pub time: Rational16,
pub mass: Rational16,
pub current: Rational16,
pub temperature: Rational16,
pub angle: Rational16,
pub solid_angle: Rational16,
pub luminous_intensity: Rational16,
pub magnitude: Rational16,
pub amount: Rational16,
pub photon: Rational16,
}
impl Dimension {
pub const DIMENSIONLESS: Dimension = Dimension {
length: Rational16::ZERO,
time: Rational16::ZERO,
mass: Rational16::ZERO,
current: Rational16::ZERO,
temperature: Rational16::ZERO,
angle: Rational16::ZERO,
solid_angle: Rational16::ZERO,
luminous_intensity: Rational16::ZERO,
magnitude: Rational16::ZERO,
amount: Rational16::ZERO,
photon: Rational16::ZERO,
};
pub const LENGTH: Dimension = Dimension {
length: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const TIME: Dimension = Dimension {
time: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const MASS: Dimension = Dimension {
mass: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const CURRENT: Dimension = Dimension {
current: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const TEMPERATURE: Dimension = Dimension {
temperature: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const ANGLE: Dimension = Dimension {
angle: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const SOLID_ANGLE: Dimension = Dimension {
solid_angle: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const LUMINOUS_INTENSITY: Dimension = Dimension {
luminous_intensity: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const MAGNITUDE: Dimension = Dimension {
magnitude: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const AMOUNT: Dimension = Dimension {
amount: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const PHOTON: Dimension = Dimension {
photon: Rational16::ONE,
..Self::DIMENSIONLESS
};
pub const fn is_dimensionless(&self) -> bool {
self.length.numer == 0
&& self.time.numer == 0
&& self.mass.numer == 0
&& self.current.numer == 0
&& self.temperature.numer == 0
&& self.angle.numer == 0
&& self.solid_angle.numer == 0
&& self.luminous_intensity.numer == 0
&& self.magnitude.numer == 0
&& self.amount.numer == 0
&& self.photon.numer == 0
}
pub const fn mul(&self, other: &Dimension) -> Dimension {
Dimension {
length: self.length.const_add(other.length),
time: self.time.const_add(other.time),
mass: self.mass.const_add(other.mass),
current: self.current.const_add(other.current),
temperature: self.temperature.const_add(other.temperature),
angle: self.angle.const_add(other.angle),
solid_angle: self.solid_angle.const_add(other.solid_angle),
luminous_intensity: self.luminous_intensity.const_add(other.luminous_intensity),
magnitude: self.magnitude.const_add(other.magnitude),
amount: self.amount.const_add(other.amount),
photon: self.photon.const_add(other.photon),
}
}
pub const fn div(&self, other: &Dimension) -> Dimension {
Dimension {
length: self.length.const_sub(other.length),
time: self.time.const_sub(other.time),
mass: self.mass.const_sub(other.mass),
current: self.current.const_sub(other.current),
temperature: self.temperature.const_sub(other.temperature),
angle: self.angle.const_sub(other.angle),
solid_angle: self.solid_angle.const_sub(other.solid_angle),
luminous_intensity: self.luminous_intensity.const_sub(other.luminous_intensity),
magnitude: self.magnitude.const_sub(other.magnitude),
amount: self.amount.const_sub(other.amount),
photon: self.photon.const_sub(other.photon),
}
}
pub const fn pow(&self, power: Rational16) -> Dimension {
Dimension {
length: self.length.const_mul(power),
time: self.time.const_mul(power),
mass: self.mass.const_mul(power),
current: self.current.const_mul(power),
temperature: self.temperature.const_mul(power),
angle: self.angle.const_mul(power),
solid_angle: self.solid_angle.const_mul(power),
luminous_intensity: self.luminous_intensity.const_mul(power),
magnitude: self.magnitude.const_mul(power),
amount: self.amount.const_mul(power),
photon: self.photon.const_mul(power),
}
}
pub const fn inv(&self) -> Dimension {
self.pow(Rational16::new(-1, 1))
}
}
impl fmt::Debug for Dimension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_dimensionless() {
return write!(f, "dimensionless");
}
let mut parts = Vec::new();
let dims = [
("L", self.length),
("T", self.time),
("M", self.mass),
("I", self.current),
("Θ", self.temperature),
("A", self.angle),
("Ω", self.solid_angle),
("J", self.luminous_intensity),
("mag", self.magnitude),
("N", self.amount),
("ph", self.photon),
];
for (name, exp) in dims {
if !exp.is_zero() {
if exp == Rational16::ONE {
parts.push(name.to_string());
} else {
parts.push(format!("{}^{}", name, exp));
}
}
}
write!(f, "{}", parts.join(" "))
}
}
impl fmt::Display for Dimension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rational_basic() {
let r = Rational16::new(2, 4);
assert_eq!(r.numer, 1);
assert_eq!(r.denom, 2);
}
#[test]
fn test_rational_negative_denom() {
let r = Rational16::new(1, -2);
assert_eq!(r.numer, -1);
assert_eq!(r.denom, 2);
}
#[test]
fn test_rational_add() {
let a = Rational16::new(1, 2);
let b = Rational16::new(1, 3);
let c = a + b;
assert_eq!(c.numer, 5);
assert_eq!(c.denom, 6);
}
#[test]
fn test_dimension_velocity() {
let velocity = Dimension::LENGTH.div(&Dimension::TIME);
assert_eq!(velocity.length, Rational16::ONE);
assert_eq!(velocity.time, Rational16::new(-1, 1));
}
#[test]
fn test_dimension_energy() {
let energy = Dimension::MASS
.mul(&Dimension::LENGTH.pow(Rational16::new(2, 1)))
.mul(&Dimension::TIME.pow(Rational16::new(-2, 1)));
assert_eq!(energy.mass, Rational16::ONE);
assert_eq!(energy.length, Rational16::new(2, 1));
assert_eq!(energy.time, Rational16::new(-2, 1));
}
#[test]
fn test_dimensionless() {
let d = Dimension::LENGTH.div(&Dimension::LENGTH);
assert!(d.is_dimensionless());
}
#[test]
fn test_rational_large_denominators() {
let a = Rational16::new(1, 12);
let b = Rational16::new(1, 12);
let c = a + b;
assert_eq!(c.numer, 1);
assert_eq!(c.denom, 6);
}
#[test]
fn test_rational_multiply_large() {
let a = Rational16::new(50, 1);
let b = Rational16::new(50, 1);
let c = a * b;
assert_eq!(c.numer, 2500);
assert_eq!(c.denom, 1);
}
#[test]
fn test_rational_complex_fraction() {
let a = Rational16::new(1, 15);
let b = Rational16::new(1, 15);
let c = a * b;
assert_eq!(c.numer, 1);
assert_eq!(c.denom, 225);
}
#[test]
fn test_dimension_high_power() {
let high_power = Dimension::LENGTH.pow(Rational16::new(100, 1));
assert_eq!(high_power.length.numer, 100);
assert_eq!(high_power.length.denom, 1);
}
#[test]
fn test_rational_add_reduces_before_cast() {
let a = Rational16::new(1, 200);
let b = Rational16::new(1, 200);
let c = a + b;
assert_eq!(c.numer, 1);
assert_eq!(c.denom, 100);
}
#[test]
#[should_panic(expected = "dimension exponent overflow")]
fn test_rational_add_overflow_panics() {
let a = Rational16::new(i16::MAX, 1);
let b = Rational16::new(i16::MAX, 1);
let _ = a + b;
}
#[test]
#[should_panic(expected = "dimension exponent overflow")]
fn test_rational_mul_overflow_panics() {
let a = Rational16::new(i16::MAX, 1);
let b = Rational16::new(2, 1);
let _ = a * b;
}
#[test]
fn test_rational_mul_reduces_before_cast() {
let a = Rational16::new(200, 1);
let b = Rational16::new(1, 200);
let c = a * b;
assert_eq!(c.numer, 1);
assert_eq!(c.denom, 1);
}
#[test]
#[should_panic(expected = "does not fit")]
fn test_rational_from_i32_overflow_panics() {
let _ = Rational16::from(50000i32);
}
#[test]
fn test_checked_new_zero_denom() {
let result = Rational16::checked_new(1, 0);
assert!(result.is_err());
}
#[test]
fn test_checked_new_valid() {
let result = Rational16::checked_new(3, 6);
assert!(result.is_ok());
let r = result.unwrap();
assert_eq!(r.numer, 1);
assert_eq!(r.denom, 2);
}
}