Skip to main content

c/
math_lrint.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// Port of musl lrint/llrint/lrintf/llrintf/lrintl/llrintl
5//
6// On x86_64: c_long = i64 (LONG_MAX >= 2^53), c_longlong = i64, long double = f64.
7// All six functions reduce to: round-to-integer-using-current-FP-mode, then cast.
8//
9// The rint/rintf helpers use musl's `x + toint - toint` trick which rounds via
10// the current FP rounding mode (MXCSR on x86_64), unlike Rust's `as` which
11// truncates toward zero.
12
13// musl rint: rounds f64 to integer using current FP rounding mode
14#[no_mangle]
15pub fn rint(x: f64) -> f64 {
16    let u = asuint64(x);
17    let e = ((u >> 52) & 0x7ff) as i32;
18    let s = u >> 63;
19
20    // Already an integer (exponent >= 52 means no fractional bits)
21    if e >= 0x3ff + 52 {
22        return x;
23    }
24
25    // toint = 1/DBL_EPSILON = 2^52 = 4503599627370496.0
26    let toint = 4503599627370496.0f64;
27    let y = if s != 0 {
28        x - toint + toint
29    } else {
30        x + toint - toint
31    };
32
33    if y == 0.0 {
34        if s != 0 { -0.0 } else { 0.0 }
35    } else {
36        y
37    }
38}
39
40// musl rintf: rounds f32 to integer using current FP rounding mode
41#[no_mangle]
42pub fn rintf(x: f32) -> f32 {
43    let u = asuint(x);
44    let e = ((u >> 23) & 0xff) as i32;
45    let s = u >> 31;
46
47    // Already an integer (exponent >= 23 means no fractional bits)
48    if e >= 0x7f + 23 {
49        return x;
50    }
51
52    // toint = 1/FLT_EPSILON = 2^23 = 8388608.0
53    let toint = 8388608.0f32;
54    let y = if s != 0 {
55        x - toint + toint
56    } else {
57        x + toint - toint
58    };
59
60    if y == 0.0 {
61        if s != 0 { -0.0f32 } else { 0.0f32 }
62    } else {
63        y
64    }
65}
66
67// On x86_64 with c_long = i64 (LONG_MAX >= 2^53), the musl slow path
68// (lrint_slow with FE_INEXACT handling) is not needed. Simple cast suffices
69// because: (1) rint returns an exact integer as f64, (2) the largest integer
70// representable as f64 is 2^53 which fits in i64, (3) for |x| >= 2^52 rint
71// returns x unchanged and the saturating `as` cast handles overflow.
72
73#[no_mangle]
74pub extern "C" fn lrint(x: f64) -> c_long {
75    rint(x) as c_long
76}
77
78#[no_mangle]
79pub extern "C" fn llrint(x: f64) -> c_longlong {
80    rint(x) as c_longlong
81}
82
83#[no_mangle]
84pub extern "C" fn lrintf(x: f32) -> c_long {
85    rintf(x) as c_long
86}
87
88#[no_mangle]
89pub extern "C" fn llrintf(x: f32) -> c_longlong {
90    rintf(x) as c_longlong
91}
92
93// On x86_64 with -mlong-double-64: long double = f64, delegate to lrint/llrint.
94// On aarch64: long double = f128 (IEEE quad), cast to f64 then round.
95#[cfg(target_arch = "x86_64")]
96#[no_mangle]
97pub extern "C" fn lrintl(x: f64) -> c_long {
98    lrint(x)
99}
100
101#[cfg(target_arch = "x86_64")]
102#[no_mangle]
103pub extern "C" fn llrintl(x: f64) -> c_longlong {
104    llrint(x)
105}
106
107#[cfg(target_arch = "aarch64")]
108#[no_mangle]
109pub extern "C" fn lrintl(x: f128) -> c_long {
110    lrint(x as f64)
111}
112
113#[cfg(target_arch = "aarch64")]
114#[no_mangle]
115pub extern "C" fn llrintl(x: f128) -> c_longlong {
116    llrint(x as f64)
117}
118
119#[cfg(target_arch = "riscv64")]
120#[no_mangle]
121pub extern "C" fn lrintl(x: f128) -> c_long {
122    lrint(x as f64)
123}
124
125#[cfg(target_arch = "riscv64")]
126#[no_mangle]
127pub extern "C" fn llrintl(x: f128) -> c_longlong {
128    llrint(x as f64)
129}