about summary refs log tree commit diff
path: root/library/compiler-builtins/libm/src/math/generic/rint.rs
blob: c5bc27d3de6bc62cd861272550765161fe4a3a55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* SPDX-License-Identifier: MIT */
/* origin: musl src/math/rint.c */

use crate::support::{Float, FpResult, Round};

/// IEEE 754-2019 `roundToIntegralExact`, which respects rounding mode and raises inexact if
/// applicable.
#[inline]
pub fn rint_round<F: Float>(x: F, _round: Round) -> FpResult<F> {
    let toint = F::ONE / F::EPSILON;
    let e = x.ex();
    let positive = x.is_sign_positive();

    // On i386 `force_eval!` must be used to force rounding via storage to memory. Otherwise,
    // the excess precission from x87 would cause an incorrect final result.
    let force = |x| {
        if cfg!(x86_no_sse) && (F::BITS == 32 || F::BITS == 64) {
            force_eval!(x)
        } else {
            x
        }
    };

    let res = if e >= F::EXP_BIAS + F::SIG_BITS {
        // No fractional part; exact result can be returned.
        x
    } else {
        // Apply a net-zero adjustment that nudges `y` in the direction of the rounding mode. For
        // Rust this is always nearest, but ideally it would take `round` into account.
        let y = if positive {
            force(force(x) + toint) - toint
        } else {
            force(force(x) - toint) + toint
        };

        if y == F::ZERO {
            // A zero result takes the sign of the input.
            if positive { F::ZERO } else { F::NEG_ZERO }
        } else {
            y
        }
    };

    FpResult::ok(res)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::support::{Hexf, Status};

    fn spec_test<F: Float>(cases: &[(F, F, Status)]) {
        let roundtrip = [
            F::ZERO,
            F::ONE,
            F::NEG_ONE,
            F::NEG_ZERO,
            F::INFINITY,
            F::NEG_INFINITY,
        ];

        for x in roundtrip {
            let FpResult { val, status } = rint_round(x, Round::Nearest);
            assert_biteq!(val, x, "rint_round({})", Hexf(x));
            assert_eq!(status, Status::OK, "{}", Hexf(x));
        }

        for &(x, res, res_stat) in cases {
            let FpResult { val, status } = rint_round(x, Round::Nearest);
            assert_biteq!(val, res, "rint_round({})", Hexf(x));
            assert_eq!(status, res_stat, "{}", Hexf(x));
        }
    }

    #[test]
    #[cfg(f16_enabled)]
    fn spec_tests_f16() {
        let cases = [];
        spec_test::<f16>(&cases);
    }

    #[test]
    fn spec_tests_f32() {
        let cases = [
            (0.1, 0.0, Status::OK),
            (-0.1, -0.0, Status::OK),
            (0.5, 0.0, Status::OK),
            (-0.5, -0.0, Status::OK),
            (0.9, 1.0, Status::OK),
            (-0.9, -1.0, Status::OK),
            (1.1, 1.0, Status::OK),
            (-1.1, -1.0, Status::OK),
            (1.5, 2.0, Status::OK),
            (-1.5, -2.0, Status::OK),
            (1.9, 2.0, Status::OK),
            (-1.9, -2.0, Status::OK),
            (2.8, 3.0, Status::OK),
            (-2.8, -3.0, Status::OK),
        ];
        spec_test::<f32>(&cases);
    }

    #[test]
    fn spec_tests_f64() {
        let cases = [
            (0.1, 0.0, Status::OK),
            (-0.1, -0.0, Status::OK),
            (0.5, 0.0, Status::OK),
            (-0.5, -0.0, Status::OK),
            (0.9, 1.0, Status::OK),
            (-0.9, -1.0, Status::OK),
            (1.1, 1.0, Status::OK),
            (-1.1, -1.0, Status::OK),
            (1.5, 2.0, Status::OK),
            (-1.5, -2.0, Status::OK),
            (1.9, 2.0, Status::OK),
            (-1.9, -2.0, Status::OK),
            (2.8, 3.0, Status::OK),
            (-2.8, -3.0, Status::OK),
        ];
        spec_test::<f64>(&cases);
    }

    #[test]
    #[cfg(f128_enabled)]
    fn spec_tests_f128() {
        let cases = [];
        spec_test::<f128>(&cases);
    }
}