use crate::{Problem, Rational};
use num::bigint::ToBigInt;
use num::{BigInt, BigUint, One};
macro_rules! impl_integer_conversion {
($T:ty) => {
impl From<$T> for Rational {
#[inline]
fn from(n: $T) -> Rational {
Self::from_bigint(ToBigInt::to_bigint(&n).unwrap())
}
}
impl TryFrom<Rational> for $T {
type Error = Problem;
fn try_from(n: Rational) -> Result<$T, Self::Error> {
if let Some(i) = n.to_big_integer() {
<$T>::try_from(i).map_err(|_| Problem::OutOfRange)
} else {
Err(Problem::NotAnInteger)
}
}
}
};
}
impl_integer_conversion!(i8);
impl_integer_conversion!(i16);
impl_integer_conversion!(i32);
impl_integer_conversion!(i64);
impl_integer_conversion!(i128);
impl_integer_conversion!(u8);
impl_integer_conversion!(u16);
impl_integer_conversion!(u32);
impl_integer_conversion!(u64);
impl_integer_conversion!(u128);
fn signed(n: Rational, neg: bool) -> Rational {
if neg { -n } else { n }
}
fn pow2_fraction_u32(numerator: u32, denominator_shift: u32, neg: bool) -> Rational {
if numerator == 0 {
return Rational::zero();
}
let shift = numerator.trailing_zeros().min(denominator_shift);
let numerator = numerator >> shift;
let denominator = BigUint::one() << (denominator_shift - shift);
let numerator: BigInt = numerator.into();
signed(
Rational::from_bigint_fraction(numerator, denominator).unwrap(),
neg,
)
}
fn pow2_fraction_u64(numerator: u64, denominator_shift: u32, neg: bool) -> Rational {
if numerator == 0 {
return Rational::zero();
}
let shift = numerator.trailing_zeros().min(denominator_shift);
let numerator = numerator >> shift;
let denominator = BigUint::one() << (denominator_shift - shift);
let numerator: BigInt = numerator.into();
signed(
Rational::from_bigint_fraction(numerator, denominator).unwrap(),
neg,
)
}
impl TryFrom<f32> for Rational {
type Error = Problem;
fn try_from(n: f32) -> Result<Rational, Self::Error> {
const NEG_BITS: u32 = 0x8000_0000;
const EXP_BITS: u32 = 0x7f80_0000;
const SIG_BITS: u32 = 0x007f_ffff;
debug_assert_eq!(NEG_BITS + EXP_BITS + SIG_BITS, u32::MAX);
let bits = n.to_bits();
let neg = (bits & NEG_BITS) == NEG_BITS;
let exp = (bits & EXP_BITS) >> EXP_BITS.trailing_zeros();
let sig = bits & SIG_BITS;
match exp {
0 => {
if sig == 0 {
Ok(Rational::zero())
} else {
Ok(pow2_fraction_u32(sig, 149, neg))
}
}
1..=150 => {
let n = SIG_BITS + 1 + sig;
Ok(pow2_fraction_u32(n, 150 - exp, neg))
}
151..=254 => {
let n = SIG_BITS + 1 + sig;
let mut big: BigInt = n.into();
big <<= exp - 150;
Ok(signed(Rational::from_bigint(big), neg))
}
255 => {
if sig == 0 {
Err(Problem::Infinity)
} else {
Err(Problem::NotANumber)
}
}
_ => unreachable!(),
}
}
}
impl TryFrom<f64> for Rational {
type Error = Problem;
fn try_from(n: f64) -> Result<Rational, Self::Error> {
const NEG_BITS: u64 = 0x8000_0000_0000_0000;
const EXP_BITS: u64 = 0x7ff0_0000_0000_0000;
const SIG_BITS: u64 = 0x000f_ffff_ffff_ffff;
debug_assert_eq!(NEG_BITS + EXP_BITS + SIG_BITS, u64::MAX);
let bits = n.to_bits();
let neg = (bits & NEG_BITS) == NEG_BITS;
let exp = (bits & EXP_BITS) >> EXP_BITS.trailing_zeros();
let sig = bits & SIG_BITS;
match exp {
0 => {
if sig == 0 {
Ok(Rational::zero())
} else {
Ok(pow2_fraction_u64(sig, 1074, neg))
}
}
1..=1075 => {
let n = SIG_BITS + 1 + sig;
Ok(pow2_fraction_u64(n, (1075 - exp) as u32, neg))
}
1076..=2046 => {
let n = SIG_BITS + 1 + sig;
let mut big: BigInt = n.into();
big <<= exp - 1075;
Ok(signed(Rational::from_bigint(big), neg))
}
2047 => {
if sig == 0 {
Err(Problem::Infinity)
} else {
Err(Problem::NotANumber)
}
}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signed_integers() {
let one: Rational = i8::MAX.into();
let two: Rational = i16::MAX.into();
let three: Rational = i32::MAX.into();
let four: Rational = i64::MAX.into();
assert_eq!(one, Rational::new(0x7f));
assert_eq!(two, Rational::new(0x7fff));
assert_eq!(three, Rational::new(0x7fff_ffff));
assert_eq!(four, Rational::new(0x7fff_ffff_ffff_ffff));
}
#[test]
fn unsigned_integers() {
let one: Rational = u8::MAX.into();
let two: Rational = u16::MAX.into();
let three: Rational = u32::MAX.into();
assert_eq!(one, Rational::new(0xff));
assert_eq!(two, Rational::new(0xffff));
assert_eq!(three, Rational::new(0xffff_ffff));
}
#[test]
fn nines() {
let nine = Rational::new(99);
let n_i8: i8 = nine.clone().try_into().unwrap();
let n_u32: u32 = nine.clone().try_into().unwrap();
let n_i64: i64 = nine.clone().try_into().unwrap();
let n_u128: u128 = nine.clone().try_into().unwrap();
assert_eq!(n_i8, 99);
assert_eq!(n_u32, 99);
assert_eq!(n_i64, 99);
assert_eq!(n_u128, 99);
}
#[test]
fn not_int() {
let almost_pi = Rational::fraction(22, 7).unwrap();
let problem = <Rational as TryInto<i16>>::try_into(almost_pi).unwrap_err();
assert_eq!(problem, Problem::NotAnInteger);
let almost_pi = Rational::fraction(22, 7).unwrap();
let three: u32 = almost_pi.trunc().try_into().unwrap();
assert_eq!(three, 3);
}
#[test]
fn huge() {
let huge = Rational::new(123_456_789);
let problem = <Rational as TryInto<i16>>::try_into(huge).unwrap_err();
assert_eq!(problem, Problem::OutOfRange);
}
#[test]
fn negative() {
let minus_100 = Rational::new(-100);
let problem = <Rational as TryInto<u8>>::try_into(minus_100).unwrap_err();
assert_eq!(problem, Problem::OutOfRange);
}
#[test]
fn zero() {
let f: f32 = 0.0;
let d: f64 = 0.0;
let a: Rational = f.try_into().unwrap();
let b: Rational = d.try_into().unwrap();
let zero = Rational::zero();
assert_eq!(a, zero);
assert_eq!(b, zero);
}
#[test]
fn half_from_float() {
let half = 0.5_f32;
let correct = Rational::fraction(1, 2).unwrap();
let answer: Rational = half.try_into().unwrap();
assert_eq!(answer, correct);
let half = 0.5_f64;
let answer: Rational = half.try_into().unwrap();
assert_eq!(answer, correct);
}
#[test]
fn repr_f32() {
let f: f32 = 1.234_567_9;
let a: Rational = f.try_into().unwrap();
let correct = Rational::fraction(5178153, 4194304).unwrap();
assert_eq!(a, correct);
}
#[test]
fn repr_f64() {
let f: f64 = 1.23456789;
let a: Rational = f.try_into().unwrap();
let correct = Rational::fraction(5559999489367579, 4503599627370496).unwrap();
assert_eq!(a, correct);
}
#[test]
fn reduced_binary_fraction_f64() {
let value: Rational = 0.75_f64.try_into().unwrap();
assert_eq!(value, Rational::fraction(3, 4).unwrap());
}
#[test]
fn reduced_subnormal_f64() {
let value: Rational = f64::from_bits(2).try_into().unwrap();
let correct =
Rational::from_bigint_fraction(BigInt::from(1), BigUint::one() << 1073).unwrap();
assert_eq!(value, correct);
}
}