1#[cfg(feature = "std")]
11use core::cell::RefCell;
12
13use crate::{
14 i128_div_mod_floor, i128_shifted_div_mod_floor, i256_div_mod_floor,
15 ten_pow,
16};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum RoundingMode {
21 Round05Up,
24 RoundCeiling,
26 RoundDown,
28 RoundFloor,
30 RoundHalfDown,
32 RoundHalfEven,
34 RoundHalfUp,
36 RoundUp,
38}
39
40#[cfg(feature = "std")]
41thread_local!(
42 static DFLT_ROUNDING_MODE: RefCell<RoundingMode> =
43 const { RefCell::new(RoundingMode::RoundHalfEven) }
44);
45
46#[cfg(feature = "std")]
47impl Default for RoundingMode {
48 fn default() -> Self {
53 DFLT_ROUNDING_MODE.with(|m| *m.borrow())
54 }
55}
56
57#[cfg(feature = "std")]
58impl RoundingMode {
59 pub fn set_default(mode: Self) {
61 DFLT_ROUNDING_MODE.with(|m| *m.borrow_mut() = mode);
62 }
63}
64
65#[cfg(not(feature = "std"))]
66static DFLT_ROUNDING_MODE: RoundingMode = RoundingMode::RoundHalfEven;
67
68#[cfg(not(feature = "std"))]
69impl Default for RoundingMode {
70 fn default() -> Self {
75 DFLT_ROUNDING_MODE
76 }
77}
78
79pub trait Round
81where
82 Self: Sized,
83{
84 fn round(self, n_frac_digits: i8) -> Self;
88
89 fn checked_round(self, n_frac_digits: i8) -> Option<Self>;
94}
95
96#[inline]
101fn round_quot(
102 quot: i128,
103 rem: u128,
104 divisor: u128,
105 mode: Option<RoundingMode>,
106) -> i128 {
107 if rem == 0 {
108 return quot;
110 }
111 let mode = mode.unwrap_or_default();
114 match mode {
115 RoundingMode::Round05Up => {
116 if quot >= 0 && quot % 5 == 0 || quot < 0 && (quot + 1) % 5 != 0 {
121 return quot + 1;
122 }
123 }
124 RoundingMode::RoundCeiling => {
125 return quot + 1;
128 }
129 RoundingMode::RoundDown => {
130 if quot < 0 {
133 return quot + 1;
134 }
135 }
136 RoundingMode::RoundFloor => {
137 return quot;
140 }
141 RoundingMode::RoundHalfDown => {
142 let rem_doubled = rem << 1;
147 if rem_doubled > divisor || rem_doubled == divisor && quot < 0 {
148 return quot + 1;
149 }
150 }
151 RoundingMode::RoundHalfEven => {
152 let rem_doubled = rem << 1;
157 if rem_doubled > divisor
158 || rem_doubled == divisor && quot % 2 != 0
159 {
160 return quot + 1;
161 }
162 }
163 RoundingMode::RoundHalfUp => {
164 let rem_doubled = rem << 1;
169 if rem_doubled > divisor || rem_doubled == divisor && quot >= 0 {
170 return quot + 1;
171 }
172 }
173 RoundingMode::RoundUp => {
174 if quot >= 0 {
177 return quot + 1;
178 }
179 }
180 }
181 quot
183}
184
185#[doc(hidden)]
187#[must_use]
188#[allow(clippy::cast_sign_loss)]
189pub fn i128_div_rounded(
190 mut divident: i128,
191 mut divisor: i128,
192 mode: Option<RoundingMode>,
193) -> i128 {
194 if divisor < 0 {
195 divident = -divident;
196 divisor = -divisor;
197 }
198 let (quot, rem) = i128_div_mod_floor(divident, divisor);
199 round_quot(quot, rem as u128, divisor as u128, mode)
201}
202
203#[doc(hidden)]
206#[must_use]
207#[allow(clippy::cast_sign_loss)]
208pub fn i128_shifted_div_rounded(
209 mut divident: i128,
210 p: u8,
211 mut divisor: i128,
212 mode: Option<RoundingMode>,
213) -> Option<i128> {
214 if divisor < 0 {
215 divident = -divident;
216 divisor = -divisor;
217 }
218 let (quot, rem) = i128_shifted_div_mod_floor(divident, p, divisor)?;
219 Some(round_quot(quot, rem as u128, divisor as u128, mode))
221}
222
223#[doc(hidden)]
225#[must_use]
226#[allow(clippy::cast_sign_loss)]
227pub fn i128_mul_div_ten_pow_rounded(
228 x: i128,
229 y: i128,
230 p: u8,
231 mode: Option<RoundingMode>,
232) -> Option<i128> {
233 let divisor = ten_pow(p);
234 let (quot, rem) = i256_div_mod_floor(x, y, divisor)?;
235 Some(round_quot(quot, rem as u128, divisor as u128, mode))
237}
238
239#[cfg(feature = "std")]
240#[cfg(test)]
241mod rounding_mode_tests {
242 use super::*;
243
244 #[test]
245 fn test1() {
246 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
247 RoundingMode::set_default(RoundingMode::RoundUp);
248 assert_eq!(RoundingMode::default(), RoundingMode::RoundUp);
249 RoundingMode::set_default(RoundingMode::RoundHalfEven);
250 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
251 }
252
253 #[test]
254 fn test2() {
255 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
256 RoundingMode::set_default(RoundingMode::RoundHalfUp);
257 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfUp);
258 RoundingMode::set_default(RoundingMode::RoundHalfEven);
259 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
260 }
261}
262
263#[cfg(test)]
264mod helper_tests {
265 use super::*;
266
267 const TESTDATA: [(i128, i128, RoundingMode, i128); 34] = [
268 (17, 5, RoundingMode::Round05Up, 3),
269 (27, 5, RoundingMode::Round05Up, 6),
270 (-17, 5, RoundingMode::Round05Up, -3),
271 (-27, 5, RoundingMode::Round05Up, -6),
272 (17, 5, RoundingMode::RoundCeiling, 4),
273 (15, 5, RoundingMode::RoundCeiling, 3),
274 (-17, 5, RoundingMode::RoundCeiling, -3),
275 (-15, 5, RoundingMode::RoundCeiling, -3),
276 (19, 5, RoundingMode::RoundDown, 3),
277 (15, 5, RoundingMode::RoundDown, 3),
278 (-18, 5, RoundingMode::RoundDown, -3),
279 (-15, 5, RoundingMode::RoundDown, -3),
280 (19, 5, RoundingMode::RoundFloor, 3),
281 (15, 5, RoundingMode::RoundFloor, 3),
282 (-18, 5, RoundingMode::RoundFloor, -4),
283 (-15, 5, RoundingMode::RoundFloor, -3),
284 (19, 2, RoundingMode::RoundHalfDown, 9),
285 (15, 4, RoundingMode::RoundHalfDown, 4),
286 (-19, 2, RoundingMode::RoundHalfDown, -9),
287 (-15, 4, RoundingMode::RoundHalfDown, -4),
288 (19, 2, RoundingMode::RoundHalfEven, 10),
289 (15, 4, RoundingMode::RoundHalfEven, 4),
290 (-225, 50, RoundingMode::RoundHalfEven, -4),
291 (-15, 4, RoundingMode::RoundHalfEven, -4),
292 (
293 u64::MAX as i128,
294 i64::MIN as i128 * 10,
295 RoundingMode::RoundHalfEven,
296 0,
297 ),
298 (19, 2, RoundingMode::RoundHalfUp, 10),
299 (10802, 4321, RoundingMode::RoundHalfUp, 2),
300 (-19, 2, RoundingMode::RoundHalfUp, -10),
301 (-10802, 4321, RoundingMode::RoundHalfUp, -2),
302 (19, 2, RoundingMode::RoundUp, 10),
303 (10802, 4321, RoundingMode::RoundUp, 3),
304 (-19, 2, RoundingMode::RoundUp, -10),
305 (-10802, 4321, RoundingMode::RoundUp, -3),
306 (i32::MAX as i128, 1, RoundingMode::RoundUp, i32::MAX as i128),
307 ];
308
309 #[test]
310 fn test_div_rounded() {
311 for (divident, divisor, rnd_mode, result) in TESTDATA {
312 let quot = i128_div_rounded(divident, divisor, Some(rnd_mode));
313 assert_eq!(quot, result);
314 }
315 }
316}