easy_cast/rounding.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Rounding modes
7
8use crate::{ConvExact, ConvTo, Error, RangeError};
9use core::convert::Infallible;
10
11/// Rounding mode
12///
13/// Implementations of this trait are (probably) unit structs, used to mark the
14/// type of rounding used at the type level.
15///
16/// # Implied implementations
17///
18/// <code>impl<S, T: [ConvExact]<S>> [ConvTo]<S, R></code> is
19/// is implemented for each rounding mode `R` provided by this crate since a
20/// more general impl over `R: Rounding` is not compatible with the wider trait
21/// design under the limitations of Rust's current trait solver. Any rounding
22/// mode added by a third-party crate should therefore provide a similar `impl`.
23pub trait Rounding: Copy + Default {
24 /// Maximum error type
25 type MaximumError: From<Infallible> + Into<Error> + core::error::Error;
26}
27
28/// Exact conversion only
29///
30/// Successful conversions using this "rounding" mode must preserve the value
31/// exactly.
32///
33/// Example: `2.0_f32` may convert to `2_i32`. `2.1_f32` is not convertible to
34/// [`i32`].
35///
36/// Another example: [`u128::MAX`] (which is larger than [`f32::MAX`]) may not
37/// be converted to [`f32`] with `Exact` rounding (with other modes it may
38/// round to [`f32::INFINITY`]).
39#[derive(Clone, Copy, Debug, Default)]
40pub struct Exact;
41impl Rounding for Exact {
42 type MaximumError = Error;
43}
44
45/// Approximate conversion
46///
47/// This rounding mode allows an implementation-defined rounding mode.
48/// The result must be close to the input value (see below).
49///
50/// Example: `2.1_f32` may convert to `2_i32` or to `3_i32` (either
51/// implementation is valid so long as the behaviour is well-defined).
52///
53/// # Limits of approximation
54///
55/// (This section applies to all [`Rounding`] modes provided by `easy-cast`
56/// except for [`Exact`].)
57///
58/// The output value of a successful conversion must be close to the input
59/// value. More precisely, the distance between the input and output values
60/// should be less than the distance between the two closest representable
61/// values in the target type.
62/// For example, `1.9_f32` may be approximated to `1_i32` or `2_i32` since
63/// mathematically `1.9` lies between `1` and `2`. As another example,
64/// `1_f64 + (f32::EPSILON as f64) / 2.0` may be approximated to
65/// `1_f32` or `1_f32 + f32::EPSILON`.
66///
67/// Infinity "values" like [`f32::INFINITY`] are a bit special; essentially we
68/// allow any input of the appropriate sign to approximate to "infinity" where
69/// the input may not approximate to another value. For example, the above rules
70/// may be used to calculate the maximum `u128` value which is allowed to
71/// approximate to `f32::MAX` (`0xFFFFFF7F_FFFFFFFF_FFFFFFFF_FFFFFFFF`);
72/// the value above this should thus approximate to `f32::INFINITY`.
73#[derive(Clone, Copy, Debug, Default)]
74pub struct Approx;
75impl Rounding for Approx {
76 type MaximumError = RangeError;
77}
78
79/// Truncation towards zero
80///
81/// Excess precision is truncated (rounds towards zero). This is the rounding
82/// mode used by [`as` numeric casts] for floating-point to integer conversions.
83///
84/// Example: `2.9_f32` converts to `2_i32`, `-2.9_f32` converts to `-2_i32`.
85///
86/// The [`§ Limits of approximation`](Approx#limits-of-approximation) as
87/// specified by [`Approx`] apply.
88///
89/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
90#[derive(Clone, Copy, Debug, Default)]
91pub struct Trunc;
92impl Rounding for Trunc {
93 type MaximumError = RangeError;
94}
95
96/// Round to the nearest representable value
97///
98/// The precise behaviour of half-way cases is implementation-defined. Provided
99/// implementations follow common practices: float-to-int conversions use the
100/// `round()` inherent function which rounds away from zero while int-to-float
101/// conversions follow the behaviour of [`as` numeric casts] which rounds ties
102/// to even.
103///
104/// Example: `2.5_f32` converts to `3_i32`, `-2.5_f32` converts to `-3_i32`.
105/// Another example: converting [`i32::MAX`] to [`f32`] rounds up to
106/// 2<sup>31</sup>.
107///
108/// The [`§ Limits of approximation`](Approx#limits-of-approximation) as
109/// specified by [`Approx`] apply.
110///
111/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
112#[cfg(any(feature = "std", feature = "libm"))]
113#[derive(Clone, Copy, Debug, Default)]
114pub struct Nearest;
115#[cfg(any(feature = "std", feature = "libm"))]
116impl Rounding for Nearest {
117 type MaximumError = RangeError;
118}
119
120/// Round towards negative infinity (floor)
121///
122/// Returns the largest integer less than or equal to the input.
123///
124/// Example: `2.9_f32` converts to `2_i32`, `-2.1_f32` converts to `-3_i32`.
125///
126/// The [`§ Limits of approximation`](Approx#limits-of-approximation) as
127/// specified by [`Approx`] apply.
128#[cfg(any(feature = "std", feature = "libm"))]
129#[derive(Clone, Copy, Debug, Default)]
130pub struct Floor;
131#[cfg(any(feature = "std", feature = "libm"))]
132impl Rounding for Floor {
133 type MaximumError = RangeError;
134}
135
136/// Round towards positive infinity (ceiling)
137///
138/// Returns the smallest integer greater than or equal to the input.
139///
140/// Example: `2.1_f32` converts to `3_i32`, `-2.9_f32` converts to `-2_i32`.
141///
142/// The [`§ Limits of approximation`](Approx#limits-of-approximation) as
143/// specified by [`Approx`] apply.
144#[cfg(any(feature = "std", feature = "libm"))]
145#[derive(Clone, Copy, Debug, Default)]
146pub struct Ceil;
147#[cfg(any(feature = "std", feature = "libm"))]
148impl Rounding for Ceil {
149 type MaximumError = RangeError;
150}
151
152impl<S, T: ConvExact<S>> ConvTo<S, Exact> for T {
153 type Error = T::Error;
154
155 #[inline]
156 fn try_conv_to(_: Exact, s: S) -> Result<Self, Self::Error> {
157 T::try_conv_exact(s)
158 }
159
160 #[inline]
161 fn conv_to(_: Exact, s: S) -> Self {
162 T::conv_exact(s)
163 }
164}
165
166impl<S, T: ConvExact<S>> ConvTo<S, Approx> for T {
167 type Error = T::Error;
168
169 #[inline]
170 fn try_conv_to(_: Approx, s: S) -> Result<Self, Self::Error> {
171 T::try_conv_exact(s)
172 }
173
174 #[inline]
175 fn conv_to(_: Approx, s: S) -> Self {
176 T::conv_exact(s)
177 }
178}
179
180#[cfg(any(feature = "std", feature = "libm"))]
181impl<S, T: ConvExact<S>> ConvTo<S, Trunc> for T {
182 type Error = T::Error;
183
184 #[inline]
185 fn try_conv_to(_: Trunc, s: S) -> Result<Self, Self::Error> {
186 T::try_conv_exact(s)
187 }
188
189 #[inline]
190 fn conv_to(_: Trunc, s: S) -> Self {
191 T::conv_exact(s)
192 }
193}
194
195#[cfg(any(feature = "std", feature = "libm"))]
196impl<S, T: ConvExact<S>> ConvTo<S, Nearest> for T {
197 type Error = T::Error;
198
199 #[inline]
200 fn try_conv_to(_: Nearest, s: S) -> Result<Self, Self::Error> {
201 T::try_conv_exact(s)
202 }
203
204 #[inline]
205 fn conv_to(_: Nearest, s: S) -> Self {
206 T::conv_exact(s)
207 }
208}
209
210#[cfg(any(feature = "std", feature = "libm"))]
211impl<S, T: ConvExact<S>> ConvTo<S, Floor> for T {
212 type Error = T::Error;
213
214 #[inline]
215 fn try_conv_to(_: Floor, s: S) -> Result<Self, Self::Error> {
216 T::try_conv_exact(s)
217 }
218
219 #[inline]
220 fn conv_to(_: Floor, s: S) -> Self {
221 T::conv_exact(s)
222 }
223}
224
225#[cfg(any(feature = "std", feature = "libm"))]
226impl<S, T: ConvExact<S>> ConvTo<S, Ceil> for T {
227 type Error = T::Error;
228
229 #[inline]
230 fn try_conv_to(_: Ceil, s: S) -> Result<Self, Self::Error> {
231 T::try_conv_exact(s)
232 }
233
234 #[inline]
235 fn conv_to(_: Ceil, s: S) -> Self {
236 T::conv_exact(s)
237 }
238}