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}