use core::{fmt, ops::Index};
use smallvec::{smallvec, SmallVec};
#[derive(PartialOrd, Ord, PartialEq, Eq, Clone)]
pub struct Fraction(SmallVec<[u8; 8]>);
const DIGIT_BITS: u32 = 7;
const DIGIT_MASK: usize = (1 << DIGIT_BITS) - 1;
const DIGIT_MASK_U8: u8 = (1 << DIGIT_BITS) - 1;
impl AsRef<[u8]> for Fraction {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Index<usize> for Fraction {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
self.0.get(index).unwrap_or(&0u8)
}
}
impl fmt::Debug for Fraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Fraction({})", self)
}
}
impl fmt::Display for Fraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.0.is_empty() {
write!(f, "0x0.{}", hex::encode(&self.0))
} else {
write!(f, "0x0")
}
}
}
impl Fraction {
pub fn new(data: SmallVec<[u8; 8]>) -> Self {
Self(data)
}
pub fn from_digits(mut digits: SmallVec<[u8; 8]>) -> Self {
assert!(digits.iter().all(|x| *x < 0x80));
if digits.is_empty() {
digits.push(0);
} else {
while digits.len() > 1 && digits.last() == Some(&0u8) {
digits.pop();
}
}
for i in 0..digits.len() {
digits[i] <<= 1;
if i < digits.len() - 1 {
digits[i] |= 1;
}
}
Self(digits)
}
pub fn mid(&self, that: &Self) -> Self {
let n = self.0.len().max(that.0.len());
let mut digits = SmallVec::with_capacity(n);
let mut carry = 0usize;
for i in (0..n).rev() {
carry += self.digit(i);
carry += that.digit(i);
digits.push((carry & DIGIT_MASK) as u8);
carry >>= DIGIT_BITS;
}
digits.reverse();
for i in 0..n {
let r = digits[i];
digits[i] = (r >> 1) + ((carry as u8) << (DIGIT_BITS - 1));
carry = (r & 1) as usize;
}
if carry != 0 {
digits.push(0x40);
}
Self::from_digits(digits)
}
pub fn succ(&self) -> Self {
let mut digits = self.digits();
if digits.iter().all(|x| *x == DIGIT_MASK_U8) {
let n = digits.len().max(1);
digits.extend((0..n).map(|_| 0u8));
}
for digit in digits.iter_mut().rev() {
*digit = (*digit + 1) & DIGIT_MASK_U8;
if *digit != 0 {
break;
}
}
Self::from_digits(digits)
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
pub fn zero() -> Fraction {
Self::new(smallvec![0u8])
}
pub fn half() -> Fraction {
Self::from_digits(smallvec![1 << (DIGIT_BITS - 1)])
}
fn digits(&self) -> SmallVec<[u8; 8]> {
let mut res = self.0.clone();
res.iter_mut().for_each(|x| *x >>= 1);
res
}
fn digit(&self, i: usize) -> usize {
self.0.get(i).map(|x| *x >> 1).unwrap_or_default() as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn fraction_smoke() {
let t = Fraction::zero();
let u = t.succ();
let v = t.mid(&u);
assert!(t < u);
assert!(t < v && v < u);
println!("{:?} < {:?} < {:?}", t, v, u);
}
fn arb_fraction() -> impl Strategy<Value = Fraction> {
any::<Vec<u8>>().prop_map(|v| {
let mut digits: SmallVec<[u8; 8]> = v.into();
digits.iter_mut().for_each(|x| *x &= DIGIT_MASK_U8);
Fraction::from_digits(digits)
})
}
impl Arbitrary for Fraction {
type Parameters = ();
type Strategy = BoxedStrategy<Fraction>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
arb_fraction().boxed()
}
}
fn to_bytes(fs: &[Fraction]) -> Vec<u8> {
let mut res = Vec::new();
for f in fs {
res.extend_from_slice(&f.0);
}
res
}
proptest! {
#[test]
fn mid(
mut a in arb_fraction(),
mut b in arb_fraction(),
) {
if a != b {
if a > b {
std::mem::swap(&mut a, &mut b);
}
let m = a.mid(&b);
println!("a={:?}, b={:?}, m={:?}", a, b, m);
prop_assert!(a < m);
prop_assert!(m < b);
} else {
let m = a.mid(&b);
prop_assert!(m == a);
prop_assert!(m == b);
}
}
#[test]
fn succ(
a in arb_fraction(),
) {
prop_assert!(a.succ() > a);
}
#[test]
fn ord(
a in any::<Vec<Fraction>>(),
b in any::<Vec<Fraction>>(),
) {
let ab = to_bytes(&a);
let bb = to_bytes(&b);
let byte_order = ab.cmp(&bb);
let val_order = a.cmp(&b);
println!("a={:?} b={:?}", a, b);
println!("ab={} bb={}", hex::encode(ab), hex::encode(bb));
prop_assert_eq!(byte_order, val_order);
}
}
}