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 = match mode {
114 None => RoundingMode::default(),
115 Some(mode) => mode,
116 };
117 match mode {
118 RoundingMode::Round05Up => {
119 if quot >= 0 && quot % 5 == 0 || quot < 0 && (quot + 1) % 5 != 0 {
124 return quot + 1;
125 }
126 }
127 RoundingMode::RoundCeiling => {
128 return quot + 1;
131 }
132 RoundingMode::RoundDown => {
133 if quot < 0 {
136 return quot + 1;
137 }
138 }
139 RoundingMode::RoundFloor => {
140 return quot;
143 }
144 RoundingMode::RoundHalfDown => {
145 let rem_doubled = rem << 1;
150 if rem_doubled > divisor || rem_doubled == divisor && quot < 0 {
151 return quot + 1;
152 }
153 }
154 RoundingMode::RoundHalfEven => {
155 let rem_doubled = rem << 1;
160 if rem_doubled > divisor
161 || rem_doubled == divisor && quot % 2 != 0
162 {
163 return quot + 1;
164 }
165 }
166 RoundingMode::RoundHalfUp => {
167 let rem_doubled = rem << 1;
172 if rem_doubled > divisor || rem_doubled == divisor && quot >= 0 {
173 return quot + 1;
174 }
175 }
176 RoundingMode::RoundUp => {
177 if quot >= 0 {
180 return quot + 1;
181 }
182 }
183 }
184 quot
186}
187
188#[doc(hidden)]
190#[must_use]
191#[allow(clippy::cast_sign_loss)]
192pub fn i128_div_rounded(
193 mut divident: i128,
194 mut divisor: i128,
195 mode: Option<RoundingMode>,
196) -> i128 {
197 if divisor < 0 {
198 divident = -divident;
199 divisor = -divisor;
200 }
201 let (quot, rem) = i128_div_mod_floor(divident, divisor);
202 round_quot(quot, rem as u128, divisor as u128, mode)
204}
205
206#[doc(hidden)]
209#[must_use]
210#[allow(clippy::cast_sign_loss)]
211pub fn i128_shifted_div_rounded(
212 mut divident: i128,
213 p: u8,
214 mut divisor: i128,
215 mode: Option<RoundingMode>,
216) -> Option<i128> {
217 if divisor < 0 {
218 divident = -divident;
219 divisor = -divisor;
220 }
221 let (quot, rem) = i128_shifted_div_mod_floor(divident, p, divisor)?;
222 Some(round_quot(quot, rem as u128, divisor as u128, mode))
224}
225
226#[doc(hidden)]
228#[must_use]
229#[allow(clippy::cast_sign_loss)]
230pub fn i128_mul_div_ten_pow_rounded(
231 x: i128,
232 y: i128,
233 p: u8,
234 mode: Option<RoundingMode>,
235) -> Option<i128> {
236 let divisor = ten_pow(p);
237 let (quot, rem) = i256_div_mod_floor(x, y, divisor)?;
238 Some(round_quot(quot, rem as u128, divisor as u128, mode))
240}
241
242#[cfg(feature = "std")]
243#[cfg(test)]
244mod rounding_mode_tests {
245 use super::*;
246
247 #[test]
248 fn test1() {
249 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
250 RoundingMode::set_default(RoundingMode::RoundUp);
251 assert_eq!(RoundingMode::default(), RoundingMode::RoundUp);
252 RoundingMode::set_default(RoundingMode::RoundHalfEven);
253 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
254 }
255
256 #[test]
257 fn test2() {
258 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
259 RoundingMode::set_default(RoundingMode::RoundHalfUp);
260 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfUp);
261 RoundingMode::set_default(RoundingMode::RoundHalfEven);
262 assert_eq!(RoundingMode::default(), RoundingMode::RoundHalfEven);
263 }
264}
265
266#[cfg(test)]
267mod helper_tests {
268 use super::*;
269
270 const TESTDATA: [(i128, i128, RoundingMode, i128); 34] = [
271 (17, 5, RoundingMode::Round05Up, 3),
272 (27, 5, RoundingMode::Round05Up, 6),
273 (-17, 5, RoundingMode::Round05Up, -3),
274 (-27, 5, RoundingMode::Round05Up, -6),
275 (17, 5, RoundingMode::RoundCeiling, 4),
276 (15, 5, RoundingMode::RoundCeiling, 3),
277 (-17, 5, RoundingMode::RoundCeiling, -3),
278 (-15, 5, RoundingMode::RoundCeiling, -3),
279 (19, 5, RoundingMode::RoundDown, 3),
280 (15, 5, RoundingMode::RoundDown, 3),
281 (-18, 5, RoundingMode::RoundDown, -3),
282 (-15, 5, RoundingMode::RoundDown, -3),
283 (19, 5, RoundingMode::RoundFloor, 3),
284 (15, 5, RoundingMode::RoundFloor, 3),
285 (-18, 5, RoundingMode::RoundFloor, -4),
286 (-15, 5, RoundingMode::RoundFloor, -3),
287 (19, 2, RoundingMode::RoundHalfDown, 9),
288 (15, 4, RoundingMode::RoundHalfDown, 4),
289 (-19, 2, RoundingMode::RoundHalfDown, -9),
290 (-15, 4, RoundingMode::RoundHalfDown, -4),
291 (19, 2, RoundingMode::RoundHalfEven, 10),
292 (15, 4, RoundingMode::RoundHalfEven, 4),
293 (-225, 50, RoundingMode::RoundHalfEven, -4),
294 (-15, 4, RoundingMode::RoundHalfEven, -4),
295 (
296 u64::MAX as i128,
297 i64::MIN as i128 * 10,
298 RoundingMode::RoundHalfEven,
299 0,
300 ),
301 (19, 2, RoundingMode::RoundHalfUp, 10),
302 (10802, 4321, RoundingMode::RoundHalfUp, 2),
303 (-19, 2, RoundingMode::RoundHalfUp, -10),
304 (-10802, 4321, RoundingMode::RoundHalfUp, -2),
305 (19, 2, RoundingMode::RoundUp, 10),
306 (10802, 4321, RoundingMode::RoundUp, 3),
307 (-19, 2, RoundingMode::RoundUp, -10),
308 (-10802, 4321, RoundingMode::RoundUp, -3),
309 (i32::MAX as i128, 1, RoundingMode::RoundUp, i32::MAX as i128),
310 ];
311
312 #[test]
313 fn test_div_rounded() {
314 for (divident, divisor, rnd_mode, result) in TESTDATA {
315 let quot = i128_div_rounded(divident, divisor, Some(rnd_mode));
316 assert_eq!(quot, result);
317 }
318 }
319}