dithr 0.3.0

Buffer-first rust dithering and halftoning library.
Documentation
use crate::{Error, Result};

pub fn mul_div_i32(value: i32, numer: i32, denom: i32) -> Result<i32> {
    if denom == 0 {
        return Err(Error::InvalidArgument("denominator must be non-zero"));
    }

    let product = i64::from(value) * i64::from(numer);
    let quotient = product / i64::from(denom);
    let max = i64::from(i32::MAX);
    let min = i64::from(i32::MIN);

    if quotient > max {
        Ok(i32::MAX)
    } else if quotient < min {
        Ok(i32::MIN)
    } else {
        Ok(quotient as i32)
    }
}

#[must_use]
pub fn round_shift_i32(value: i32, shift: u32) -> i32 {
    if shift == 0 {
        return value;
    }

    if shift >= 32 {
        return if value >= 0 { 0 } else { -1 };
    }

    let half = 1_i64 << (shift - 1);
    let adjusted = if value >= 0 {
        i64::from(value) + half
    } else {
        i64::from(value) - half
    };
    let shifted = adjusted >> shift;
    let max = i64::from(i32::MAX);
    let min = i64::from(i32::MIN);

    if shifted > max {
        i32::MAX
    } else if shifted < min {
        i32::MIN
    } else {
        shifted as i32
    }
}

#[cfg(test)]
mod tests {
    use super::mul_div_i32;
    use crate::Error;

    #[test]
    fn mul_div_i32_matches_simple_cases() {
        assert_eq!(mul_div_i32(10, 3, 2), Ok(15));
        assert_eq!(mul_div_i32(7, 2, 3), Ok(4));
        assert_eq!(mul_div_i32(-10, 3, 2), Ok(-15));
        assert_eq!(mul_div_i32(10, -3, 2), Ok(-15));
    }

    #[test]
    fn mul_div_i32_rejects_zero_denominator() {
        assert_eq!(
            mul_div_i32(10, 3, 0),
            Err(Error::InvalidArgument("denominator must be non-zero"))
        );
    }
}