Skip to main content

steel_math/
rounding.rs

1//! Java-compatible floating-point rounding
2
3use crate::float_consts::{EXP_BIAS, EXP_BIT_MASK, SIGNIF_BIT_MASK, SIGNIFICAND_WIDTH, SIZE};
4
5/// Rounds ties toward positive infinity, unlike [`f32::round`], which rounds
6/// them away from zero. Uses the JDK's IEEE 754 bit-shift implementation of
7/// [`Math.round(float)`], including saturation and NaN handling, without rounding
8/// up values just below a half-integer
9///
10/// [`Math.round(float)`]: https://github.com/openjdk/jdk25u/blob/master/src/java.base/share/classes/java/lang/Math.java#L752-L783
11#[must_use]
12pub const fn round_f32(value: f32) -> i32 {
13    let bits = value.to_bits() as i32;
14    let biased_exp = (bits & EXP_BIT_MASK) >> (SIGNIFICAND_WIDTH - 1);
15    let shift = (SIGNIFICAND_WIDTH - 2 + EXP_BIAS) - biased_exp;
16    // The JDK spells this `(shift & -32) == 0`: a finite value whose ulp lies in [2^-32, 1).
17    if shift >= 0 && shift < SIZE {
18        let mut r = (bits & SIGNIF_BIT_MASK) | (SIGNIF_BIT_MASK + 1);
19        if bits < 0 {
20            r = -r;
21        }
22        ((r >> shift) + 1) >> 1
23    } else {
24        value as i32
25    }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::round_f32;
31
32    #[test]
33    fn round_f32_matches_java_math_round_on_halves() {
34        assert_eq!(round_f32(2.5), 3);
35        assert_eq!(round_f32(-2.5), -2);
36        assert_eq!(round_f32(2.4), 2);
37        assert_eq!(round_f32(-2.6), -3);
38        assert_eq!(round_f32(0.499_999_97), 0);
39        assert_eq!(round_f32(f32::NAN), 0);
40        assert_eq!(round_f32(1.0e10), i32::MAX);
41    }
42}