Skip to main content

use_integer/
integer.rs

1use crate::error::IntegerError;
2
3/// Sign classification for a signed integer value.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum IntegerSign {
6    Negative,
7    Zero,
8    Positive,
9}
10
11/// Classifies a signed integer as negative, zero, or positive.
12#[must_use]
13pub const fn classify_sign(value: i128) -> IntegerSign {
14    if value < 0 {
15        IntegerSign::Negative
16    } else if value > 0 {
17        IntegerSign::Positive
18    } else {
19        IntegerSign::Zero
20    }
21}
22
23/// Returns whether an integer is evenly divisible by two.
24#[must_use]
25pub const fn is_even(value: i128) -> bool {
26    value % 2 == 0
27}
28
29/// Returns whether an integer leaves a remainder of one when divided by two.
30#[must_use]
31pub const fn is_odd(value: i128) -> bool {
32    !is_even(value)
33}
34
35/// Returns whether `value` is evenly divisible by `divisor`.
36///
37/// # Errors
38///
39/// Returns [`IntegerError::DivisionByZero`] when `divisor` is zero.
40pub const fn is_divisible_by(value: i128, divisor: i128) -> Result<bool, IntegerError> {
41    if divisor == 0 {
42        Err(IntegerError::DivisionByZero)
43    } else {
44        Ok(value % divisor == 0)
45    }
46}
47
48/// Computes the non-negative greatest common divisor of two signed integers.
49#[must_use]
50pub const fn gcd(left: i128, right: i128) -> u128 {
51    gcd_u128(left.unsigned_abs(), right.unsigned_abs())
52}
53
54/// Returns whether two integers share no positive common divisor other than one.
55#[must_use]
56pub const fn are_coprime(left: i128, right: i128) -> bool {
57    gcd(left, right) == 1
58}
59
60/// Computes the non-negative least common multiple of two signed integers.
61///
62/// # Errors
63///
64/// Returns [`IntegerError::ArithmeticOverflow`] when the least common multiple does not fit in `u128`.
65pub const fn lcm(left: i128, right: i128) -> Result<u128, IntegerError> {
66    if left == 0 || right == 0 {
67        return Ok(0);
68    }
69
70    let divisor = gcd(left, right);
71    let scaled = left.unsigned_abs() / divisor;
72
73    match scaled.checked_mul(right.unsigned_abs()) {
74        Some(result) => Ok(result),
75        None => Err(IntegerError::ArithmeticOverflow { operation: "lcm" }),
76    }
77}
78
79const fn gcd_u128(mut left: u128, mut right: u128) -> u128 {
80    while right != 0 {
81        let remainder = left % right;
82        left = right;
83        right = remainder;
84    }
85
86    left
87}
88
89#[cfg(test)]
90mod tests {
91    use super::{
92        IntegerSign, are_coprime, classify_sign, gcd, is_divisible_by, is_even, is_odd, lcm,
93    };
94    use crate::IntegerError;
95
96    #[test]
97    fn classifies_sign_and_parity() {
98        assert_eq!(classify_sign(-1), IntegerSign::Negative);
99        assert_eq!(classify_sign(0), IntegerSign::Zero);
100        assert_eq!(classify_sign(1), IntegerSign::Positive);
101        assert!(is_even(-8));
102        assert!(is_odd(-7));
103    }
104
105    #[test]
106    fn computes_divisibility_and_common_divisors() -> Result<(), IntegerError> {
107        assert!(is_divisible_by(81, 9)?);
108        assert!(!is_divisible_by(81, 8)?);
109        assert_eq!(gcd(-54, 24), 6);
110        assert_eq!(gcd(0, 0), 0);
111        assert_eq!(lcm(-6, 15)?, 30);
112        assert_eq!(lcm(0, 15)?, 0);
113        assert!(are_coprime(35, 64));
114
115        Ok(())
116    }
117
118    #[test]
119    fn rejects_invalid_divisors_and_reports_overflow() {
120        assert_eq!(is_divisible_by(10, 0), Err(IntegerError::DivisionByZero));
121        assert!(matches!(
122            lcm(i128::MAX, i128::MAX - 1),
123            Err(IntegerError::ArithmeticOverflow { operation: "lcm" })
124        ));
125    }
126}