#![no_std]
#![cfg_attr(feature = "nightly", feature(generic_const_exprs))]
pub type TenRat = Rational<{ i64::MAX / 16 }>;
pub type HundRat = Rational<{ i64::MAX / 128 }>;
#[cfg(feature = "serde1")]
use serde as sd;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
#[cfg_attr(feature = "serde1", derive(sd::Serialize, sd::Deserialize))]
pub struct Rational<const DENOM: i64> {
numer: i64,
}
impl<const DENOM: i64> Rational<DENOM> {
pub fn to_storage(self) -> i64 {
self.numer
}
pub fn from_storage(storage: i64) -> Self {
Self { numer: storage }
}
pub fn from_int(i: i64) -> Self {
Self { numer: i * DENOM }
}
pub fn aprox_float_fast(f: f64) -> Option<Self> {
use core::num::FpCategory as Cat;
if let Cat::Subnormal | Cat::Normal | Cat::Zero = f.classify() {
} else {
return None;
}
let expanded = f * (DENOM as f64);
Some(Self {
numer: expanded as i64,
})
}
#[doc(hidden)]
pub fn aprox_float(f: f64) -> Option<Self> {
use core::num::FpCategory as Cat;
match f.classify() {
Cat::Subnormal | Cat::Normal => {}
Cat::Zero => return Self::from(0).into(),
_ => return None,
}
use num_traits::float::FloatCore;
let (mant, f_exp, sign) = f.integer_decode();
let d_exp = 64 - (DENOM as u64).leading_zeros();
let exp = f_exp + (d_exp as i16);
let neg = exp.is_negative();
let exp = exp.abs() as u32;
let numer = if !neg {
if mant.leading_zeros() < exp {
return None;
}
mant << exp
} else {
mant >> exp
};
let numer = numer as i64;
let numer = if sign.is_negative() { -numer } else { numer };
Self { numer }.into()
}
pub fn to_f64(self) -> f64 {
self.numer as f64 / DENOM as f64
}
pub fn to_i64(self) -> i64 {
self.numer / DENOM
}
pub fn clamp(self, low: Self, high: Self) -> Self {
self.max(low).min(high)
}
pub const fn max() -> Self {
Self { numer: i64::MAX }
}
pub const fn min() -> Self {
Self { numer: i64::MIN }
}
pub fn checked_add(self, other: Self) -> Option<Self> {
Self {
numer: self.numer.checked_add(other.numer)?,
}
.into()
}
pub fn checked_mul(self, other: Self) -> Option<Self> {
Self {
numer: self.numer.checked_mul(other.numer)?,
}
.into()
}
pub fn checked_sub(self, other: Self) -> Option<Self> {
Self {
numer: self.numer.checked_sub(other.numer)?,
}
.into()
}
pub fn checked_div(self, other: Self) -> Option<Self> {
Self {
numer: self.numer.checked_div(other.numer)?,
}
.into()
}
pub fn wrapping_add(self, other: Self) -> Self {
Self {
numer: self.numer.wrapping_add(other.numer),
}
}
pub fn wrapping_mul(self, other: i64) -> Self {
Self {
numer: self.numer.wrapping_mul(other),
}
}
pub fn wrapping_sub(self, other: Self) -> Self {
Self {
numer: self.numer.wrapping_sub(other.numer),
}
}
pub fn wrapping_div(self, other: i64) -> Self {
Self {
numer: self.numer.wrapping_div(other),
}
}
pub fn saturating_add(self, other: Self) -> Self {
Self {
numer: self.numer.saturating_add(other.numer),
}
}
pub fn saturating_mul(self, other: i64) -> Self {
Self {
numer: self.numer.saturating_mul(other),
}
}
pub fn saturating_sub(self, other: Self) -> Self {
Self {
numer: self.numer.saturating_sub(other.numer),
}
}
}
impl<const DENOM: i64> From<f64> for Rational<DENOM> {
fn from(o: f64) -> Self {
Self::aprox_float_fast(o).unwrap()
}
}
impl<const DENOM: i64> From<i64> for Rational<DENOM> {
fn from(o: i64) -> Self {
Self::from_int(o)
}
}
impl<const DENOM: i64> core::ops::Add for Rational<DENOM> {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
numer: self.numer + other.numer,
}
}
}
impl<const DENOM: i64> core::ops::Sub for Rational<DENOM> {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
numer: self.numer - other.numer,
}
}
}
impl<const DENOM: i64> core::ops::Mul<i64> for Rational<DENOM> {
type Output = Self;
fn mul(self, other: i64) -> Self {
Self {
numer: self.numer * other,
}
}
}
impl<const DENOM: i64> core::ops::Div<i64> for Rational<DENOM> {
type Output = Self;
fn div(self, other: i64) -> Self {
Self {
numer: self.numer / other,
}
}
}
impl<const DENOM: i64> core::iter::Sum for Rational<DENOM> {
fn sum<I>(i: I) -> Self
where
I: Iterator<Item = Self>,
{
i.fold(Self::from(0), |sum, new| sum + new)
}
}
unsafe impl<const DENOM: i64> bytemuck::NoUninit for Rational<DENOM> {}
#[cfg(feature = "nightly")]
impl<const DENOMS: i64, const DENOMO: i64> core::ops::Mul<Rational<DENOMO>> for Rational<DENOMS>
where
[(); { DENOMS * DENOMO } as usize]:,
{
type Output = Rational<{ DENOMS * DENOMO }>;
fn mul(self, other: Rational<DENOMO>) -> Self::Output {
Self::Output {
numer: self.numer * other.numer,
}
}
}
#[test]
fn converts() {
let _tenrat: TenRat = 0.0.into();
let _tenrat: TenRat = 1.0.into();
let _tenrat: TenRat = (-1.0).into();
let _tenrat: TenRat = 10.0.into();
let _tenrat: TenRat = (-10.0).into();
}
#[test]
fn maths() {
let num: Rational<{ 1 << 30 }> = 0.1.into();
let add = (num + num).to_f64() - 0.2;
assert!(add.abs() < 0.00001);
let sub = (num - num - num).to_f64() + 0.1;
assert!(sub.abs() < 0.00001);
#[cfg(feature = "nightly")]
{
let bignum: Rational<{ 1 << 30 }> = 10.0.into();
let mul = (num * num).to_f64() - 0.01;
assert!(mul.abs() < 0.00001);
let mul2 = (num * bignum).to_f64() - 1.0;
assert!(mul2.abs() < 0.00001);
}
}
#[test]
fn precision() {
type R = Rational<{ i64::MAX / (1 << 10) }>;
extern crate std;
use std::dbg;
let f = 640.132143234189097_f64;
let r: R = Rational::aprox_float(f).unwrap();
let r2: R = Rational::aprox_float_fast(f).unwrap();
let rf = r.to_f64();
let rl = r2.to_f64();
let absdiff = (f - rf).abs();
dbg!(f, r, r2, rf, rl, absdiff);
assert!(absdiff < 1e20);
}
#[test]
fn displaytest() {
let tenrat: TenRat = (-10.0).into();
extern crate std;
use std::println;
println!("{:#?}", tenrat);
}
#[cfg(feature = "serde1")]
#[test]
fn serde_test() {
let r: TenRat = 3.0.into();
use bincode;
let s = bincode::serde::encode_to_vec(&r, bincode::config::standard()).unwrap();
let d = bincode::serde::decode_from_slice(&s, bincode::config::standard()).unwrap();
assert_eq!(r, d.0);
}