pxfm/rounding/
round.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#[inline]
31pub const fn roundf(x: f32) -> f32 {
32    let mut i0 = x.to_bits() as i32;
33    let j0 = ((i0 >> 23) & 0xff) - 0x7f;
34    if j0 < 23 {
35        if j0 < 0 {
36            i0 &= 0x80000000u32 as i32;
37            if j0 == -1 {
38                i0 |= 0x3f800000;
39            }
40        } else {
41            let i = 0x007fffff >> j0;
42            if (i0 & i) == 0 {
43                /* X is integral.  */
44                return x;
45            }
46
47            i0 += 0x00400000 >> j0;
48            i0 &= !i;
49        }
50    } else {
51        return if j0 == 0x80 {
52            /* Inf or NaN.  */
53            x + x
54        } else {
55            x
56        };
57    }
58    f32::from_bits(i0 as u32)
59}
60
61// infinity, NaNs are assumed already handled somewhere
62#[inline]
63pub(crate) fn froundf_finite(x: f32) -> f32 {
64    #[cfg(any(
65        all(
66            any(target_arch = "x86", target_arch = "x86_64"),
67            target_feature = "sse4.1"
68        ),
69        target_arch = "aarch64"
70    ))]
71    {
72        x.round()
73    }
74    #[cfg(not(any(
75        all(
76            any(target_arch = "x86", target_arch = "x86_64"),
77            target_feature = "sse4.1"
78        ),
79        target_arch = "aarch64"
80    )))]
81    {
82        roundf(x)
83    }
84}
85
86#[inline]
87pub const fn round(x: f64) -> f64 {
88    let mut i0: i64 = x.to_bits() as i64;
89    let j0: i32 = (((i0 >> 52) & 0x7ff) - 0x3ff) as i32;
90    if j0 < 52 {
91        if j0 < 0 {
92            i0 &= 0x8000000000000000u64 as i64;
93            if j0 == -1 {
94                i0 |= 0x3ff0000000000000u64 as i64;
95            }
96        } else {
97            let i = (0x000fffffffffffffu64 >> j0) as i64;
98            if (i0 & i) == 0 {
99                /* X is integral.  */
100                return x;
101            }
102
103            i0 += (0x0008000000000000u64 >> j0) as i64;
104            i0 &= !i;
105        }
106    } else {
107        return if j0 == 0x400 {
108            /* Inf or NaN.  */
109            x + x
110        } else {
111            x
112        };
113    }
114    f64::from_bits(i0 as u64)
115}
116
117// infinity, NaNs are assumed already handled somewhere
118#[inline]
119pub(crate) fn fround_finite(x: f64) -> f64 {
120    #[cfg(any(
121        all(
122            any(target_arch = "x86", target_arch = "x86_64"),
123            target_feature = "sse4.1"
124        ),
125        target_arch = "aarch64"
126    ))]
127    {
128        x.round()
129    }
130    #[cfg(not(any(
131        all(
132            any(target_arch = "x86", target_arch = "x86_64"),
133            target_feature = "sse4.1"
134        ),
135        target_arch = "aarch64"
136    )))]
137    {
138        round(x)
139    }
140}
141
142pub(crate) trait CpuRound {
143    fn cpu_round(self) -> Self;
144}
145
146impl CpuRound for f32 {
147    #[inline]
148    fn cpu_round(self) -> Self {
149        froundf_finite(self)
150    }
151}
152
153impl CpuRound for f64 {
154    #[inline]
155    fn cpu_round(self) -> Self {
156        fround_finite(self)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_roundf() {
166        assert_eq!(roundf(0f32), 0.0f32.round());
167        assert_eq!(roundf(1f32), 1.0f32.round());
168        assert_eq!(roundf(1.2f32), 1.2f32.round());
169        assert_eq!(roundf(-1.2f32), (-1.2f32).round());
170        assert_eq!(roundf(-1.6f32), (-1.6f32).round());
171        assert_eq!(roundf(-1.5f32), (-1.5f32).round());
172        assert_eq!(roundf(1.6f32), 1.6f32.round());
173        assert_eq!(roundf(1.5f32), 1.5f32.round());
174        assert_eq!(roundf(2.5f32), 2.5f32.round());
175    }
176
177    #[test]
178    fn test_round() {
179        assert_eq!(round(0.), 0.0f64.round());
180        assert_eq!(round(1.), 1.0f64.round());
181        assert_eq!(round(1.2), 1.2f64.round());
182        assert_eq!(round(-1.2), (-1.2f64).round());
183        assert_eq!(round(-1.6), (-1.6f64).round());
184        assert_eq!(round(-1.5), (-1.5f64).round());
185        assert_eq!(round(1.6), 1.6f64.round());
186        assert_eq!(round(1.5), 1.5f64.round());
187        assert_eq!(round(2.5), 2.5f64.round());
188    }
189}