awint_ext 0.8.0

Externally allocating `awint` functionality
Documentation
use alloc::{string::String, vec::Vec};
use core::{
    borrow::BorrowMut,
    cmp::{max, min},
    num::NonZeroUsize,
};

use awint_core::Bits;

use crate::{
    awint_internals::{bits_upper_bound, SerdeError, SerdeError::*},
    ExtAwi, FP,
};

fn itousize(i: isize) -> Option<usize> {
    usize::try_from(i).ok()
}

/// These functions are associated to avoid name clashes.
///
/// Note: Adding new functions to `FP` is a WIP
// TODO
impl<B: BorrowMut<Bits>> FP<B> {
    /// One-assigns `this`. Returns `None` if a positive one value is not
    /// representable.
    #[must_use]
    pub fn one_(this: &mut Self) -> Option<()> {
        // if fp is negative, one can certainly not be represented
        let fp = itousize(this.fp())?;
        // if `this.signed() && fp == this.bw()`, trying to set the one would set the
        // sign bit
        if fp > this.bw().wrapping_sub(this.signed() as usize) {
            None
        } else {
            this.const_as_mut().zero_();
            this.const_as_mut().usize_or_(1, fp);
            Some(())
        }
    }

    /// Relative significant bit positions, determines the bit positions
    /// (inclusive) of the least and most significant bits relative to the
    /// fixed point
    ///
    /// Note: because the msb position is one less than the bitwidth, the
    /// bitwidth is equal to the difference in the bounds _plus one_
    #[inline]
    #[must_use]
    pub fn rel_sb(this: &Self) -> (isize, isize) {
        // cannot overflow because of the invariants
        let lo = this.fp().wrapping_neg();
        // the msb position is one less than the bitwidth
        (lo, this.ibw().wrapping_sub(1).wrapping_add(lo))
    }

    /// The same as [FP::truncate_] except it always intreprets arguments
    /// as unsigned
    pub fn utruncate_<C: BorrowMut<Bits>>(this: &mut Self, rhs: &FP<C>) {
        this.zero_();
        let lbb = FP::rel_sb(this);
        let rbb = FP::rel_sb(rhs);

        // find overlap
        let lo = max(lbb.0, rbb.0);
        let hi = min(lbb.1, rbb.1);
        if hi < lo {
            // does not overlap
            return
        }
        let width = hi.wrapping_sub(lo).wrapping_add(1) as usize;
        let diff = lbb.0.abs_diff(rbb.0);
        // the fielding will start from 0 in one argument and end at `diff` in the other
        let (to, from) = if lbb.0 < rbb.0 { (diff, 0) } else { (0, diff) };
        this.const_as_mut()
            .field(to, rhs.const_as_ref(), from, width)
            .unwrap();
    }

    /// Truncate-assigns `rhs` to `this`. For the unsigned case, logically what
    /// this does is make `this` and `rhs` into concatenations with infinite
    /// zeros on both ends, aligns the fixed points, and copies from `rhs`
    /// to `this`. For the case of `rhs.signed()`, the absolute value of
    /// `rhs` is used for truncation to `this` followed by
    /// `this.neg_(rhs.msb() && this.signed())`.
    pub fn truncate_<C: BorrowMut<Bits>>(this: &mut Self, rhs: &mut FP<C>) {
        let mut b = rhs.is_negative();
        // reinterpret as unsigned to avoid imin overflow
        rhs.const_as_mut().neg_(b);
        FP::utruncate_(this, rhs);
        rhs.const_as_mut().neg_(b);
        b &= this.signed();
        this.const_as_mut().neg_(b);
    }

    /// The same as [FP::otruncate_] except it always intreprets arguments
    /// as unsigned
    #[must_use = "use `utruncate_` if you do not need the overflow booleans"]
    pub fn outruncate_<C: BorrowMut<Bits>>(this: &mut Self, rhs: &FP<C>) -> (bool, bool) {
        this.zero_();
        if rhs.is_zero() {
            return (false, false)
        }
        let lbb = FP::rel_sb(this);
        let rbb = FP::rel_sb(rhs);

        // find overlap
        let lo = max(lbb.0, rbb.0);
        let hi = min(lbb.1, rbb.1);
        if hi < lo {
            // does not overlap
            return (true, true)
        }
        let width = hi.wrapping_sub(lo).wrapping_add(1) as usize;
        let diff = lbb.0.abs_diff(rbb.0);
        let (to, from) = if lbb.0 < rbb.0 { (diff, 0) } else { (0, diff) };
        this.const_as_mut()
            .field(to, rhs.const_as_ref(), from, width)
            .unwrap();
        // when testing if a less significant numerical bit is cut off, we need to be
        // aware that it can be cut off from above even if overlap happens, for
        // example:
        //
        // 1.0
        //  .yyy
        // _____
        //  .000
        //
        // The `1` is the least significant numerical bit, but will get truncated by
        // being above the rel_msb.

        // note overflow cannot happen because of the `rhs.is_zero()` early return and
        // invariants
        let mut lsnb = rhs.const_as_ref().tz() as isize;
        lsnb = lsnb.wrapping_add(rbb.0);
        let mut msnb = rhs
            .bw()
            .wrapping_sub(rhs.const_as_ref().lz())
            .wrapping_sub(1) as isize;
        msnb = msnb.wrapping_add(rbb.0);
        (
            (lsnb < lbb.0) || (lsnb > lbb.1),
            (msnb < lbb.0) || (msnb > lbb.1),
        )
    }

    /// Overflow-truncate-assigns `rhs` to `this`. The same as
    /// [FP::truncate_], except that a tuple of booleans is returned. The
    /// first indicates if the least significant numerical bit was truncated,
    /// and the second indicates if the most significant numerical bit was
    /// truncated. Additionally, if `this.is_negative() != rhs.is_negative()`,
    /// the second overflow is set.
    ///
    /// What this means is that if transitive truncations return no overflow,
    /// then numerical value is preserved. If only `FP::otruncate_(...).0`
    /// is true, then less significant numerical values were changed and only
    /// some kind of truncation rounding has occured to the numerical value. If
    /// `FP::otruncate_(...).1` is true, then the numerical value could be
    /// dramatically changed.
    #[must_use = "use `truncate_` if you do not need the overflow booleans"]
    pub fn otruncate_<C: BorrowMut<Bits>>(this: &mut Self, rhs: &mut FP<C>) -> (bool, bool) {
        let mut b = rhs.is_negative();
        // reinterpret as unsigned to avoid imin overflow
        rhs.const_as_mut().neg_(b);
        let o = FP::outruncate_(this, rhs);
        rhs.const_as_mut().neg_(b);
        // imin works correctly
        b &= this.signed();
        this.const_as_mut().neg_(b);
        (o.0, o.1 || (this.is_negative() != rhs.is_negative()))
    }

    /// Creates a tuple of `Vec<u8>`s representing the integer and fraction
    /// parts `this` (sign indicators, prefixes, points, and postfixes not
    /// included). This function performs allocation. This is the inverse of
    /// [ExtAwi::from_bytes_general] and extends the abilities of
    /// [ExtAwi::bits_to_vec_radix]. Signedness and fixed point position
    /// information is taken from `this`. `min_integer_chars` specifies the
    /// minimum number of chars in the integer part, inserting leading '0's if
    /// there are not enough chars. `min_fraction_chars` works likewise for the
    /// fraction part, inserting trailing '0's.
    ///
    /// ```
    /// use awint::awi::*;
    /// // note: a user may want to define their own helper functions to do
    /// // this in one step and combine the output into one string using
    /// // the notation they prefer.
    ///
    /// // This creates a fixed point value of -42.1234_i32f16
    /// // (`ExtAwi::from_str` will be able to parse this format in the future
    /// // after more changes to `awint` are made).
    /// let awi = ExtAwi::from_str_general(Some(true), "42", "1234", 0, 10, bw(32), 16).unwrap();
    /// let fp_awi = FP::new(true, awi, 16).unwrap();
    /// assert_eq!(
    ///     // note: in many situations users will want at least 1 zero for
    ///     // both parts so that zero parts result in "0" strings and not "",
    ///     // so `min_..._chars` will be 1. See also
    ///     // `FPType::unique_min_fraction_digits`.
    ///     FP::to_str_general(&fp_awi, 10, false, 1, 1),
    ///     Ok(("42".to_owned(), "1234".to_owned()))
    /// );
    /// ```
    ///
    /// # Errors
    ///
    /// This can only return an error if `radix` is not in the range 2..=36 or
    /// if resource exhaustion occurs.
    pub fn to_vec_general(
        this: &Self,
        radix: u8,
        upper: bool,
        min_integer_chars: usize,
        min_fraction_chars: usize,
    ) -> Result<(Vec<u8>, Vec<u8>), SerdeError> {
        if radix < 2 || radix > 36 {
            return Err(InvalidRadix)
        }
        // I was originally going to include b'-', but it causes insertion performance
        // problems here, and users have to remove it anyway in the usage cases where a
        // prefix is added (we want "-0x123" and not "0x-123")

        let is_zero = this.is_zero();
        let is_negative = this.is_negative();
        let mut unsigned = ExtAwi::zero(this.nzbw());
        unsigned.copy_(this).unwrap();
        // reinterpret as unsigned for `imin`
        unsigned.neg_(is_negative);
        // safe because of invariants
        let tot_lz = unsigned.lz() as isize;

        // the order of these `||` is important to avoid overflow
        let integer_part_zero =
            is_zero || (this.fp() > this.ibw()) || (tot_lz > (this.ibw() - this.fp()));
        let mut integer_part = if integer_part_zero {
            alloc::vec![b'0'; min_integer_chars]
        } else {
            let from = max(this.fp(), 0) as usize;
            // no overflow because of `integer_part_zero` checks
            let bits = this.ibw().wrapping_sub(tot_lz);
            let integer_bits = bits.wrapping_sub(this.fp()) as usize;
            let field_bits = bits.wrapping_sub(from as isize) as usize;
            // if the fixed point mandates more trailing zeroes in the integer part
            let extra_zeros = if this.fp() < 0 {
                this.fp().unsigned_abs()
            } else {
                0
            };
            match NonZeroUsize::new(integer_bits) {
                Some(integer_bits) => {
                    let mut tmp = ExtAwi::zero(integer_bits);
                    tmp.field(extra_zeros, &unsigned, from, field_bits).unwrap();
                    // note: we do not unwrap here in case of resource exhaustion
                    ExtAwi::bits_to_vec_radix(&tmp, false, radix, upper, min_integer_chars)?
                }
                None => alloc::vec![b'0'; min_integer_chars],
            }
        };

        let tot_tz = unsigned.tz() as isize;
        // order is important again
        let fraction_part_zero = is_zero || (this.fp() <= 0) || (tot_tz >= this.fp());
        let mut fraction_part = if fraction_part_zero {
            alloc::vec![b'0'; min_fraction_chars]
        } else {
            let unique_digits = this
                .fp_ty()
                .unique_min_fraction_digits(usize::from(radix))
                .unwrap();
            let calc_digits = max(unique_digits, min_fraction_chars);
            let multiplier_bits = bits_upper_bound(calc_digits, radix)?;
            // avoid needing some calculation by dropping zero bits that have no impact
            let calc_fp = this.fp().wrapping_sub(tot_tz) as usize;
            let field_bits = min(this.fp(), this.ibw()).wrapping_sub(tot_tz) as usize;
            let mut tmp = ExtAwi::zero(
                NonZeroUsize::new(multiplier_bits.checked_add(calc_fp).ok_or(Overflow)?).unwrap(),
            );
            tmp.field_from(&unsigned, tot_tz as usize, field_bits)
                .unwrap();
            for _ in 0..calc_digits {
                tmp.short_cin_mul(0, usize::from(radix));
            }
            let inc = if (tmp.get_digit(calc_fp.checked_sub(1).ok_or(Overflow)?) & 1) == 0 {
                // round down
                false
            } else if tmp.tz().checked_add(1).ok_or(Overflow)? == calc_fp {
                // round to even
                (tmp.get_digit(calc_fp) & 1) == 0
            } else {
                // round up
                true
            };
            tmp.lshr_(calc_fp).unwrap();
            tmp.inc_(inc);
            // note: we do not unwrap here in case of resource exhaustion
            let mut s = ExtAwi::bits_to_vec_radix(&tmp, false, radix, upper, calc_digits)?;
            // trim off zeroes
            while s.len() > min_fraction_chars {
                // s.len() > 0 so this cannot overflow
                if s[s.len().wrapping_sub(1)] == b'0' {
                    let _ = s.pop();
                } else {
                    break
                }
            }
            s
        };
        integer_part.shrink_to_fit();
        fraction_part.shrink_to_fit();
        Ok((integer_part, fraction_part))
    }

    /// Creates a tuple of `String`s representing the integer and fraction
    /// parts of `this`. This does the same thing as `[FP::to_vec_general]`
    /// but with `String`s.
    pub fn to_str_general(
        this: &Self,
        radix: u8,
        upper: bool,
        min_integer_chars: usize,
        min_fraction_chars: usize,
    ) -> Result<(String, String), SerdeError> {
        let (i, f) = FP::to_vec_general(this, radix, upper, min_integer_chars, min_fraction_chars)?;
        Ok((String::from_utf8(i).unwrap(), String::from_utf8(f).unwrap()))
    }
}