1use crate::error::IntegerError;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum IntegerSign {
6 Negative,
7 Zero,
8 Positive,
9}
10
11#[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#[must_use]
25pub const fn is_even(value: i128) -> bool {
26 value % 2 == 0
27}
28
29#[must_use]
31pub const fn is_odd(value: i128) -> bool {
32 !is_even(value)
33}
34
35pub 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#[must_use]
50pub const fn gcd(left: i128, right: i128) -> u128 {
51 gcd_u128(left.unsigned_abs(), right.unsigned_abs())
52}
53
54#[must_use]
56pub const fn are_coprime(left: i128, right: i128) -> bool {
57 gcd(left, right) == 1
58}
59
60pub 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}