use std::ops::{Add, Sub, Mul, Div};
const REDUCE_MAX: i128 = i32::MAX as i128;
const RATIONAL_MIN: Rational = Rational {
num: -2147483647,
den: 1,
};
const RATIONAL_MAX: Rational = Rational {
num: 2147483647,
den: 1,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Rational {
num: i64,
den: i64,
}
fn i64_gcd(mut a: i128, mut b: i128) -> i128 {
if a < 0 {
a = -a;
}
if b < 0 {
b = -b;
}
while b != 0 {
let t = a % b;
a = b;
b = t;
}
a
}
fn reduce_fraction(num: &mut i128, den: &mut i128, max: i128) {
if *den == 0 {
*num = 0;
return;
}
let sign = (*num < 0) != (*den < 0);
let gcd = i64_gcd(*num, *den);
if gcd != 0 {
*num = if *num < 0 { -*num } else { *num } / gcd;
*den = if *den < 0 { -*den } else { *den } / gcd;
}
if *num <= max && *den <= max {
*num = if sign { -*num } else { *num };
return;
}
let mut a0n: i128 = 0;
let mut a0d: i128 = 1;
let mut a1n: i128 = 1;
let mut a1d: i128 = 0;
let mut n = *num;
let mut d = *den;
while d != 0 {
let x = n / d;
let next_den = n - d * x;
let a2n = x * a1n + a0n;
let a2d = x * a1d + a0d;
if a2n > max || a2d > max {
let mut x = x;
if a1n != 0 {
x = (max - a0n) / a1n;
}
if a1d != 0 && (max - a0d) / a1d < x {
x = (max - a0d) / a1d;
}
if d * (2 * x * a1d + a0d) > n * a1d {
a1n = x * a1n + a0n;
a1d = x * a1d + a0d;
}
break;
}
a0n = a1n;
a0d = a1d;
a1n = a2n;
a1d = a2d;
n = d;
d = next_den;
}
*num = if sign { -a1n } else { a1n };
*den = a1d;
}
fn frexp(x: f64, exp: &mut i32) -> f64 {
if x == 0.0 || x.is_nan() || x.is_infinite() {
*exp = 0;
return x;
}
let bits = x.to_bits();
let raw = ((bits >> 52) & 0x7ff) as i32;
if raw == 0 {
let scaled = x * 9007199254740992.0; let mut e = 0;
let m = frexp(scaled, &mut e);
*exp = e - 53;
return m;
}
*exp = raw - 1022;
f64::from_bits((bits & !(0x7ffu64 << 52)) | (1022u64 << 52))
}
fn fix_signs(num: &mut i64, den: &mut i64) {
if *den < 0 {
*den = -*den;
*num = -*num;
} else if *den == 0 {
*num = 0;
} else if *num == 0 {
*den = 1;
}
}
fn from_reduced(num: i128, den: i128) -> Rational {
let mut num = num as i64;
let mut den = den as i64;
fix_signs(&mut num, &mut den);
Rational { num, den }
}
fn compare_fractions(an: i64, ad: i64, bn: i64, bd: i64) -> i32 {
let tmp = an as i128 * bd as i128 - bn as i128 * ad as i128;
if tmp != 0 {
if tmp > 0 {
1
} else {
-1
}
} else if bd != 0 && ad != 0 {
0
} else if an != 0 && bn != 0 {
((an >> 31) - (bn >> 31)) as i32
} else {
i32::MIN
}
}
fn to_int(s: &str) -> i64 {
s.trim().parse::<i64>().unwrap_or(0)
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Rounding {
Round,
Floor,
}
fn flipped(r: Rational) -> Rational {
if r.num == 0 {
return r;
}
let mut num = r.den;
let mut den = r.num;
fix_signs(&mut num, &mut den);
Rational { num, den }
}
fn timestamp_to_time(ts: i64, timebase: Rational) -> Rational {
let mut num = timebase.num as i128 * ts as i128;
let mut den = timebase.den as i128;
reduce_fraction(&mut num, &mut den, REDUCE_MAX);
from_reduced(num, den)
}
pub(crate) fn time_to_timestamp_rnd(time: Rational, timebase: Rational, rnd: Rounding) -> i64 {
let d = time.to_f64() * flipped(timebase).to_f64();
if d.is_nan() {
return 0;
}
let eps = 0.000000000001;
match rnd {
Rounding::Round => d.round() as i64,
Rounding::Floor => {
if d > d.ceil() - eps {
d.ceil() as i64
} else {
d.floor() as i64
}
}
}
}
pub(crate) fn snap_time_to_timebase(time: Rational, timebase: Rational) -> Rational {
let ts = time_to_timestamp_rnd(time, timebase, Rounding::Floor);
timestamp_to_time(ts, timebase)
}
impl Rational {
pub const NULL: Rational = Rational { num: 0, den: 0 };
pub fn new(num: i64, den: i64) -> Rational {
let mut num = num;
let mut den = den;
fix_signs(&mut num, &mut den);
let mut num = num as i128;
let mut den = den as i128;
reduce_fraction(&mut num, &mut den, REDUCE_MAX);
Rational {
num: num as i64,
den: den as i64,
}
}
pub fn numerator(self) -> i64 {
self.num
}
pub fn denominator(self) -> i64 {
self.den
}
pub fn is_null(self) -> bool {
self.num == 0
}
pub fn is_nan(self) -> bool {
self.den == 0
}
fn is_minmax(self) -> bool {
self == RATIONAL_MIN || self == RATIONAL_MAX
}
pub fn from_string(s: &str) -> Rational {
let elements: Vec<&str> = s.split('/').collect();
match elements.len() {
1 => Rational::new(to_int(elements[0]), 1),
2 => Rational::new(to_int(elements[0]), to_int(elements[1])),
_ => Rational::NULL,
}
}
pub fn to_display_string(self) -> String {
format!("{}/{}", self.num, self.den)
}
pub fn to_f64(self) -> f64 {
if self.den != 0 {
self.num as f64 / self.den as f64
} else {
f64::NAN
}
}
pub fn from_double(value: f64) -> Rational {
if value.is_nan() || value.abs() > i32::MAX as f64 + 3.0 {
return Rational::NULL;
}
let mut exponent = 0;
let _ = frexp(value, &mut exponent);
exponent = (exponent - 1).max(0);
let den: i64 = 1i64 << (62 - exponent);
let num: i64 = (value * den as f64 + 0.5).floor() as i64;
let mut rnum = num as i128;
let mut rden = den as i128;
reduce_fraction(&mut rnum, &mut rden, i32::MAX as i128);
if (rnum == 0 || rden == 0) && value != 0.0 {
rnum = (value * i64::MAX as f64) as i64 as i128;
rden = i64::MAX as i128;
reduce_fraction(&mut rnum, &mut rden, i32::MAX as i128);
}
from_reduced(rnum, rden)
}
pub fn time_to_timestamp(self, time: Rational) -> i64 {
time_to_timestamp_rnd(time, self, Rounding::Round)
}
pub fn timestamp_to_time(self, ts: i64) -> Rational {
timestamp_to_time(ts, self)
}
}
impl Add for Rational {
type Output = Rational;
fn add(self, rhs: Rational) -> Rational {
if self.is_minmax() || rhs.is_minmax() {
return Rational::NULL;
}
if self.is_nan() {
return self;
}
if rhs.is_nan() {
return Rational::NULL;
}
let mut n = self.num as i128 * rhs.den as i128 + rhs.num as i128 * self.den as i128;
let mut d = self.den as i128 * rhs.den as i128;
reduce_fraction(&mut n, &mut d, REDUCE_MAX);
from_reduced(n, d)
}
}
impl Sub for Rational {
type Output = Rational;
fn sub(self, rhs: Rational) -> Rational {
if self.is_minmax() || rhs.is_minmax() {
return Rational::NULL;
}
if self.is_nan() {
return self;
}
if rhs.is_nan() {
return Rational::NULL;
}
let mut n = self.num as i128 * rhs.den as i128 - rhs.num as i128 * self.den as i128;
let mut d = self.den as i128 * rhs.den as i128;
reduce_fraction(&mut n, &mut d, REDUCE_MAX);
from_reduced(n, d)
}
}
impl Mul for Rational {
type Output = Rational;
fn mul(self, rhs: Rational) -> Rational {
if self.is_minmax() || rhs.is_minmax() {
return Rational::NULL;
}
if self.is_nan() {
return self;
}
if rhs.is_nan() {
return Rational::NULL;
}
let mut n = self.num as i128 * rhs.num as i128;
let mut d = self.den as i128 * rhs.den as i128;
reduce_fraction(&mut n, &mut d, REDUCE_MAX);
from_reduced(n, d)
}
}
impl Div for Rational {
type Output = Rational;
fn div(self, rhs: Rational) -> Rational {
if self.is_minmax() || rhs.is_minmax() {
return Rational::NULL;
}
if self.is_nan() {
return self;
}
if rhs.is_nan() {
return Rational::NULL;
}
let mut n = self.num as i128 * rhs.den as i128;
let mut d = self.den as i128 * rhs.num as i128;
reduce_fraction(&mut n, &mut d, REDUCE_MAX);
from_reduced(n, d)
}
}
impl PartialOrd for Rational {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (self.den == 0, other.den == 0) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
(false, false) => match compare_fractions(self.num, self.den, other.num, other.den) {
0 => Ordering::Equal,
1 => Ordering::Greater,
_ => Ordering::Less,
},
}
}
}