Add a generic version of ceil

Additionally, make use of this version to implement `ceil` and `ceilf`.

Musl's `ceilf` algorithm seems to work better for all versions of the
functions. Testing with a generic version of musl's `ceil` routine
showed the following regressions:

    icount::icount_bench_ceil_group::icount_bench_ceil logspace:setup_ceil()
    Performance has regressed: Instructions (14064 > 13171) regressed by +6.78005% (>+5.00000)
      Baselines:                      softfloat|softfloat
      Instructions:                       14064|13171                (+6.78005%) [+1.06780x]
      L1 Hits:                            16697|15803                (+5.65715%) [+1.05657x]
      L2 Hits:                                0|0                    (No change)
      RAM Hits:                               7|8                    (-12.5000%) [-1.14286x]
      Total read+write:                   16704|15811                (+5.64797%) [+1.05648x]
      Estimated Cycles:                   16942|16083                (+5.34104%) [+1.05341x]
    icount::icount_bench_ceilf_group::icount_bench_ceilf logspace:setup_ceilf()
    Performance has regressed: Instructions (14732 > 9901) regressed by +48.7931% (>+5.00000)
      Baselines:                      softfloat|softfloat
      Instructions:                       14732|9901                 (+48.7931%) [+1.48793x]
      L1 Hits:                            17494|12611                (+38.7202%) [+1.38720x]
      L2 Hits:                                0|0                    (No change)
      RAM Hits:                               6|6                    (No change)
      Total read+write:                   17500|12617                (+38.7018%) [+1.38702x]
      Estimated Cycles:                   17704|12821                (+38.0860%) [+1.38086x]
This commit is contained in:
Trevor Gross 2025-01-13 11:17:03 +00:00
parent a7cd13b6a3
commit c00f119166
4 changed files with 91 additions and 91 deletions

View file

@ -1,8 +1,3 @@
#![allow(unreachable_code)]
use core::f64;
const TOINT: f64 = 1. / f64::EPSILON;
/// Ceil (f64)
///
/// Finds the nearest integer greater than or equal to `x`.
@ -15,40 +10,5 @@ pub fn ceil(x: f64) -> f64 {
args: x,
}
let u: u64 = x.to_bits();
let e: i64 = ((u >> 52) & 0x7ff) as i64;
let y: f64;
if e >= 0x3ff + 52 || x == 0. {
return x;
}
// y = int(x) - x, where int(x) is an integer neighbor of x
y = if (u >> 63) != 0 { x - TOINT + TOINT - x } else { x + TOINT - TOINT - x };
// special case because of non-nearest rounding modes
if e < 0x3ff {
force_eval!(y);
return if (u >> 63) != 0 { -0. } else { 1. };
}
if y < 0. { x + y + 1. } else { x + y }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_eq!(ceil(1.1), 2.0);
assert_eq!(ceil(2.9), 3.0);
}
/// The spec: https://en.cppreference.com/w/cpp/numeric/math/ceil
#[test]
fn spec_tests() {
// Not Asserted: that the current rounding mode has no effect.
assert!(ceil(f64::NAN).is_nan());
for f in [0.0, -0.0, f64::INFINITY, f64::NEG_INFINITY].iter().copied() {
assert_eq!(ceil(f), f);
}
}
super::generic::ceil(x)
}

View file

@ -1,5 +1,3 @@
use core::f32;
/// Ceil (f32)
///
/// Finds the nearest integer greater than or equal to `x`.
@ -11,52 +9,5 @@ pub fn ceilf(x: f32) -> f32 {
args: x,
}
let mut ui = x.to_bits();
let e = (((ui >> 23) & 0xff).wrapping_sub(0x7f)) as i32;
if e >= 23 {
return x;
}
if e >= 0 {
let m = 0x007fffff >> e;
if (ui & m) == 0 {
return x;
}
force_eval!(x + f32::from_bits(0x7b800000));
if ui >> 31 == 0 {
ui += m;
}
ui &= !m;
} else {
force_eval!(x + f32::from_bits(0x7b800000));
if ui >> 31 != 0 {
return -0.0;
} else if ui << 1 != 0 {
return 1.0;
}
}
f32::from_bits(ui)
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_eq!(ceilf(1.1), 2.0);
assert_eq!(ceilf(2.9), 3.0);
}
/// The spec: https://en.cppreference.com/w/cpp/numeric/math/ceil
#[test]
fn spec_tests() {
// Not Asserted: that the current rounding mode has no effect.
assert!(ceilf(f32::NAN).is_nan());
for f in [0.0, -0.0, f32::INFINITY, f32::NEG_INFINITY].iter().copied() {
assert_eq!(ceilf(f), f);
}
}
super::generic::ceil(x)
}

View file

@ -0,0 +1,87 @@
/* SPDX-License-Identifier: MIT */
/* origin: musl src/math/ceilf.c */
//! Generic `ceil` algorithm.
//!
//! Note that this uses the algorithm from musl's `ceilf` rather than `ceil` or `ceill` because
//! performance seems to be better (based on icount) and it does not seem to experience rounding
//! errors on i386.
use super::super::{Float, Int, IntTy, MinInt};
pub fn ceil<F: Float>(x: F) -> F {
let zero = IntTy::<F>::ZERO;
let mut ix = x.to_bits();
let e = x.exp_unbiased();
// If the represented value has no fractional part, no truncation is needed.
if e >= F::SIG_BITS as i32 {
return x;
}
if e >= 0 {
// |x| >= 1.0
let m = F::SIG_MASK >> e.unsigned();
if (ix & m) == zero {
// Portion to be masked is already zero; no adjustment needed.
return x;
}
// Otherwise, raise an inexact exception.
force_eval!(x + F::MAX);
if x.is_sign_positive() {
ix += m;
}
ix &= !m;
} else {
// |x| < 1.0, raise an inexact exception since truncation will happen (unless x == 0).
force_eval!(x + F::MAX);
if x.is_sign_negative() {
// -1.0 < x <= -0.0; rounding up goes toward -0.0.
return F::NEG_ZERO;
} else if ix << 1 != zero {
// 0.0 < x < 1.0; rounding up goes toward +1.0.
return F::ONE;
}
}
F::from_bits(ix)
}
#[cfg(test)]
mod tests {
use super::*;
/// Test against https://en.cppreference.com/w/cpp/numeric/math/ceil
fn spec_test<F: Float>() {
// Not Asserted: that the current rounding mode has no effect.
for f in [F::ZERO, F::NEG_ZERO, F::INFINITY, F::NEG_INFINITY].iter().copied() {
assert_biteq!(ceil(f), f);
}
}
#[test]
fn sanity_check_f32() {
assert_eq!(ceil(1.1f32), 2.0);
assert_eq!(ceil(2.9f32), 3.0);
}
#[test]
fn spec_tests_f32() {
spec_test::<f32>();
}
#[test]
fn sanity_check_f64() {
assert_eq!(ceil(1.1f64), 2.0);
assert_eq!(ceil(2.9f64), 3.0);
}
#[test]
fn spec_tests_f64() {
spec_test::<f64>();
}
}

View file

@ -1,9 +1,11 @@
mod ceil;
mod copysign;
mod fabs;
mod fdim;
mod sqrt;
mod trunc;
pub use ceil::ceil;
pub use copysign::copysign;
pub use fabs::fabs;
pub use fdim::fdim;