#![no_std]
#![cfg_attr(feature = "bench", feature(test))]
#[cfg(feature = "std")]
extern crate std;
use core::fmt;
use core::num::ParseIntError;
mod r32_t;
mod r64_t;
pub use r32_t::r32;
pub use r64_t::r64;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseRatioErr {
Empty,
Overflow,
Numerator(ParseIntError),
Denominator(ParseIntError),
}
impl fmt::Display for ParseRatioErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseRatioErr::Empty =>
f.write_str("cannot parse rational from empty string"),
ParseRatioErr::Overflow =>
f.write_str("numbers are too large to fit in fraction"),
ParseRatioErr::Numerator(pie) =>
write!(f, "numerator error: {}", pie),
ParseRatioErr::Denominator(pie) =>
write!(f, "denominator error: {}", pie),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseRatioErr {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseRatioErr::Numerator(pie) => Some(pie),
ParseRatioErr::Denominator(pie) => Some(pie),
_ => None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TryFromRatioError;
#[macro_export]
macro_rules! r32 {
($numer:literal) => { r32!($numer / 1) };
($numer:literal / $denom:literal) => {
r32::new($numer, $denom).expect("literal out of range for `r32`")
};
}
#[macro_export]
macro_rules! r64 {
($numer:literal) => { r64!($numer / 1) };
($numer:literal / $denom:literal) => {
r64::new($numer, $denom).expect("literal out of range for `r64`")
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_ratio_traits {
($name:ident $uint:ident $int:ident $nzuint:ident) => {
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_nan() {
return f.write_str("NaN");
}
let norm = self.normalize();
norm.numer().fmt(f)?;
if norm.denom_size() > 0 {
write!(f, "/{}", norm.denom())?;
}
Ok(())
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_nan() {
f.write_str("NaN")
} else {
write!(f, "{}/{}", self.numer(), self.denom())
}
}
}
impl FromStr for $name {
type Err = ParseRatioErr;
fn from_str(src: &str) -> Result<Self, Self::Err> {
use core::num::$nzuint;
if src.is_empty() {
return Err(ParseRatioErr::Empty);
}
if src == "NaN" {
return Ok($name::NAN);
}
let bar_pos = src.find('/');
let numer_end = bar_pos.unwrap_or(src.len());
let numerator = src[..numer_end]
.parse::<$int>()
.map_err(ParseRatioErr::Numerator)?;
let denominator = bar_pos
.map(|pos|
src[pos+1..]
.parse::<$nzuint>()
.map_err(ParseRatioErr::Denominator)
) .transpose()?
.map($nzuint::get)
.unwrap_or(1);
$name::new(numerator, denominator)
.ok_or(ParseRatioErr::Overflow)
}
}
impl From<u8> for $name {
#[inline]
fn from(v: u8) -> Self { $name(v as $uint) }
}
impl From<i8> for $name {
#[inline]
fn from(v: i8) -> Self {
unsafe { $name::new_unchecked(v as $int, 1) }
}
}
impl PartialEq for $name {
#[inline]
fn eq(&self, other: &$name) -> bool {
self.is_nan() && other.is_nan()
|| self.normalize().0 == other.normalize().0
}
}
impl Neg for $name {
type Output = $name;
#[inline]
fn neg(self) -> Self::Output {
self.checked_neg().expect("attempt to negate with overflow")
}
}
impl AddAssign for $name {
#[inline]
fn add_assign(&mut self, other: $name) {
*self = *self + other
}
}
impl Sub for $name {
type Output = $name;
#[inline]
fn sub(self, other: $name) -> Self::Output {
self + (-other)
}
}
impl SubAssign for $name {
#[inline]
fn sub_assign(&mut self, other: $name) {
*self = *self - other
}
}
impl MulAssign for $name {
#[inline]
fn mul_assign(&mut self, other: $name) {
*self = *self * other
}
}
impl Div for $name {
type Output = $name;
#[inline]
fn div(self, other: $name) -> Self::Output {
self * other.recip()
}
}
impl DivAssign for $name {
#[inline]
fn div_assign(&mut self, other: $name) {
*self = *self / other
}
}
impl Rem for $name {
type Output = $name;
#[inline]
fn rem(self, other: $name) -> Self::Output {
(self / other).fract() * other
}
}
impl RemAssign for $name {
#[inline]
fn rem_assign(&mut self, other: $name) {
*self = *self % other
}
}
}}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_ratio_tests {
($ratio:ident) => {
#[cfg(test)]
mod tests {
#[cfg(feature = "bench")]
extern crate test;
use super::*;
use crate::$ratio;
#[test]
fn checked_pow() {
assert_eq!($ratio(2).checked_pow(FRACTION_SIZE as i32), None);
}
#[test]
fn trunc() {
assert_eq!($ratio::NAN.trunc(), $ratio::NAN);
assert_eq!($ratio(5).trunc(), $ratio(5));
assert_eq!($ratio!( 1/2).trunc(), $ratio(0));
assert_eq!($ratio!(-1/2).trunc(), $ratio(0));
assert_eq!($ratio!( 3/2).trunc(), $ratio(1));
assert_eq!($ratio!(-3/2).trunc(), $ratio!(-1));
}
#[test]
fn fract() {
assert_eq!($ratio::NAN.fract(), $ratio::NAN);
assert_eq!($ratio(5).fract(), $ratio(0));
assert_eq!($ratio!( 3/2).fract(), $ratio!( 1/2));
assert_eq!($ratio!(-3/2).fract(), $ratio!(-1/2));
}
#[test]
fn floor() {
assert_eq!($ratio::NAN.floor(), $ratio::NAN);
assert_eq!($ratio(1).floor(), $ratio(1));
assert_eq!($ratio!(-1).floor(), $ratio!(-1));
assert_eq!($ratio!( 3/2).floor(), $ratio(1));
assert_eq!($ratio!(-3/2).floor(), $ratio!(-2));
}
#[test]
fn ceil() {
assert_eq!($ratio::NAN.ceil(), $ratio::NAN);
assert_eq!($ratio(1).ceil(), $ratio(1));
assert_eq!($ratio!(-1).ceil(), $ratio!(-1));
assert_eq!($ratio!( 3/2).ceil(), $ratio(2));
assert_eq!($ratio!(-3/2).ceil(), $ratio!(-1));
}
#[test]
fn round() {
assert_eq!($ratio::NAN.round(), $ratio::NAN);
assert_eq!($ratio(1).round(), $ratio(1));
assert_eq!($ratio!(-1).round(), $ratio!(-1));
assert_eq!($ratio!( 3/2).round(), $ratio(2));
assert_eq!($ratio!(-3/2).round(), $ratio!(-2));
}
#[test]
fn min() {
assert_eq!($ratio::NAN.min($ratio::NAN), $ratio::NAN);
assert_eq!($ratio::NAN.min($ratio(0)), $ratio(0));
assert_eq!($ratio(0).min($ratio::NAN), $ratio(0));
assert_eq!($ratio(0).min($ratio(1)), $ratio(0));
}
#[test]
fn max() {
assert_eq!($ratio::NAN.max($ratio::NAN), $ratio::NAN);
assert_eq!($ratio::NAN.max($ratio(0)), $ratio(0));
assert_eq!($ratio(0).max($ratio::NAN), $ratio(0));
assert_eq!($ratio(0).max($ratio(1)), $ratio(1));
}
#[test]
fn abs() {
assert_eq!($ratio::NAN.abs(), $ratio::NAN);
assert_eq!($ratio(0).abs(), $ratio(0));
assert_eq!($ratio(1).abs(), $ratio(1));
assert_eq!($ratio!(-1).abs(), $ratio(1));
}
#[test]
fn signum() {
assert_eq!($ratio::NAN.signum(), $ratio::NAN);
assert_eq!($ratio(0).signum(), $ratio(0));
assert_eq!($ratio(1).signum(), $ratio(1));
assert_eq!($ratio(2).signum(), $ratio(1));
assert_eq!($ratio!(-1).signum(), $ratio!(-1));
assert_eq!($ratio!(-2).signum(), $ratio!(-1));
}
#[test]
fn recip() {
assert_eq!($ratio::NAN.recip(), $ratio::NAN);
assert_eq!($ratio(5).recip(), $ratio!(1/5));
assert_eq!($ratio!(5/2).recip(), $ratio!(2/5));
assert_eq!($ratio(1).recip(), $ratio(1));
}
#[test]
fn normalize() {
assert_eq!($ratio!( 4 / 2).normalize(), $ratio!( 2));
assert_eq!($ratio!(-4 / 2).normalize(), $ratio!(-2));
}
#[test]
fn pow() {
assert_eq!( $ratio::NAN.pow(0), $ratio(1) );
assert_eq!( $ratio(0).pow(0), $ratio(1) );
assert_eq!( $ratio(1).pow(1), $ratio(1) );
assert_eq!( $ratio(3).pow( 2), $ratio(9) );
assert_eq!( $ratio(3).pow(-2), $ratio!(1/9) );
assert_eq!( $ratio!(-3).pow( 2), $ratio(9) );
assert_eq!( $ratio!(-3).pow(-2), $ratio!(1/9) );
assert_eq!( $ratio(2).pow( 3), $ratio(8) );
assert_eq!( $ratio(2).pow(-3), $ratio!(1/8) );
assert_eq!( $ratio!(1/2).pow( 3), $ratio!(1/8) );
assert_eq!( $ratio!(1/2).pow(-3), $ratio(8) );
assert_eq!( $ratio!(-2).pow( 3), $ratio!(-8) );
assert_eq!( $ratio!(-2).pow(-3), $ratio!(-1/8) );
assert_eq!( $ratio!(-1/2).pow( 3), $ratio!(-1/8) );
assert_eq!( $ratio!(-1/2).pow(-3), $ratio!(-8) );
}
#[test]
fn cmp() {
assert!($ratio(0) == $ratio(0));
assert!($ratio(0) < $ratio(1));
assert!($ratio(2) < $ratio(3));
assert!($ratio(0) > -$ratio(1));
assert!($ratio(2) > -$ratio(3));
}
#[test]
fn neg() {
assert_eq!(-$ratio!( 0), $ratio!( 0));
assert_eq!(-$ratio!( 1), $ratio!(-1));
assert_eq!(-$ratio!(-1), $ratio!( 1));
}
#[test]
fn checked_neg() {
let den = 1 << (FRACTION_SIZE - 1);
assert_eq!($ratio::new(-1, den).unwrap().checked_neg(), None);
}
#[test]
fn add() {
assert_eq!($ratio(0) + $ratio(0), $ratio(0));
assert_eq!($ratio(1) + $ratio(1), $ratio(2));
assert_eq!($ratio(1) + $ratio!(-1), $ratio(0));
assert_eq!($ratio!(-1) + $ratio(1), $ratio(0));
assert_eq!($ratio!(-1) + $ratio!(-1), $ratio!(-2));
assert_eq!($ratio(2) + $ratio(2), $ratio(4));
assert_eq!($ratio!(1/2) + $ratio!(3/4), $ratio!(5/4));
assert_eq!($ratio!(1/2) + $ratio!(-3/4), $ratio!(-1/4));
assert_eq!($ratio!(-1/2) + $ratio!(3/4), $ratio!(1/4));
}
#[test]
fn checked_add() {
assert_eq!($ratio(0).checked_add($ratio(0)), Some($ratio(0)));
assert_eq!($ratio(1).checked_add($ratio(1)), Some($ratio(2)));
assert_eq!($ratio(1).checked_add($ratio!(-1)), Some($ratio(0)));
assert_eq!($ratio!(-1).checked_add($ratio(1)), Some($ratio(0)));
assert_eq!($ratio!(-1).checked_add($ratio!(-1)), Some($ratio!(-2)));
assert_eq!($ratio(2).checked_add($ratio(2)), Some($ratio(4)));
assert_eq!($ratio!(1/2).checked_add($ratio!(3/4)), Some($ratio!(5/4)));
assert_eq!($ratio!(1/2).checked_add($ratio!(-3/4)), Some($ratio!(-1/4)));
assert_eq!($ratio!(-1/2).checked_add($ratio!(3/4)), Some($ratio!(1/4)));
assert_eq!($ratio::MAX.checked_add($ratio(1)), None);
}
#[test]
fn mul() {
assert_eq!($ratio(0) * $ratio(0), $ratio(0));
assert_eq!($ratio(0) * $ratio(1), $ratio(0));
assert_eq!($ratio(1) * $ratio(0), $ratio(0));
assert_eq!($ratio(1) * $ratio(1), $ratio(1));
assert_eq!(-$ratio(1) * $ratio(1), -$ratio(1));
assert_eq!( $ratio(1) * -$ratio(1), -$ratio(1));
assert_eq!(-$ratio(1) * -$ratio(1), $ratio(1));
assert_eq!($ratio(1) * $ratio(2), $ratio(2));
assert_eq!($ratio(2) * $ratio(2), $ratio(4));
assert_eq!(
$ratio!(1/2) * $ratio!(1/2), $ratio!(1/4)
);
assert_eq!(
$ratio!(-1/2) * $ratio!(1/2), $ratio!(-1/4)
);
assert_eq!(
$ratio!(2/3) * $ratio!(2/3), $ratio!(4/9)
);
assert_eq!(
$ratio!(3/2) * $ratio!(2/3), $ratio(1)
);
}
#[test]
fn div() {
assert_eq!($ratio(0) / $ratio(1), $ratio(0));
assert_eq!($ratio(0) / $ratio(2), $ratio(0));
assert_eq!($ratio(1) / $ratio(1), $ratio(1));
assert_eq!(-$ratio(1) / $ratio(1), -$ratio(1));
assert_eq!( $ratio(1) / -$ratio(1), -$ratio(1));
assert_eq!(-$ratio(1) / -$ratio(1), $ratio(1));
assert_eq!($ratio(1) / $ratio(2), $ratio!(1/2));
assert_eq!($ratio(2) / $ratio(1), $ratio(2));
assert_eq!($ratio(2) / $ratio(2), $ratio(1));
}
#[test]
fn rem() {
assert_eq!($ratio(5) % $ratio(2), $ratio(1));
assert_eq!($ratio(6) % $ratio(2), $ratio(0));
assert_eq!($ratio(8) % $ratio!(3 / 2), $ratio!(1 / 2));
assert_eq!(-$ratio(5) % $ratio(2), -$ratio(1));
assert_eq!( $ratio(5) % -$ratio(2), $ratio(1));
assert_eq!(-$ratio(5) % -$ratio(2), -$ratio(1));
}
#[test]
fn from_str() {
assert_eq!("NaN".parse::<$ratio>().unwrap(), $ratio::NAN);
assert_eq!("0".parse::<$ratio>().unwrap(), $ratio(0));
assert_eq!("1".parse::<$ratio>().unwrap(), $ratio(1));
assert_eq!("+1".parse::<$ratio>().unwrap(), $ratio(1));
assert_eq!("-1".parse::<$ratio>().unwrap(), $ratio!(-1));
assert_eq!("1/1".parse::<$ratio>().unwrap(), $ratio(1));
}
#[test] #[should_panic]
fn from_str_invalid() {
"1/-1".parse::<$ratio>().unwrap();
"/1".parse::<$ratio>().unwrap();
"1/".parse::<$ratio>().unwrap();
"1/0".parse::<$ratio>().unwrap();
}
}
}}