hwcalc_lib 0.2.0

Backend for the hwcalc calculator
Documentation
use std::fmt;

use num_traits::sign::Signed;

use super::ir::Int;
use super::ir::Ty;
use super::ir::Val;
use super::Base::*;
use super::Num;

/// Maximum number of fractional digits to calculate when formatting a fractional number.
const MAX_FRAC_DIGITS: usize = 64;

impl fmt::Display for Num {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.fmt(f, Dec)
    }
}

impl fmt::LowerHex for Num {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.fmt(f, Hex)
    }
}

impl fmt::Binary for Num {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.fmt(f, Bin)
    }
}

impl fmt::Octal for Num {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.fmt(f, Oct)
    }
}

impl Num {
    fn fmt<F: fmt::Write>(&self, f: &mut F, base: super::Base) -> fmt::Result {
        match base {
            super::Base::Dec => self.fmt_dec(f),
            super::Base::Bin | super::Base::Oct | super::Base::Hex => {
                f.write_char('0')?;
                f.write_char(base.prefix())?;
                self.fmt_pow2(f, base)
            }
        }
    }

    fn fmt_dec<F: std::fmt::Write>(&self, f: &mut F) -> std::fmt::Result {
        if self.negative() && self.ty.signed == Some(false) {
            f.write_str("INF")
        } else {
            if self.negative() {
                f.write_char('-')?;
            }
            let int = format!("{}", self.val.to_integer());
            fmt_group(f, &int[int.starts_with('-').into()..], 3)?;
            FracRepr::new(&self.val.fract().abs(), 10, 0, Some(MAX_FRAC_DIGITS)).fmt(f, 3)
        }
    }

    fn fmt_pow2<F: std::fmt::Write>(&self, f: &mut F, base: super::Base) -> std::fmt::Result {
        const GROUP_SIZE: usize = 4;
        let bits_digit = u64::from(base.bits_per_digit());

        if self.signed() {
            return self.to_unsigned().fmt_pow2(f, base);
        }
        assert_eq!(self.ty.signed, Some(false));

        if self.negative() {
            assert_eq!(self.ty.width, None);
            f.write_fmt(format_args!("({})", base.max_digit()))?;
            let int = self.val.to_integer().abs();
            let pos = int > 0.into();
            let w = if self.val.fract() == Val::default() {
                &int - Int::from(1)
            } else {
                int
            }
            .bits();
            let w = (((w + (bits_digit - 1)) / bits_digit) * bits_digit)
                .try_into()
                .unwrap();
            if w > 0 && pos {
                let mut i = self.clone().with_ty(Ty::default().w(w).wf(0));
                if self.val.fract() != Val::default() {
                    i.val -= Val::from_integer(1.into());
                }
                i.fmt_pow2(f, base)?;
            }
        } else {
            let int = self.val.to_integer();
            let max_digits = self
                .ty
                .width
                .map(|w| (u64::from(w) + bits_digit - 1) / bits_digit);
            let actual_digits = u64::max((int.bits() + bits_digit - 1) / bits_digit, 1);
            let padding = max_digits.map_or(0, |m| m.saturating_sub(actual_digits));
            let int: String = std::iter::repeat('0')
                .take(padding as usize)
                .chain(int.to_str_radix(base.radix().into()).chars())
                .collect();
            fmt_group(f, &int, GROUP_SIZE)?;
        }

        let frac_min_width = u64::from(self.ty.width_frac.unwrap_or(0));
        let frac_min_digits = (frac_min_width + bits_digit - 1) / bits_digit;
        FracRepr::new(
            &self.val.fract(),
            base.radix().into(),
            frac_min_digits as usize,
            Some(MAX_FRAC_DIGITS),
        )
        .fmt(f, GROUP_SIZE)
    }
}

fn fmt_group<F: std::fmt::Write>(f: &mut F, s: &str, size: usize) -> std::fmt::Result {
    let mut first = true;
    for g in s.as_bytes().rchunks(size).rev() {
        let g = std::str::from_utf8(g).unwrap();
        if first {
            first = false;
        } else {
            f.write_char('_')?;
        }
        f.write_str(g)?;
    }
    Ok(())
}

pub struct FracRepr {
    digits: Vec<u8>,
    non_repeating: usize,
    radix: u32,
    truncated: bool,
}

impl FracRepr {
    fn new(frac: &Val, radix: u32, min_digits: usize, max_digits: Option<usize>) -> FracRepr {
        assert!(frac.abs() < Val::from_integer(1.into()));

        let frac = if frac < &Val::from_integer(0.into()) {
            Val::from_integer(1.into()) + frac
        } else {
            frac.clone()
        };

        let mut digits = Vec::new();
        let mut non_repeating = None;
        let mut truncated = false;

        let mut denom = Int::from(1);
        let mut rem = frac.numer().clone();
        let mut rems = Vec::new();

        while rem > 0.into() {
            if max_digits.map_or(false, |m| digits.len() >= m) {
                truncated = true;
                break;
            }

            denom *= Int::from(radix);
            rems.push(rem.clone());

            let numerc = &denom * &rem;
            if frac.denom() <= &numerc {
                let digit = &numerc / frac.denom();
                rem = &numerc % frac.denom();
                denom = Int::from(1);
                digits.push(digit.try_into().unwrap());

                if let Some(nr) = rems.iter().position(|x| x == &rem) {
                    non_repeating = Some(nr);
                    break;
                }
            } else {
                digits.push(0);
            }
        }

        if non_repeating.is_none() && digits.len() < min_digits {
            digits.resize_with(min_digits, || 0);
        }

        let non_repeating = if let Some(mut nr) = non_repeating {
            // normalize
            while nr > 0 && digits.last() == digits.get(nr - 1) {
                digits.pop();
                nr -= 1;
            }
            nr
        } else {
            digits.len()
        };

        Self {
            digits,
            non_repeating,
            radix,
            truncated,
        }
    }

    fn fmt<F: std::fmt::Write>(&self, f: &mut F, group_size: usize) -> std::fmt::Result {
        if self.digits.is_empty() {
            Ok(())
        } else {
            f.write_char('.')?;
            let mut pos = 0;
            while pos < self.non_repeating {
                let len = usize::min(self.non_repeating - pos, group_size);
                let chunk = &self.digits[pos..pos + len];
                for d in chunk {
                    f.write_char(char::from_digit(u32::from(*d), self.radix).unwrap())?;
                }
                pos += len;
                if pos < self.non_repeating {
                    f.write_char('_')?;
                }
            }
            if self.non_repeating < self.digits.len() {
                f.write_char('(')?;
                for d in &self.digits[self.non_repeating..] {
                    f.write_char(char::from_digit(u32::from(*d), self.radix).unwrap())?;
                }
                f.write_char(')')?;
            } else if self.truncated {
                f.write_str("..")?;
            }
            Ok(())
        }
    }
}

#[cfg(test)]
mod test {
    use crate::Base::*;
    use crate::Num;

    macro_rules! test_fmt {
        ($n:expr, $b:expr, $expect:literal) => {
            let mut s = String::new();
            $n.fmt(&mut s, $b).unwrap();
            assert_eq!(s, $expect);
        };
    }

    #[test]
    fn fmt() {
        assert_eq!(format!("{}", Num::n(10)), "10");
        assert_eq!(format!("{:b}", Num::n(10)), "0b1010");
        assert_eq!(format!("{:o}", Num::n(10)), "0o12");
        assert_eq!(format!("{:x}", Num::n(10)), "0xa");
    }

    #[test]
    fn fmt_bin() {
        test_fmt!(Num::u(0b0), Bin, "0b0");
        test_fmt!(Num::u(0b1010), Bin, "0b1010");
        test_fmt!(Num::u(0b00_1010).w(6), Bin, "0b00_1010");
        test_fmt!(Num::u(0b0000_1010).w(8), Bin, "0b0000_1010");
        test_fmt!(Num::i(-4).w(4), Bin, "0b1100");
    }

    #[test]
    fn fmt_oct() {
        test_fmt!(Num::u(0o377), Oct, "0o377");
        test_fmt!(Num::u(0o01).w(6), Oct, "0o01");
        test_fmt!(Num::u(0o01).w(7), Oct, "0o001");
        test_fmt!(Num::i(-2).w(6), Oct, "0o76");
        test_fmt!(Num::u(0o7_7777_7777), Oct, "0o7_7777_7777");
        test_fmt!(Num::u(0o077_7777).w(21), Oct, "0o077_7777");
    }

    #[test]
    fn fmt_hex() {
        test_fmt!(Num::u(0xff), Hex, "0xff");
        test_fmt!(Num::u(0x01).w(8), Hex, "0x01");
        test_fmt!(Num::u(0x01).w(9), Hex, "0x001");
        test_fmt!(Num::i(-2).w(8), Hex, "0xfe");
        test_fmt!(Num::u(0x7fff_ffff).w(31), Hex, "0x7fff_ffff");
    }

    #[test]
    fn fmt_dec() {
        test_fmt!(Num::u(0), Dec, "0");
        test_fmt!(Num::u(8), Dec, "8");
        test_fmt!(Num::i(-1), Dec, "-1");
        test_fmt!(Num::i(-150), Dec, "-150");
        test_fmt!(Num::u(-1), Dec, "INF");
        test_fmt!(Num::i(-1).w(4), Dec, "-1");
        test_fmt!(Num::u(1).w(4), Dec, "1");
        test_fmt!(Num::u(8_239_847), Dec, "8_239_847");
        test_fmt!(Num::u(239_847), Dec, "239_847");
    }

    #[test]
    fn fmt_bin_inf() {
        test_fmt!(Num::i(-1), Bin, "0b(1)");
        test_fmt!(Num::i(-2), Bin, "0b(1)0");
        test_fmt!(Num::i(-3), Bin, "0b(1)01");
        test_fmt!(Num::u(-3), Bin, "0b(1)01");
        test_fmt!(Num::i(-4), Bin, "0b(1)00");
        test_fmt!(Num::i(-234), Bin, "0b(1)0001_0110");
    }

    #[test]
    fn fmt_hex_inf() {
        test_fmt!(Num::i(-1), Hex, "0x(f)");
        test_fmt!(Num::i(-2), Hex, "0x(f)e");
        test_fmt!(Num::i(-3), Hex, "0x(f)d");
        test_fmt!(Num::i(-234), Hex, "0x(f)16");
    }

    #[test]
    fn fmt_bin_fix() {
        test_fmt!(Num::nd(1, 10).wf(12), Bin, "0b0.0001_1001_1001");
        test_fmt!(Num::n(5).w(6).wf(7), Bin, "0b00_0101.0000_000");
        test_fmt!(Num::nd(1, 5), Bin, "0b0.(0011)");
        test_fmt!(Num::nd(1, 10), Bin, "0b0.0(0011)");
        test_fmt!(Num::nd(1, 1600), Bin, "0b0.0000_00(00001010001111010111)");
        // zero width integer part
        test_fmt!(Num::uq(0b1010_01, 0b1_00).w(0), Bin, "0b0.01");
    }

    #[test]
    fn fmt_dec_fix() {
        test_fmt!(Num::nd(-3, 4), Dec, "-0.75");
        test_fmt!(Num::nd(5, 2), Dec, "2.5");
        test_fmt!(Num::nd(-7, 5), Dec, "-1.4");
        test_fmt!(Num::nd(1_234_567, 1_000_000), Dec, "1.234_567");
        test_fmt!(Num::nd(4, 9), Dec, "0.(4)");
        test_fmt!(Num::nd(1, 81), Dec, "0.(012345679)");
        // zero width integer part
        test_fmt!(Num::uq(5, 2).w(0), Dec, "0.5");
    }

    #[test]
    fn fmt_hex_fix() {
        test_fmt!(Num::nd(13, 9), Hex, "0x1.(71c)");
    }

    #[test]
    fn fmt_bin_fix_inf() {
        test_fmt!(Num::nd(-1, 4), Bin, "0b(1).11");
        test_fmt!(Num::nd(-3, 4), Bin, "0b(1).01");
        test_fmt!(Num::nd(-5, 2), Bin, "0b(1)01.1");
    }

    #[test]
    fn fmt_oct_fix_inf() {
        test_fmt!(Num::nd(-3, 4), Oct, "0o(7).2");
        test_fmt!(Num::q(-3, 4), Oct, "0o(7).2");
        test_fmt!(Num::uq(-3, 4), Oct, "0o(7).2");
    }

    #[test]
    fn fmt_hex_fix_inf() {
        test_fmt!(Num::nd(-23, 4), Hex, "0x(f)a.4");
        test_fmt!(Num::nd(-5, 2), Hex, "0x(f)d.8");
        test_fmt!(Num::nd(-13, 9), Hex, "0x(f)e.(8e3)");
        test_fmt!(Num::nd(-5, 12), Hex, "0x(f).9(5)");
    }

    #[test]
    fn fmt_truncate() {
        // exactly fits
        test_fmt!(
            Num::nd(1, 1 << super::MAX_FRAC_DIGITS),
            Bin,
            "0b0.0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001"
        );
        // 1 digit too many
        test_fmt!(
            Num::nd(1, 1 << (super::MAX_FRAC_DIGITS + 1)),
            Bin,
            "0b0.0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000.."
        );
        // way too many digits
        test_fmt!(
            Num::nd(1, 1000),
            Bin,
            "0b0.0000_0000_0100_0001_1000_1001_0011_0111_0100_1011_1100_0110_1010_0111_1110_1111.."
        );
    }
}