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 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]
191pub fn i128_div_rounded(
192 mut divident: i128,
193 mut divisor: i128,
194 mode: Option<RoundingMode>,
195) -> i128 {
196 if divisor < 0 {
197 divident = -divident;
198 divisor = -divisor;
199 }
200 let (quot, rem) = i128_div_mod_floor(divident, divisor);
201 round_quot(quot, rem as u128, divisor as u128, mode)
203}
204
205#[doc(hidden)]
208#[must_use]
209pub fn i128_shifted_div_rounded(
210 mut divident: i128,
211 p: u8,
212 mut divisor: i128,
213 mode: Option<RoundingMode>,
214) -> Option<i128> {
215 if divisor < 0 {
216 divident = -divident;
217 divisor = -divisor;
218 }
219 let (quot, rem) = i128_shifted_div_mod_floor(divident, p, divisor)?;
220 Some(round_quot(quot, rem as u128, divisor as u128, mode))
222}
223
224#[doc(hidden)]
226#[must_use]
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}