about summary refs log tree commit diff
path: root/library/compiler-builtins/libm/src/math/generic/trunc.rs
blob: d5b444d15dfc01fae060a77aa7d19d7d3cd2d94d (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/* SPDX-License-Identifier: MIT
 * origin: musl src/math/trunc.c */

use crate::support::{Float, FpResult, Int, IntTy, MinInt, Status};

#[inline]
pub fn trunc<F: Float>(x: F) -> F {
    trunc_status(x).val
}

#[inline]
pub fn trunc_status<F: Float>(x: F) -> FpResult<F> {
    let mut xi: F::Int = x.to_bits();
    let e: i32 = x.exp_unbiased();

    // C1: The represented value has no fractional part, so no truncation is needed
    if e >= F::SIG_BITS as i32 {
        return FpResult::ok(x);
    }

    let mask = if e < 0 {
        // C2: If the exponent is negative, the result will be zero so we mask out everything
        // except the sign.
        F::SIGN_MASK
    } else {
        // C3: Otherwise, we mask out the last `e` bits of the significand.
        !(F::SIG_MASK >> e.unsigned())
    };

    // C4: If the to-be-masked-out portion is already zero, we have an exact result
    if (xi & !mask) == IntTy::<F>::ZERO {
        return FpResult::ok(x);
    }

    // C5: Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the
    // result, and return.

    let status = if xi & F::SIG_MASK == F::Int::ZERO {
        Status::OK
    } else {
        Status::INEXACT
    };
    xi &= mask;
    FpResult::new(F::from_bits(xi), status)
}

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

    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 } = trunc_status(x);
            assert_biteq!(val, x, "{}", Hexf(x));
            assert_eq!(status, Status::OK, "{}", Hexf(x));
        }

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

    /* Skipping f16 / f128 "sanity_check"s and spec cases due to rejected literal lexing at MSRV */

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

    #[test]
    fn sanity_check_f32() {
        assert_eq!(trunc(0.5f32), 0.0);
        assert_eq!(trunc(1.1f32), 1.0);
        assert_eq!(trunc(2.9f32), 2.0);
    }

    #[test]
    fn spec_tests_f32() {
        let cases = [
            (0.1, 0.0, Status::INEXACT),
            (-0.1, -0.0, Status::INEXACT),
            (0.9, 0.0, Status::INEXACT),
            (-0.9, -0.0, Status::INEXACT),
            (1.1, 1.0, Status::INEXACT),
            (-1.1, -1.0, Status::INEXACT),
            (1.9, 1.0, Status::INEXACT),
            (-1.9, -1.0, Status::INEXACT),
        ];
        spec_test::<f32>(&cases);

        assert_biteq!(trunc(1.1f32), 1.0);
        assert_biteq!(trunc(1.1f64), 1.0);

        // C1
        assert_biteq!(trunc(hf32!("0x1p23")), hf32!("0x1p23"));
        assert_biteq!(trunc(hf64!("0x1p52")), hf64!("0x1p52"));
        assert_biteq!(trunc(hf32!("-0x1p23")), hf32!("-0x1p23"));
        assert_biteq!(trunc(hf64!("-0x1p52")), hf64!("-0x1p52"));

        // C2
        assert_biteq!(trunc(hf32!("0x1p-1")), 0.0);
        assert_biteq!(trunc(hf64!("0x1p-1")), 0.0);
        assert_biteq!(trunc(hf32!("-0x1p-1")), -0.0);
        assert_biteq!(trunc(hf64!("-0x1p-1")), -0.0);
    }

    #[test]
    fn sanity_check_f64() {
        assert_eq!(trunc(1.1f64), 1.0);
        assert_eq!(trunc(2.9f64), 2.0);
    }

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

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