roundable/lib.rs
1//! # Round numbers and durations to a given factor
2//!
3//! This provides an implementation of rounding for various values, including
4//! the the native number types and [`core::time::Duration`] (also known as
5//! `std::time::Duration`).
6//!
7//! The [`Roundable`] trait adds the following functions to roundable values:
8//!
9//! * [`Roundable::try_round_to(factor, tie_strategy)`](Roundable::try_round_to())
10//! (returns `None` on overflow)
11//! * [`Roundable::round_to(factor, tie_strategy)`](Roundable::round_to())
12//! (panics on overflow)
13//!
14//! ### Example
15//!
16//! ```rust
17//! use roundable::{Roundable, Tie};
18//!
19//! assert!(310 == 314.round_to(10, Tie::Up));
20//! assert!(300.0 == 314.1.round_to(100.0, Tie::Up));
21//!
22//! // To avoid panicking on overflow:
23//! assert!(Some(260) == 255.try_round_to(10, Tie::Up));
24//! assert!(None == 255u8.try_round_to(10, Tie::Up));
25//! ```
26//!
27//! ## Tie strategies
28//!
29//! “Ties” are numbers exactly halfway between two round numbers, e.g. 0.5 when
30//! rounding to the nearest whole number. Traditionally, ties are resolved by
31//! picking the higher number, but there are other strategies. `Roundable` supports
32//! the following rules:
33//!
34//! * [`Tie::Up`]: Round ties up (what most people consider correct).
35//! * [`Tie::Down`]: Round ties down.
36//! * [`Tie::TowardZero`]: Round ties toward zero.
37//! * [`Tie::AwayFromZero`]: Round ties away from zero.
38//! * [`Tie::TowardEven`]: Round ties toward the “even” number (see docs).
39//! * [`Tie::TowardOdd`]: Round ties toward the “odd” number (see docs).
40//!
41//! ## Rounding `Duration`
42//!
43//! [`Duration`](core::time::Duration) can be rounded to a `Duration` factor,
44//! just like a number type. For convenience, there are a number of
45//! [constants](#constants) that can be used to make rounding `Duration` easier.
46//!
47//! ```rust
48//! use roundable::{SECOND, MINUTE, Roundable, Tie};
49//! use std::time::Duration;
50//!
51//! assert!(Duration::ZERO == Duration::from_millis(314).round_to(SECOND, Tie::Up));
52//! assert!(MINUTE == Duration::from_millis(59_500).round_to(SECOND, Tie::Up));
53//! ```
54//!
55//! ## `#![no_std]` by default
56//!
57//! You can use this crate with or without `std` and `alloc`. You do not need to
58//! enable or disable features either way.
59//!
60//! ## Minimum supported Rust version
61//!
62//! Currently the minimum supported Rust version (MSRV) is **1.56.1**. Future
63//! increases in the MSRV will require a major version bump.
64
65// Lint configuration in Cargo.toml isn’t supported by cargo-geiger.
66#![forbid(unsafe_code)]
67#![no_std]
68
69mod duration;
70pub use duration::*;
71mod float;
72mod int;
73
74/// How to handle a value that is exactly half, e.g. `5.round_to(10, ...)`.
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum Tie {
77 /// Round half up (what most people consider correct).
78 ///
79 /// ```rust
80 /// use roundable::{Roundable, Tie};
81 ///
82 /// assert!(10 == 5.round_to(10, Tie::Up));
83 /// assert!(0 == (-5).round_to(10, Tie::Up));
84 ///
85 /// // Other values are unaffected:
86 /// assert!(0 == 4.round_to(10, Tie::Up));
87 /// assert!(10 == 6.round_to(10, Tie::Up));
88 /// assert!(0 == (-4).round_to(10, Tie::Up));
89 /// assert!(-10 == (-6).round_to(10, Tie::Up));
90 /// ```
91 Up,
92
93 /// Round half down.
94 ///
95 /// ```rust
96 /// use roundable::{Roundable, Tie};
97 ///
98 /// assert!(0 == 5.round_to(10, Tie::Down));
99 /// assert!(-10 == (-5).round_to(10, Tie::Down));
100 ///
101 /// // Other values are unaffected:
102 /// assert!(0 == 4.round_to(10, Tie::Down));
103 /// assert!(10 == 6.round_to(10, Tie::Down));
104 /// assert!(0 == (-4).round_to(10, Tie::Down));
105 /// assert!(-10 == (-6).round_to(10, Tie::Down));
106 /// ```
107 Down,
108
109 /// Round half toward zero.
110 ///
111 /// ```rust
112 /// use roundable::{Roundable, Tie};
113 ///
114 /// assert!(0 == 5.round_to(10, Tie::TowardZero));
115 /// assert!(0 == (-5).round_to(10, Tie::TowardZero));
116 ///
117 /// // Other values are unaffected:
118 /// assert!(0 == 4.round_to(10, Tie::TowardZero));
119 /// assert!(10 == 6.round_to(10, Tie::TowardZero));
120 /// assert!(0 == (-4).round_to(10, Tie::TowardZero));
121 /// assert!(-10 == (-6).round_to(10, Tie::TowardZero));
122 /// ```
123 TowardZero,
124
125 /// Round half away from zero.
126 ///
127 /// ```rust
128 /// use roundable::{Roundable, Tie};
129 ///
130 /// assert!(10 == 5.round_to(10, Tie::AwayFromZero));
131 /// assert!(-10 == (-5).round_to(10, Tie::AwayFromZero));
132 ///
133 /// // Other values are unaffected:
134 /// assert!(0 == 4.round_to(10, Tie::AwayFromZero));
135 /// assert!(10 == 6.round_to(10, Tie::AwayFromZero));
136 /// assert!(0 == (-4).round_to(10, Tie::AwayFromZero));
137 /// assert!(-10 == (-6).round_to(10, Tie::AwayFromZero));
138 /// ```
139 AwayFromZero,
140
141 /// Round half toward even.
142 ///
143 /// “Even” has a special meaning here since we only care about round values.
144 /// If we are rounding to the nearest 10, then 0 is even, 10 is odd, 20 is
145 /// even, and so on.
146 ///
147 /// If we are rounding to whole numbers, then even and odd have the
148 /// conventional meaning.
149 ///
150 /// In general, a multiple of factor _n_ is even if `(n / factor) % 2 == 0`.
151 ///
152 /// ## Examples
153 ///
154 /// ```rust
155 /// use roundable::{Roundable, Tie};
156 ///
157 /// assert!(20 == 15.round_to(10, Tie::TowardEven));
158 /// assert!(0 == 5.round_to(10, Tie::TowardEven));
159 /// assert!(0 == (-5).round_to(10, Tie::TowardEven));
160 /// assert!(-20 == (-15).round_to(10, Tie::TowardEven));
161 ///
162 /// // Other values are unaffected:
163 /// assert!(0 == 4.round_to(10, Tie::TowardEven));
164 /// assert!(10 == 6.round_to(10, Tie::TowardEven));
165 /// assert!(0 == (-4).round_to(10, Tie::TowardEven));
166 /// assert!(-10 == (-6).round_to(10, Tie::TowardEven));
167 /// ```
168 TowardEven,
169
170 /// Round half toward odd.
171 ///
172 /// “Odd” has a special meaning here since we only care about round values.
173 /// If we are rounding to the nearest 10, then 0 is even, 10 is odd, 20 is
174 /// even, and so on.
175 ///
176 /// If we are rounding to whole numbers, then even and odd have the
177 /// conventional meaning.
178 ///
179 /// In general, a multiple of factor _n_ is odd if `(n / factor) % 2 != 0`.
180 ///
181 /// ## Examples
182 ///
183 /// ```rust
184 /// use roundable::{Roundable, Tie};
185 ///
186 /// assert!(10 == 15.round_to(10, Tie::TowardOdd));
187 /// assert!(10 == 5.round_to(10, Tie::TowardOdd));
188 /// assert!(-10 == (-5).round_to(10, Tie::TowardOdd));
189 /// assert!(-10 == (-15).round_to(10, Tie::TowardOdd));
190 ///
191 /// // Other values are unaffected:
192 /// assert!(0 == 4.round_to(10, Tie::TowardOdd));
193 /// assert!(10 == 6.round_to(10, Tie::TowardOdd));
194 /// assert!(0 == (-4).round_to(10, Tie::TowardOdd));
195 /// assert!(-10 == (-6).round_to(10, Tie::TowardOdd));
196 /// ```
197 TowardOdd,
198}
199
200/// Methods to round to an arbitrary factor.
201///
202/// For example, you might wish to round an integer to the nearest ten or
203/// nearest hundred:
204///
205/// ```rust
206/// use roundable::{Roundable, Tie};
207///
208/// assert!(310 == 314.round_to(10, Tie::Up));
209/// assert!(Some(300) == 314.try_round_to(100, Tie::Up));
210/// ```
211pub trait Roundable: Sized {
212 /// Round to the nearest `factor`. Panics if there is an overflow.
213 ///
214 /// Ties (values exactly halfway between to round numbers) are handled
215 /// according to the second parameter. For traditional rounding use
216 /// [`Tie::Up`], which will cause ties to be resolved by choosing the higher
217 /// round number.
218 ///
219 /// See [`Tie`] for other tie strategies.
220 ///
221 /// ```rust
222 /// use roundable::{Roundable, Tie};
223 ///
224 /// assert!(315 == 314.round_to(5, Tie::Up));
225 /// assert!(-10 == (-15).round_to(10, Tie::Up));
226 /// ```
227 ///
228 /// `255u8` can’t be rounded to the nearest 10 (which would be 260) because
229 /// 260 won’t fit in a `u8`:
230 ///
231 /// ```rust,should_panic
232 /// # use roundable::{Roundable, Tie};
233 /// let _ = 255u8.round_to(10u8, Tie::Up);
234 /// ```
235 ///
236 /// # Panics
237 ///
238 /// Panics if `factor` is not positive, e.g. if it’s 0, or if rounding would
239 /// return a value that does not fit in the return type.
240 #[must_use]
241 fn round_to(self, factor: Self, tie: Tie) -> Self {
242 self.try_round_to(factor, tie)
243 .expect("overflow while rounding")
244 }
245
246 /// Round to the nearest `factor`. Returns `None` if there is an overflow.
247 ///
248 /// Ties (values exactly halfway between to round numbers) are handled
249 /// according to the second parameter. For traditional rounding use
250 /// [`Tie::Up`], which will cause ties to be resolved by choosing the higher
251 /// round number.
252 ///
253 /// See [`Tie`] for other tie strategies.
254 ///
255 /// ```rust
256 /// use roundable::{Roundable, Tie};
257 ///
258 /// assert!(Some(315) == 314.try_round_to(5, Tie::Up));
259 /// assert!(Some(-10) == (-15).try_round_to(10, Tie::Up));
260 /// ```
261 ///
262 /// `255u8` can’t be rounded to the nearest 10 (which would be 260) because
263 /// 260 won’t fit in a `u8`:
264 ///
265 /// ```rust
266 /// # use roundable::{Roundable, Tie};
267 /// assert!(None == 255u8.try_round_to(10, Tie::Up));
268 /// ```
269 ///
270 /// # Panics
271 ///
272 /// Panics if `factor` is not positive, e.g. if it’s 0.
273 #[must_use]
274 fn try_round_to(self, factor: Self, tie: Tie) -> Option<Self>;
275}