rounded_div/
lib.rs

1#![no_std]
2//! Get rounded result of an integer division.
3//!
4//! ### `const fn rounded_div_*`
5//!
6//! ```
7//! assert_eq!(rounded_div::i32( 59, 4),  15); // 59/4 is equal to 14.75 which is closer to 15
8//! assert_eq!(rounded_div::i32( 58, 4),  15); // 58/4 is equal to 14.5 which is rounded to 15
9//! assert_eq!(rounded_div::i32( 57, 4),  14); // 57/4 is equal to 14.25 which is closer to 14
10//! assert_eq!(rounded_div::i32(-59, 4), -15);
11//! assert_eq!(rounded_div::i32(-58, 4), -15);
12//! assert_eq!(rounded_div::i32(-57, 4), -14);
13//! ```
14//!
15//! ### `trait RoundedDiv`
16//!
17//! ```
18//! use rounded_div::RoundedDiv;
19//! assert_eq!( 59i32.rounded_div(4),  15); // 59/4 is equal to 14.75 which is closer to 15
20//! assert_eq!( 58i32.rounded_div(4),  15); // 58/4 is equal to 14.5 which is rounded to 15
21//! assert_eq!( 57i32.rounded_div(4),  14); // 57/4 is equal to 14.25 which is closer to 14
22//! assert_eq!(-59i32.rounded_div(4), -15);
23//! assert_eq!(-58i32.rounded_div(4), -15);
24//! assert_eq!(-57i32.rounded_div(4), -14);
25//! ```
26
27/// Get rounded result of an integer division.
28pub trait RoundedDiv<Rhs = Self> {
29    /// Type of the rounded result.
30    type Output;
31    /// Get rounded result of an integer division.
32    fn rounded_div(self, rhs: Rhs) -> Self::Output;
33}
34
35macro_rules! aliases {
36    ($name:ident -> $data:ident) => {
37        impl RoundedDiv for $data {
38            type Output = Self;
39
40            #[inline]
41            fn rounded_div(self, rhs: $data) -> Self::Output {
42                $name(self, rhs)
43            }
44        }
45
46        pub use $name as $data;
47    };
48}
49
50macro_rules! unsigned_div_fn {
51    ($name:ident -> $data:ident) => {
52        /// Get rounded result of an integer division.
53        #[inline]
54        pub const fn $name(dividend: $data, divisor: $data) -> $data {
55            let quotient = dividend / divisor;
56            let remainder = dividend % divisor;
57            quotient + (remainder << 1 >= divisor) as $data
58        }
59
60        aliases!($name -> $data);
61    };
62}
63
64macro_rules! signed_div_fn {
65    ($name:ident -> $data:ident do as $unsigned:ident) => {
66        /// Get rounded result of an integer division.
67        pub const fn $name(dividend: $data, divisor: $data) -> $data {
68            // Q: No negative sign before `$data::MIN` to invert it. Is it correct?
69            // A: It is correct and defined.
70            // Q: Why do it? Why don't we just use `-`?
71            // A: Because `-$data::MIN` would panic with overflow in debug mode.
72            const NEG_OF_MIN: $unsigned = $data::MIN as $unsigned;
73
74            // The following constants are necessary because older version of Rust does not
75            // support constant expressions in match arms.
76            const HALF_MIN: $data = $data::MIN / 2;
77            const HALF_MIN_PLUS_1: $data = HALF_MIN + 1;
78            const NEG_HALF_MIN: $data = -HALF_MIN;
79            const NEG_HALF_MIN_MINUS_1: $data = NEG_HALF_MIN - 1;
80            const HALF_MAX: $data = $data::MAX / 2;
81            const HALF_MAX_PLUS_1: $data = HALF_MAX + 1;
82            const NEG_HALF_MAX: $data = -HALF_MAX;
83            const NEG_HALF_MAX_MINUS_1: $data = NEG_HALF_MAX - 1;
84
85            match (dividend, divisor) {
86                ($data::MIN, -1) => panic!("attempt to divide with overflow"),
87                ($data::MIN, $data::MIN) | ($data::MAX, $data::MAX) => 1,
88                ($data::MIN, $data::MAX) | ($data::MAX, $data::MIN) => -1,
89
90                (0, ..=-1 | 1..) => 0,
91                (..=-1 | 1.., 1) => dividend,
92                (..=-1 | 1.., -1) => -dividend,
93
94                (_, 0) => panic!("attempt to divide by zero"),
95
96                ($data::MIN, 1..) => -(self::$unsigned(NEG_OF_MIN, divisor as $unsigned) as $data),
97                ($data::MIN, ..=-1) => self::$unsigned(NEG_OF_MIN, -divisor as $unsigned) as $data,
98
99                (HALF_MIN_PLUS_1..=NEG_HALF_MIN_MINUS_1, $data::MIN) => 0, // MIN is even, so x/MIN where x.abs() < MIN/2 should be less than 0.5
100                (..=HALF_MIN, $data::MIN) => 1, // MIN is even so x/MIN where x <= MIN/2 should be greater than 0.5
101                (NEG_HALF_MIN.., $data::MIN) => -1, // MIN is even so x/MIN where x >= -MIN/2 should be less than -0.5
102
103                (NEG_HALF_MAX..=HALF_MAX, $data::MAX) => 0, // MAX is odd, so x/MAX where x.abs() <= MAX/2 should be less than 0.5
104                (HALF_MAX_PLUS_1.., $data::MAX) => 1, // MAX is odd so x/MAX where x > MAX/2 should be greater than 0.5
105                (..=NEG_HALF_MAX_MINUS_1, $data::MAX) => -1, // MAX is odd so x/MAX where x < -MAX/2 should be less than -0.5
106
107                (1.., 1..) => self::$unsigned(dividend as $unsigned, divisor as $unsigned) as $data,
108                (..=-1, 1..) => -(self::$unsigned(-dividend as $unsigned, divisor as $unsigned) as $data),
109                (1.., ..=-1) => -(self::$unsigned(dividend as $unsigned, -divisor as $unsigned) as $data),
110                (..=-1, ..=-1) => self::$unsigned(-dividend as $unsigned, -divisor as $unsigned) as $data,
111            }
112        }
113
114        aliases!($name -> $data);
115    };
116}
117
118unsigned_div_fn!(rounded_div_u8 -> u8);
119unsigned_div_fn!(rounded_div_u16 -> u16);
120unsigned_div_fn!(rounded_div_u32 -> u32);
121unsigned_div_fn!(rounded_div_u64 -> u64);
122unsigned_div_fn!(rounded_div_u128 -> u128);
123unsigned_div_fn!(rounded_div_usize -> usize);
124
125signed_div_fn!(rounded_div_i8 -> i8 do as u8);
126signed_div_fn!(rounded_div_i16 -> i16 do as u16);
127signed_div_fn!(rounded_div_i32 -> i32 do as u32);
128signed_div_fn!(rounded_div_i64 -> i64 do as u64);
129signed_div_fn!(rounded_div_i128 -> i128 do as u128);
130signed_div_fn!(rounded_div_isize -> isize do as usize);