Skip to content

Commit

Permalink
Auto merge of #3256 - RalfJung:rounding, r=RalfJung
Browse files Browse the repository at this point in the history
implement the rounding intrinsics using apfloat rounding

No reason to use host floats for these. Also merge two files that were both testing various float things.
  • Loading branch information
bors committed Jan 6, 2024
2 parents 312f174 + 88dd5d9 commit 81e8ff9
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 180 deletions.
52 changes: 30 additions & 22 deletions src/shims/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"fabsf32" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
// Can be implemented in soft-floats.
// This is a "bitwise" operation, so there's no NaN non-determinism.
this.write_scalar(Scalar::from_f32(f.abs()), dest)?;
}
"fabsf64" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
// Can be implemented in soft-floats.
// This is a "bitwise" operation, so there's no NaN non-determinism.
this.write_scalar(Scalar::from_f64(f.abs()), dest)?;
}
"floorf32" | "ceilf32" | "truncf32" | "roundf32" | "rintf32" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
let mode = match intrinsic_name {
"floorf32" => Round::TowardNegative,
"ceilf32" => Round::TowardPositive,
"truncf32" => Round::TowardZero,
"roundf32" => Round::NearestTiesToAway,
"rintf32" => Round::NearestTiesToEven,
_ => bug!(),
};
let res = f.round_to_integral(mode).value;
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf32"
| "cosf32"
Expand All @@ -165,11 +178,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "logf32"
| "log10f32"
| "log2f32"
| "floorf32"
| "ceilf32"
| "truncf32"
| "roundf32"
| "rintf32"
=> {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f32()?;
Expand All @@ -184,18 +192,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"logf32" => f_host.ln(),
"log10f32" => f_host.log10(),
"log2f32" => f_host.log2(),
"floorf32" => f_host.floor(),
"ceilf32" => f_host.ceil(),
"truncf32" => f_host.trunc(),
"roundf32" => f_host.round(),
"rintf32" => f_host.round_ties_even(),
_ => bug!(),
};
let res = res.to_soft();
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}

"floorf64" | "ceilf64" | "truncf64" | "roundf64" | "rintf64" => {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
let mode = match intrinsic_name {
"floorf64" => Round::TowardNegative,
"ceilf64" => Round::TowardPositive,
"truncf64" => Round::TowardZero,
"roundf64" => Round::NearestTiesToAway,
"rintf64" => Round::NearestTiesToEven,
_ => bug!(),
};
let res = f.round_to_integral(mode).value;
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
}
#[rustfmt::skip]
| "sinf64"
| "cosf64"
Expand All @@ -205,11 +223,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
| "logf64"
| "log10f64"
| "log2f64"
| "floorf64"
| "ceilf64"
| "truncf64"
| "roundf64"
| "rintf64"
=> {
let [f] = check_arg_count(args)?;
let f = this.read_scalar(f)?.to_f64()?;
Expand All @@ -224,11 +237,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
"logf64" => f_host.ln(),
"log10f64" => f_host.log10(),
"log2f64" => f_host.log2(),
"floorf64" => f_host.floor(),
"ceilf64" => f_host.ceil(),
"truncf64" => f_host.trunc(),
"roundf64" => f_host.round(),
"rintf64" => f_host.round_ties_even(),
_ => bug!(),
};
let res = res.to_soft();
Expand Down
163 changes: 161 additions & 2 deletions tests/pass/float.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
#![feature(stmt_expr_attributes)]
#![feature(round_ties_even)]
#![feature(float_gamma)]
#![allow(arithmetic_overflow)]

use std::fmt::Debug;
use std::hint::black_box;
use std::{f32, f64};

macro_rules! assert_approx_eq {
($a:expr, $b:expr) => {{
let (a, b) = (&$a, &$b);
assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b);
}};
}

fn main() {
basic();
Expand All @@ -11,6 +21,8 @@ fn main() {
ops();
nan_casts();
rounding();
mul_add();
libm();
}

// Helper function to avoid promotion so that this tests "run-time" casts, not CTFE.
Expand Down Expand Up @@ -148,8 +160,6 @@ fn basic() {
assert_ne!({ 5.0_f32 / 0.0 }, { -5.0_f32 / 0.0 });
assert!((5.0_f64 / 0.0).is_infinite());
assert_ne!({ 5.0_f64 / 0.0 }, { 5.0_f64 / -0.0 });
assert!((-5.0_f32).sqrt().is_nan());
assert!((-5.0_f64).sqrt().is_nan());
assert_ne!(f32::NAN, f32::NAN);
assert_ne!(f64::NAN, f64::NAN);
// negative zero
Expand Down Expand Up @@ -178,6 +188,9 @@ fn basic() {
assert!((black_box(1.0f64) % -1.0).is_sign_positive());
assert!((black_box(-1.0f64) % 1.0).is_sign_negative());
assert!((black_box(-1.0f64) % -1.0).is_sign_negative());

assert_eq!((-1.0f32).abs(), 1.0f32);
assert_eq!(34.2f64.abs(), 34.2f64);
}

/// Many of these test values are taken from
Expand Down Expand Up @@ -592,4 +605,150 @@ fn rounding() {
assert_eq((-1.3f64).round_ties_even(), -1.0f64);
assert_eq((-1.5f64).round_ties_even(), -2.0f64);
assert_eq((-1.7f64).round_ties_even(), -2.0f64);

assert_eq!(3.8f32.floor(), 3.0f32);
assert_eq!((-1.1f64).floor(), -2.0f64);

assert_eq!((-2.3f32).ceil(), -2.0f32);
assert_eq!(3.8f64.ceil(), 4.0f64);

assert_eq!(0.1f32.trunc(), 0.0f32);
assert_eq!((-0.1f64).trunc(), 0.0f64);

assert_eq!(3.3_f32.round(), 3.0);
assert_eq!(2.5_f32.round(), 3.0);
assert_eq!(3.9_f64.round(), 4.0);
assert_eq!(2.5_f64.round(), 3.0);
}

fn mul_add() {
assert_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0);
assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E);
assert_eq!(3.0f64.mul_add(2.0, 5.0), 11.0);
assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E);
assert_eq!((-3.2f32).mul_add(2.4, f32::NEG_INFINITY), f32::NEG_INFINITY);
assert_eq!((-3.2f64).mul_add(2.4, f64::NEG_INFINITY), f64::NEG_INFINITY);

let f = f32::mul_add(
-0.000000000000000000000000000000000000014728589,
0.0000037105144,
0.000000000000000000000000000000000000000000055,
);
assert_eq!(f.to_bits(), f32::to_bits(-0.0));
}

pub fn libm() {
fn ldexp(a: f64, b: i32) -> f64 {
extern "C" {
fn ldexp(x: f64, n: i32) -> f64;
}
unsafe { ldexp(a, b) }
}

assert_approx_eq!(64f32.sqrt(), 8f32);
assert_approx_eq!(64f64.sqrt(), 8f64);
assert!((-5.0_f32).sqrt().is_nan());
assert!((-5.0_f64).sqrt().is_nan());

assert_approx_eq!(25f32.powi(-2), 0.0016f32);
assert_approx_eq!(23.2f64.powi(2), 538.24f64);

assert_approx_eq!(25f32.powf(-2f32), 0.0016f32);
assert_approx_eq!(400f64.powf(0.5f64), 20f64);

assert_approx_eq!(1f32.exp(), f32::consts::E);
assert_approx_eq!(1f64.exp(), f64::consts::E);

assert_approx_eq!(1f32.exp_m1(), f32::consts::E - 1.0);
assert_approx_eq!(1f64.exp_m1(), f64::consts::E - 1.0);

assert_approx_eq!(10f32.exp2(), 1024f32);
assert_approx_eq!(50f64.exp2(), 1125899906842624f64);

assert_approx_eq!(f32::consts::E.ln(), 1f32);
assert_approx_eq!(1f64.ln(), 0f64);

assert_approx_eq!(0f32.ln_1p(), 0f32);
assert_approx_eq!(0f64.ln_1p(), 0f64);

assert_approx_eq!(10f32.log10(), 1f32);
assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E);

assert_approx_eq!(8f32.log2(), 3f32);
assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E);

#[allow(deprecated)]
{
assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0);
assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0);
}

assert_approx_eq!(27.0f32.cbrt(), 3.0f32);
assert_approx_eq!(27.0f64.cbrt(), 3.0f64);

assert_approx_eq!(3.0f32.hypot(4.0f32), 5.0f32);
assert_approx_eq!(3.0f64.hypot(4.0f64), 5.0f64);

assert_eq!(ldexp(0.65f64, 3i32), 5.2f64);
assert_eq!(ldexp(1.42, 0xFFFF), f64::INFINITY);
assert_eq!(ldexp(1.42, -0xFFFF), 0f64);

// Trigonometric functions.

assert_approx_eq!(0f32.sin(), 0f32);
assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5);
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5);
assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4);
assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4);

assert_approx_eq!(1.0f32.sinh(), 1.1752012f32);
assert_approx_eq!(1.0f64.sinh(), 1.1752012f64);
assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32);
assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64);

assert_approx_eq!(0f32.cos(), 1f32);
assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5);
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5);
assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4);
assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4);

assert_approx_eq!(1.0f32.cosh(), 1.54308f32);
assert_approx_eq!(1.0f64.cosh(), 1.54308f64);
assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32);
assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64);

assert_approx_eq!(1.0f32.tan(), 1.557408f32);
assert_approx_eq!(1.0f64.tan(), 1.557408f64);
assert_approx_eq!(1.0_f32, 1.0_f32.tan().atan());
assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan());
assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);

assert_approx_eq!(
1.0f32.tanh(),
(1.0 - f32::consts::E.powi(-2)) / (1.0 + f32::consts::E.powi(-2))
);
assert_approx_eq!(
1.0f64.tanh(),
(1.0 - f64::consts::E.powi(-2)) / (1.0 + f64::consts::E.powi(-2))
);
assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32);
assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64);

assert_approx_eq!(5.0f32.gamma(), 24.0);
assert_approx_eq!(5.0f64.gamma(), 24.0);
assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt());
assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt());

assert_eq!(2.0f32.ln_gamma(), (0.0, 1));
assert_eq!(2.0f64.ln_gamma(), (0.0, 1));
// Gamma(-0.5) = -2*sqrt(π)
let (val, sign) = (-0.5f32).ln_gamma();
assert_approx_eq!(val, (2.0 * f32::consts::PI.sqrt()).ln());
assert_eq!(sign, -1);
let (val, sign) = (-0.5f64).ln_gamma();
assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln());
assert_eq!(sign, -1);
}
8 changes: 8 additions & 0 deletions tests/pass/float_nan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ fn test_f32() {
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(f32::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.floor()),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.sin()),
Expand Down Expand Up @@ -376,6 +380,10 @@ fn test_f64() {
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(f64::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.floor()),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.sin()),
Expand Down

0 comments on commit 81e8ff9

Please sign in to comment.