libm/math/generic/
ceil.rs

1/* SPDX-License-Identifier: MIT */
2/* origin: musl src/math/ceilf.c */
3
4//! Generic `ceil` algorithm.
5//!
6//! Note that this uses the algorithm from musl's `ceilf` rather than `ceil` or `ceill` 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 ceil<F: Float>(x: F) -> F {
14    ceil_status(x).val
15}
16
17#[inline]
18pub fn ceil_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_positive() {
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 (unless x == 0).
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_negative() {
56            // -1.0 < x <= -0.0; rounding up goes toward -0.0.
57            F::NEG_ZERO
58        } else if ix << 1 != zero {
59            // 0.0 < x < 1.0; rounding up goes toward +1.0.
60            F::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/ceil
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 } = ceil_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 } = ceil_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 due to rejected literal lexing at MSRV */
100
101    #[test]
102    #[cfg(f16_enabled)]
103    fn spec_tests_f16() {
104        let cases = [
105            (0.1, 1.0, Status::INEXACT),
106            (-0.1, -0.0, Status::INEXACT),
107            (0.9, 1.0, Status::INEXACT),
108            (-0.9, -0.0, Status::INEXACT),
109            (1.1, 2.0, Status::INEXACT),
110            (-1.1, -1.0, Status::INEXACT),
111            (1.9, 2.0, Status::INEXACT),
112            (-1.9, -1.0, Status::INEXACT),
113        ];
114        spec_test::<f16>(&cases);
115    }
116
117    #[test]
118    fn sanity_check_f32() {
119        assert_eq!(ceil(1.1f32), 2.0);
120        assert_eq!(ceil(2.9f32), 3.0);
121    }
122
123    #[test]
124    fn spec_tests_f32() {
125        let cases = [
126            (0.1, 1.0, Status::INEXACT),
127            (-0.1, -0.0, Status::INEXACT),
128            (0.9, 1.0, Status::INEXACT),
129            (-0.9, -0.0, Status::INEXACT),
130            (1.1, 2.0, Status::INEXACT),
131            (-1.1, -1.0, Status::INEXACT),
132            (1.9, 2.0, Status::INEXACT),
133            (-1.9, -1.0, Status::INEXACT),
134        ];
135        spec_test::<f32>(&cases);
136    }
137
138    #[test]
139    fn sanity_check_f64() {
140        assert_eq!(ceil(1.1f64), 2.0);
141        assert_eq!(ceil(2.9f64), 3.0);
142    }
143
144    #[test]
145    fn spec_tests_f64() {
146        let cases = [
147            (0.1, 1.0, Status::INEXACT),
148            (-0.1, -0.0, Status::INEXACT),
149            (0.9, 1.0, Status::INEXACT),
150            (-0.9, -0.0, Status::INEXACT),
151            (1.1, 2.0, Status::INEXACT),
152            (-1.1, -1.0, Status::INEXACT),
153            (1.9, 2.0, Status::INEXACT),
154            (-1.9, -1.0, Status::INEXACT),
155        ];
156        spec_test::<f64>(&cases);
157    }
158
159    #[test]
160    #[cfg(f128_enabled)]
161    fn spec_tests_f128() {
162        let cases = [
163            (0.1, 1.0, Status::INEXACT),
164            (-0.1, -0.0, Status::INEXACT),
165            (0.9, 1.0, Status::INEXACT),
166            (-0.9, -0.0, Status::INEXACT),
167            (1.1, 2.0, Status::INEXACT),
168            (-1.1, -1.0, Status::INEXACT),
169            (1.9, 2.0, Status::INEXACT),
170            (-1.9, -1.0, Status::INEXACT),
171        ];
172        spec_test::<f128>(&cases);
173    }
174}