compiler_builtins/math/libm_math/generic/
floor.rs

1/* SPDX-License-Identifier: MIT
2 * origin: musl src/math/floor.c */
3
4//! Generic `floor` algorithm.
5//!
6//! Note that this uses the algorithm from musl's `floorf` rather than `floor` or `floorl` because
7//! performance seems to be better (based on icount) and it does not seem to experience rounding
8//! errors on i386.
9
10use crate::support::{Float, FpResult, Int, IntTy, MinInt, Status};
11
12#[inline]
13pub fn floor<F: Float>(x: F) -> F {
14    floor_status(x).val
15}
16
17#[inline]
18pub fn floor_status<F: Float>(x: F) -> FpResult<F> {
19    let zero = IntTy::<F>::ZERO;
20
21    let mut ix = x.to_bits();
22    let e = x.exp_unbiased();
23
24    // If the represented value has no fractional part, no truncation is needed.
25    if e >= F::SIG_BITS as i32 {
26        return FpResult::ok(x);
27    }
28
29    let status;
30    let res = if e >= 0 {
31        // |x| >= 1.0
32        let m = F::SIG_MASK >> e.unsigned();
33        if ix & m == zero {
34            // Portion to be masked is already zero; no adjustment needed.
35            return FpResult::ok(x);
36        }
37
38        // Otherwise, raise an inexact exception.
39        status = Status::INEXACT;
40
41        if x.is_sign_negative() {
42            ix += m;
43        }
44
45        ix &= !m;
46        F::from_bits(ix)
47    } else {
48        // |x| < 1.0, raise an inexact exception since truncation will happen.
49        if ix & F::SIG_MASK == F::Int::ZERO {
50            status = Status::OK;
51        } else {
52            status = Status::INEXACT;
53        }
54
55        if x.is_sign_positive() {
56            // 0.0 <= x < 1.0; rounding down goes toward +0.0.
57            F::ZERO
58        } else if ix << 1 != zero {
59            // -1.0 < x < 0.0; rounding down goes toward -1.0.
60            F::NEG_ONE
61        } else {
62            // -0.0 remains unchanged
63            x
64        }
65    };
66
67    FpResult::new(res, status)
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::support::Hexf;
74
75    /// Test against https://en.cppreference.com/w/cpp/numeric/math/floor
76    fn spec_test<F: Float>(cases: &[(F, F, Status)]) {
77        let roundtrip = [
78            F::ZERO,
79            F::ONE,
80            F::NEG_ONE,
81            F::NEG_ZERO,
82            F::INFINITY,
83            F::NEG_INFINITY,
84        ];
85
86        for x in roundtrip {
87            let FpResult { val, status } = floor_status(x);
88            assert_biteq!(val, x, "{}", Hexf(x));
89            assert_eq!(status, Status::OK, "{}", Hexf(x));
90        }
91
92        for &(x, res, res_stat) in cases {
93            let FpResult { val, status } = floor_status(x);
94            assert_biteq!(val, res, "{}", Hexf(x));
95            assert_eq!(status, res_stat, "{}", Hexf(x));
96        }
97    }
98
99    /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */
100
101    #[test]
102    #[cfg(f16_enabled)]
103    fn spec_tests_f16() {
104        let cases = [];
105        spec_test::<f16>(&cases);
106    }
107
108    #[test]
109    fn sanity_check_f32() {
110        assert_eq!(floor(0.5f32), 0.0);
111        assert_eq!(floor(1.1f32), 1.0);
112        assert_eq!(floor(2.9f32), 2.0);
113    }
114
115    #[test]
116    fn spec_tests_f32() {
117        let cases = [
118            (0.1, 0.0, Status::INEXACT),
119            (-0.1, -1.0, Status::INEXACT),
120            (0.9, 0.0, Status::INEXACT),
121            (-0.9, -1.0, Status::INEXACT),
122            (1.1, 1.0, Status::INEXACT),
123            (-1.1, -2.0, Status::INEXACT),
124            (1.9, 1.0, Status::INEXACT),
125            (-1.9, -2.0, Status::INEXACT),
126        ];
127        spec_test::<f32>(&cases);
128    }
129
130    #[test]
131    fn sanity_check_f64() {
132        assert_eq!(floor(1.1f64), 1.0);
133        assert_eq!(floor(2.9f64), 2.0);
134    }
135
136    #[test]
137    fn spec_tests_f64() {
138        let cases = [
139            (0.1, 0.0, Status::INEXACT),
140            (-0.1, -1.0, Status::INEXACT),
141            (0.9, 0.0, Status::INEXACT),
142            (-0.9, -1.0, Status::INEXACT),
143            (1.1, 1.0, Status::INEXACT),
144            (-1.1, -2.0, Status::INEXACT),
145            (1.9, 1.0, Status::INEXACT),
146            (-1.9, -2.0, Status::INEXACT),
147        ];
148        spec_test::<f64>(&cases);
149    }
150
151    #[test]
152    #[cfg(f128_enabled)]
153    fn spec_tests_f128() {
154        let cases = [];
155        spec_test::<f128>(&cases);
156    }
157}