fxd 0.1.4

Fixed-point decimal implementation
Documentation
use crate::decimal::{FixedDecimal, DIGITS_PER_UNIT, MAX_DIGITS, MAX_FRAC};
use crate::error::{Error, Result};
use crate::utils::{d3_to_str, get_units, mod9, BIN2CHAR, BIN_HIGH_UNIT, BIN_MID_UNIT, POW10};
use std::cmp::Ordering;
use std::str::FromStr;

impl FixedDecimal {
    /// parses numeric string and updates decimal value
    #[inline]
    pub fn from_ascii_str(&mut self, s: &str, reset: bool) -> Result<()> {
        self.from_bytes_str(s.as_bytes(), reset)
    }

    /// parses numeric string and updates decimal value
    #[inline]
    pub fn from_bytes_str(&mut self, bs: &[u8], reset: bool) -> Result<()> {
        if reset {
            self.set_zero();
        }
        if bs.is_empty() {
            return Err(Error::ConversionSyntax);
        }
        let mut exp = 0; // exponent
        let mut d = 0; // total digits of input
        let mut dotchar = -1; // position of dot character
        let mut last = -1; // position of last digit
        let mut cfirst = 0; // position of first digit
        let mut neg = false; // if input is negative
        let mut more_to_proc = false;
        let mut i: isize = -1;
        for c in bs.iter() {
            i += 1;
            if *c >= b'0' && *c <= b'9' {
                // test for digit char
                last = i as isize;
                d += 1;
                continue;
            }
            if *c == b'.' && dotchar == -1 {
                dotchar = i as isize;
                if i == cfirst {
                    cfirst += 1; // first digit must follow
                }
                continue;
            }
            if i == 0 {
                // first in string
                if *c == b'-' {
                    // valid - sign
                    cfirst += 1;
                    neg = true;
                    continue;
                }
                if *c == b'+' {
                    // valid + sign
                    cfirst += 1;
                    continue;
                }
            }
            // c is not a digit, or a valid '+', '-' or '.'
            more_to_proc = true; // indicate more data to process
            break;
        }
        if last == -1 {
            // no digits yet
            // decided not to support Infinities and NaNs now
            return Err(Error::ConversionSyntax);
        }
        if more_to_proc {
            // has some digits; exponent is only valid sequence now
            let mut nege = false; // negative exponent
            let mut c = bs[i as usize];
            if c != b'e' && c != b'E' {
                return Err(Error::ConversionSyntax);
            }
            // found 'e' or 'E'
            // sign no longer required
            i += 1;
            if i as usize == bs.len() {
                // to (possible) sign
                return Err(Error::ConversionSyntax);
            }
            c = bs[i as usize];
            if c == b'-' {
                nege = true;
                i += 1;
                if i as usize == bs.len() {
                    return Err(Error::ConversionSyntax);
                }
            } else if c == b'+' {
                i += 1;
                if i as usize == bs.len() {
                    return Err(Error::ConversionSyntax);
                }
            }
            loop {
                c = bs[i as usize];
                if c == b'0' && i as usize != bs.len() - 1 {
                    // strip insignificant zeroes
                    i += 1;
                } else {
                    break;
                }
            }
            let firstexp = i; // save exponent digit place
            for c in bs[i as usize..].iter() {
                if *c < b'0' || *c > b'9' {
                    // not a digit
                    return Err(Error::ConversionSyntax);
                }
                exp = exp * 10 + *c as i32 - '0' as i32;
                i += 1
            }
            // maximum exponent is 65, with sign at most 4 chars
            if i >= firstexp + 4 {
                return Err(Error::ConversionSyntax);
            }
            if (!nege && exp as usize > MAX_DIGITS) || (nege && exp as usize > MAX_FRAC) {
                return Err(Error::ConversionSyntax);
            }
            if nege {
                exp = -exp;
            }
        }
        // Here when whole string has been inspected; syntax is good
        // cfirst->first digit(never dot), last->last digit(ditto)
        let frac_digits = if dotchar == -1 || last < dotchar {
            // no dot found or dot is last char
            0
        } else {
            last - dotchar
        };
        // apply exponent to frac
        let (frac, digits, mut heading_zeroes) = {
            let mut frac = frac_digits as i8 - exp as i8;
            let digits;
            let mut heading_zeroes = 0i8;
            match frac.cmp(&0) {
                Ordering::Equal => digits = d,
                Ordering::Greater => {
                    if d > frac {
                        // have both integral and fractional part
                        digits = d;
                    } else {
                        // only fractional part
                        digits = frac;
                        heading_zeroes = frac - d;
                    }
                }
                Ordering::Less => {
                    digits = d - frac;
                    frac = 0;
                }
            }
            (frac, digits, heading_zeroes)
        };

        if digits as usize > MAX_DIGITS {
            return Err(Error::ConversionSyntax);
        }

        // units of integral part and fractional part
        let intg_units = get_units(digits - frac);
        let frac_units = get_units(frac);
        let mut up = (intg_units + frac_units - 1) as isize; // lsu unit index, from highest to lowest
                                                             // reset i as parse index
        i = cfirst;
        if intg_units > 0 {
            let mut out = 0;
            let mut cut = digits - frac;
            let mut c;
            loop {
                c = bs[i as usize];
                if c == b'.' {
                    // ignore '.', this may be caused by exponent normalization
                    i += 1;
                    continue;
                }
                out = out * 10 + c as i32 - b'0' as i32;
                cut -= 1;
                if cut == 0 {
                    break; // nothing for this unit
                }
                if i == last {
                    // no more digits to read, adjust out if cut > 0
                    break;
                }
                if mod9(cut) > 0 {
                    i += 1;
                    continue;
                }
                self.lsu[up as usize] = out; // write out
                up -= 1; // prepare for unit below
                out = 0;
                i += 1;
            }
            // input integral digits processed.
            // increment i for frac processing
            i += 1;
            // handle cut > 0, e.g. "1E2", "1E20"
            let re = mod9(cut);
            self.lsu[up as usize] = out * POW10[re];
            up -= 1;
            cut -= re as i8;
            while cut > 0 {
                self.lsu[up as usize] = 0;
                up -= 1;
                cut -= DIGITS_PER_UNIT as i8;
            }
        }
        if frac_units > 0 {
            let mut out = 0; // accumulator in unit
            let mut cut = DIGITS_PER_UNIT as i8;
            while heading_zeroes >= DIGITS_PER_UNIT as i8 {
                // current unit filled all zeroes
                self.lsu[up as usize] = 0;
                up -= 1;
                heading_zeroes -= DIGITS_PER_UNIT as i8;
            }
            cut -= heading_zeroes as i8;
            // parse fractional part
            let mut c;
            loop {
                c = bs[i as usize];
                if c == b'.' {
                    // ignore '.', this may be caused by exponent normalization
                    i += 1;
                    continue;
                }
                cut -= 1;
                out += (c as i32 - '0' as i32) * POW10[cut as usize];
                if i == last {
                    // done
                    break;
                }
                if cut > 0 {
                    i += 1;
                    continue; // more for this unit
                }
                self.lsu[up as usize] = out; // write unit
                up -= 1; // prepare for unit below
                cut = DIGITS_PER_UNIT as i8;
                out = 0;
                i += 1;
            }
            self.lsu[up as usize] = out; // write lsu
        }
        self.intg = digits - frac;
        self.frac = frac;
        if neg {
            self.set_neg();
        }
        Ok(())
    }

    /// converts this decimal to string format.
    /// frac specifies the fractional precision of the output string.
    /// if frac < 0, will output all fractional digits.
    /// if frac >= 0, will truncate to given digits.
    #[inline]
    pub fn to_string(&self, frac: isize) -> String {
        let mut bs = Vec::new();
        self.append_str_buf(&mut bs, frac);
        unsafe { String::from_utf8_unchecked(bs) }
    }

    /// appends formatted string to given buffer.
    /// if frac < 0, will output all fractional digits.
    /// if frac >= 0, will truncate to given digits.
    #[inline]
    pub fn append_str_buf(&self, buf: &mut Vec<u8>, frac: isize) {
        if self.is_neg() {
            buf.push(b'-');
        }
        let intg_units = self.intg_units();
        let frac_units = self.frac_units();
        let mut up = (intg_units + frac_units) as isize - 1;
        if intg_units > 0 {
            // intg part
            let mut elim_heading_zeroes = true;
            while up >= frac_units as isize {
                // for each unit, we divide into 3 3-digit parts and print them each time
                let mut u = self.lsu[up as usize];
                up -= 1;
                if elim_heading_zeroes && u == 0 {
                    // rare case, integral part exists but zero
                    continue;
                }
                // XXX,xxx,xxx
                if u >= BIN_HIGH_UNIT {
                    let high = u / BIN_HIGH_UNIT;
                    elim_heading_zeroes = d3_to_str(high, buf, elim_heading_zeroes);
                    u -= high * BIN_HIGH_UNIT;
                } else {
                    elim_heading_zeroes = d3_to_str(0, buf, elim_heading_zeroes);
                }
                // xxx,XXX,xxx
                if u >= BIN_MID_UNIT {
                    let mid = u / BIN_MID_UNIT;
                    elim_heading_zeroes = d3_to_str(mid, buf, elim_heading_zeroes);
                    u -= mid * BIN_MID_UNIT;
                } else {
                    elim_heading_zeroes = d3_to_str(0, buf, elim_heading_zeroes);
                }
                // xxx,xxx,XXX
                elim_heading_zeroes = d3_to_str(u, buf, elim_heading_zeroes);
            }
            if elim_heading_zeroes {
                // integral units are all zeroes
                buf.push(b'0');
            }
        } else {
            buf.push(b'0'); // append 0 if no integral part
        }
        if frac == 0 {
            // no fractional digits
            return;
        }
        if frac < 0 {
            // frac is not specified
            if frac_units > 0 {
                // fractional part exists
                buf.push(b'.');
                let frac_digits = self.frac();
                self.append_frac(buf, up as isize, frac_digits);
            }
            return;
        }
        // frac is specified
        if frac_units == 0 {
            // no fractional part
            buf.push(b'.');
            for _ in 0..frac {
                buf.push(b'0');
            }
            return;
        }
        let full_frac_digits = (frac_units * DIGITS_PER_UNIT) as isize;
        if frac <= full_frac_digits {
            // this decimal supports specified frac
            buf.push(b'.');
            self.append_frac(buf, up, frac as i8);
            return;
        }
        // this decimal does not have sufficient precision
        buf.push(b'.');
        self.append_frac(buf, up, full_frac_digits as i8);
        // add extra zeroes
        for _ in 0..frac - full_frac_digits {
            buf.push(b'0');
        }
    }

    fn append_frac(&self, buf: &mut Vec<u8>, mut up: isize, mut frac_digits: i8) {
        while up >= 0 {
            // for each unit, divide into 3 3-digit parts and print.
            // need to check whether the digits exceeds fractional length.
            if frac_digits == 0 {
                // already reached the fractional precision limit
                return;
            }
            let mut u = self.lsu[up as usize];
            up -= 1;
            if u == 0 {
                // all zeros
                if frac_digits > DIGITS_PER_UNIT as i8 {
                    // digit number larger than
                    buf.extend_from_slice(b"000000000");
                    frac_digits -= DIGITS_PER_UNIT as i8;
                    continue;
                }
                // append n zeroes and return
                for _ in 0..frac_digits {
                    buf.push(b'0');
                }
                return;
            }
            // non-zero
            // XXX,xxx,xxx
            if u >= BIN_HIGH_UNIT {
                let high = u / BIN_HIGH_UNIT;
                let start_idx = high as usize * 4 + 1;
                if frac_digits < 3 {
                    buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + frac_digits as usize]);
                    return;
                }
                buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + 3]);
                frac_digits -= 3;
                if frac_digits == 0 {
                    return;
                }
                u -= high * BIN_HIGH_UNIT;
            } else {
                if frac_digits < 3 {
                    for _ in 0..frac_digits {
                        buf.push(b'0');
                    }
                    return;
                }
                buf.extend_from_slice(b"000");
                frac_digits -= 3;
                if frac_digits == 0 {
                    return;
                }
            }
            // xxx,XXX,xxx
            if u >= BIN_MID_UNIT {
                let mid = u / BIN_MID_UNIT;
                let start_idx = mid as usize * 4 + 1;
                if frac_digits < 3 {
                    buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + frac_digits as usize]);
                    return;
                }
                buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + 3]);
                frac_digits -= 3;
                if frac_digits == 0 {
                    return;
                }
                u -= mid * BIN_MID_UNIT;
            } else {
                if frac_digits < 3 {
                    for _ in 0..frac_digits {
                        buf.push(b'0');
                    }
                    return;
                }
                buf.extend_from_slice(b"000");
                frac_digits -= 3;
                if frac_digits == 0 {
                    return;
                }
            }
            // xxx,xxx,XXX
            let start_idx = u as usize * 4 + 1;
            if frac_digits < 3 {
                buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + frac_digits as usize]);
                return;
            }
            buf.extend_from_slice(&BIN2CHAR[start_idx..start_idx + 3]);
            frac_digits -= 3;
            if frac_digits == 0 {
                return;
            }
        }
    }
}

impl FromStr for FixedDecimal {
    type Err = Error;
    #[inline]
    fn from_str(src: &str) -> Result<Self> {
        let mut fd = FixedDecimal::zero();
        fd.from_ascii_str(src, false)?;
        Ok(fd)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_from_str() {
        let mut fd = FixedDecimal::zero();
        for s in vec![
            "0",
            "1",
            "-1",
            "123",
            "123456789012345",
            "0.1",
            "0.123",
            "1.0",
            "-1.0",
            "1E1",
            "1E+2",
            "1.0E-2",
            "1.0e10",
            "1.0e02",
        ] {
            assert!(fd.from_ascii_str(s, true).is_ok());
            println!("result={:?}", fd);
        }
    }

    #[test]
    fn test_from_str_err() {
        let mut fd = FixedDecimal::zero();
        for s in vec![
            "",
            "abc",
            ".",
            ".a",
            ".NaN",
            "N",
            "Nb",
            "Na",
            "Nab",
            "NaNx",
            "0x",
            "0E",
            ".1e+",
            ".1e-",
            ".1e+f",
            ".1e12345",
            ".1e-200",
            "1234567890123456789012345678901234567890123456789012345678901234567890",
        ] {
            assert!(fd.from_ascii_str(s, true).is_err());
        }
    }

    #[test]
    fn test_to_string() {
        let mut fd = FixedDecimal::zero();
        for (input, frac, expected) in vec![
            ("0", -1, "0"),
            ("1", -1, "1"),
            ("-1", -1, "-1"),
            ("+1", -1, "1"),
            ("123", -1, "123"),
            ("123456789012345", -1, "123456789012345"),
            ("0.0", -1, "0.0"),
            ("0.100", -1, "0.100"),
            ("0.1", -1, "0.1"),
            ("0.12345678901234567890", -1, "0.12345678901234567890"),
            ("0.123", -1, "0.123"),
            ("1.0", -1, "1.0"),
            ("-1.0", -1, "-1.0"),
            ("1e0", -1, "1"),
            ("1E1", -1, "10"),
            ("1E+2", -1, "100"),
            ("1.0E-2", -1, "0.010"),
            ("1.0e10", -1, "10000000000"),
            ("1.2345e20", -1, "123450000000000000000"),
            ("5.4433e4", -1, "54433"),
            ("5.4433e3", -1, "5443.3"),
            ("5.4433e2", -1, "544.33"),
            ("5.4433e1", -1, "54.433"),
            ("5.4433e0", -1, "5.4433"),
            ("5.4433e-1", -1, "0.54433"),
            ("5.4433e-2", -1, "0.054433"),
            ("5.4433e-3", -1, "0.0054433"),
            ("5.4433e-5", -1, "0.000054433"),
            ("5.4433e-6", -1, "0.0000054433"),
            ("5.4433e-7", -1, "0.00000054433"),
            ("5.4433e-8", -1, "0.000000054433"),
            ("5.4433e-9", -1, "0.0000000054433"),
            ("5.4433e-10", -1, "0.00000000054433"),
            ("5.4433e-11", -1, "0.000000000054433"),
            ("5.4433e-20", -1, "0.000000000000000000054433"),
            ("123456789.123456789", 0, "123456789"),
            ("123456789.123456789", 1, "123456789.1"),
            ("123456789.123456789", 2, "123456789.12"),
            ("123456789.123456789", 3, "123456789.123"),
            ("123456789.123456789", 4, "123456789.1234"),
            ("123456789.123456789", 5, "123456789.12345"),
            ("123456789.123456789", 6, "123456789.123456"),
            ("123456789.123456789", 7, "123456789.1234567"),
            ("123456789.123456789", 8, "123456789.12345678"),
            ("123456789.123456789", 9, "123456789.123456789"),
            ("123456789.123456789", 10, "123456789.1234567890"),
            ("1.234000567", 4, "1.2340"),
            ("1.234000567", 5, "1.23400"),
            ("1.234000567", 6, "1.234000"),
            ("1.021", 2, "1.02"),
            ("1.00021", 2, "1.00"),
            ("123", 2, "123.00"),
        ] {
            assert!(fd.from_ascii_str(&input, true).is_ok());
            println!("fd={:?}", fd);
            let actual = fd.to_string(frac);
            assert_eq!(expected, &actual[..]);
        }
    }
}