diff --git a/src/intrinsics/mod.rs b/src/intrinsics/mod.rs index ec4fdfe0ba..a8655aee0c 100644 --- a/src/intrinsics/mod.rs +++ b/src/intrinsics/mod.rs @@ -7,12 +7,13 @@ use rand::Rng; use rustc_abi::Size; use rustc_apfloat::{Float, Round}; use rustc_middle::mir; -use rustc_middle::ty::{self, FloatTy}; +use rustc_middle::ty::{self, FloatTy, ScalarInt}; use rustc_span::{Symbol, sym}; use self::atomic::EvalContextExt as _; use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count}; use self::simd::EvalContextExt as _; +use crate::math::apply_random_float_error_ulp; use crate::*; impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -206,10 +207,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } + "sqrtf32" => { + let [f] = check_intrinsic_arg_count(args)?; + let f = this.read_scalar(f)?.to_f32()?; + // Sqrt is specified to be fully precise. + let res = math::sqrt(f); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } + "sqrtf64" => { + let [f] = check_intrinsic_arg_count(args)?; + let f = this.read_scalar(f)?.to_f64()?; + // Sqrt is specified to be fully precise. + let res = math::sqrt(f); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } + #[rustfmt::skip] | "sinf32" | "cosf32" - | "sqrtf32" | "expf32" | "exp2f32" | "logf32" @@ -218,26 +235,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats except for sqrt (but it's fine, these operations do not have + // Using host floats (but it's fine, these operations do not have // guaranteed precision). + let host = f.to_host(); let res = match intrinsic_name { - "sinf32" => f.to_host().sin().to_soft(), - "cosf32" => f.to_host().cos().to_soft(), - "sqrtf32" => math::sqrt(f), - "expf32" => f.to_host().exp().to_soft(), - "exp2f32" => f.to_host().exp2().to_soft(), - "logf32" => f.to_host().ln().to_soft(), - "log10f32" => f.to_host().log10().to_soft(), - "log2f32" => f.to_host().log2().to_soft(), + "sinf32" => host.sin(), + "cosf32" => host.cos(), + "expf32" => host.exp(), + "exp2f32" => host.exp2(), + "logf32" => host.ln(), + "log10f32" => host.log10(), + "log2f32" => host.log2(), _ => bug!(), }; + let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } #[rustfmt::skip] | "sinf64" | "cosf64" - | "sqrtf64" | "expf64" | "exp2f64" | "logf64" @@ -246,19 +270,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats except for sqrt (but it's fine, these operations do not have + // Using host floats (but it's fine, these operations do not have // guaranteed precision). + let host = f.to_host(); let res = match intrinsic_name { - "sinf64" => f.to_host().sin().to_soft(), - "cosf64" => f.to_host().cos().to_soft(), - "sqrtf64" => math::sqrt(f), - "expf64" => f.to_host().exp().to_soft(), - "exp2f64" => f.to_host().exp2().to_soft(), - "logf64" => f.to_host().ln().to_soft(), - "log10f64" => f.to_host().log10().to_soft(), - "log2f64" => f.to_host().log2().to_soft(), + "sinf64" => host.sin(), + "cosf64" => host.cos(), + "expf64" => host.exp(), + "exp2f64" => host.exp2(), + "logf64" => host.ln(), + "log10f64" => host.log10(), + "log2f64" => host.log2(), _ => bug!(), }; + let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -316,6 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powf32" => { + // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; @@ -325,6 +358,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powf64" => { + // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; @@ -335,6 +369,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powif32" => { + // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; @@ -344,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powif64" => { + // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; @@ -372,7 +408,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => bug!(), }; let res = this.binary_op(op, &a, &b)?; - // `binary_op` already called `generate_nan` if necessary. + // `binary_op` already called `generate_nan` if needed. + // Apply a relative error of 16ULP to simulate non-deterministic precision loss + // due to optimizations. + let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?; this.write_immediate(*res, dest)?; } @@ -418,11 +457,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => {} } let res = this.binary_op(op, &a, &b)?; + // This cannot be a NaN so we also don't have to apply any non-determinism. + // (Also, `binary_op` already called `generate_nan` if needed.) if !float_finite(&res)? { throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); } - // This cannot be a NaN so we also don't have to apply any non-determinism. - // (Also, `binary_op` already called `generate_nan` if needed.) + // Apply a relative error of 16ULP to simulate non-deterministic precision loss + // due to optimizations. + let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?; this.write_immediate(*res, dest)?; } @@ -455,3 +497,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } } + +/// Applies a random 16ULP floating point error to `val` and returns the new value. +/// Will fail if `val` is not a floating point number. +fn apply_random_float_error_to_imm<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + val: ImmTy<'tcx>, + ulp_exponent: u32, +) -> InterpResult<'tcx, ImmTy<'tcx>> { + let scalar = val.to_scalar_int()?; + let res: ScalarInt = match val.layout.ty.kind() { + ty::Float(FloatTy::F16) => + apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(), + ty::Float(FloatTy::F32) => + apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(), + ty::Float(FloatTy::F64) => + apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(), + ty::Float(FloatTy::F128) => + apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(), + _ => bug!("intrinsic called with non-float input type"), + }; + + interp_ok(ImmTy::from_scalar_int(res, val.layout)) +} diff --git a/src/math.rs b/src/math.rs index 7117f722fe..fdd021f853 100644 --- a/src/math.rs +++ b/src/math.rs @@ -27,6 +27,22 @@ pub(crate) fn apply_random_float_error( (val * (F::from_u128(1).value + err).value).value } +/// [`apply_random_float_error`] gives instructions to apply a 2^N ULP error. +/// This function implements these instructions such that applying a 2^N ULP error is less error prone. +/// So for a 2^N ULP error, you would pass N as the `ulp_exponent` argument. +pub(crate) fn apply_random_float_error_ulp( + ecx: &mut crate::MiriInterpCx<'_>, + val: F, + ulp_exponent: u32, +) -> F { + let n = i32::try_from(ulp_exponent) + .expect("`err_scale_for_ulp`: exponent is too large to create an error scale"); + // we know this fits + let prec = i32::try_from(F::PRECISION).unwrap(); + let err_scale = -(prec - n - 1); + apply_random_float_error(ecx, val, err_scale) +} + pub(crate) fn sqrt(x: IeeeFloat) -> IeeeFloat { match x.category() { // preserve zero sign diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index bedc1ebdc9..ec8f666382 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -765,7 +765,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "erfcf" => f_host.erfc(), _ => bug!(), }; - let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res.to_soft(), + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -788,6 +794,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res, + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } @@ -826,7 +839,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "erfc" => f_host.erfc(), _ => bug!(), }; - let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res.to_soft(), + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -849,6 +868,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res, + 4, // log2(16) + ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } @@ -874,7 +900,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = + math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */); + let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } "lgamma_r" => { @@ -885,7 +915,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = + math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */); + let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } diff --git a/tests/pass/float.rs b/tests/pass/float.rs index 51cafbb3f4..0eb7d6e830 100644 --- a/tests/pass/float.rs +++ b/tests/pass/float.rs @@ -13,11 +13,34 @@ use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; +/// Compare the two floats, allowing for $ulp many ULPs of error. +/// +/// ULP means "Units in the Last Place" or "Units of Least Precision". +/// The ULP of a float `a`` is the smallest possible change at `a`, so the ULP difference represents how +/// many discrete floating-point steps are needed to reach the actual value from the expected value. +/// +/// Essentially ULP can be seen as a distance metric of floating-point numbers, but with +/// the same amount of "spacing" between all consecutive representable values. So even though 2 very large floating point numbers +/// have a large value difference, their ULP can still be 1, so they are still "approximatly equal", +/// but the EPSILON check would have failed. 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); + ($a:expr, $b:expr, $ulp:expr) => {{ + let (actual, expected) = ($a, $b); + let allowed_ulp_diff = $ulp; + let _force_same_type = actual == expected; + // Approximate the ULP by taking half the distance between the number one place "up" + // and the number one place "down". + let ulp = (expected.next_up() - expected.next_down()) / 2.0; + let ulp_diff = ((actual - expected) / ulp).abs().round() as i32; + if ulp_diff > allowed_ulp_diff { + panic!("{actual:?} is not approximately equal to {expected:?}\ndifference in ULP: {ulp_diff} > {allowed_ulp_diff}"); + }; }}; + + ($a:expr, $b: expr) => { + // accept up to 64ULP (16ULP for host floats and 16ULP for miri artificial error and 32 for any rounding errors) + assert_approx_eq!($a, $b, 64); + }; } fn main() { @@ -33,6 +56,7 @@ fn main() { test_algebraic(); test_fmuladd(); test_min_max_nondet(); + test_non_determinism(); } trait Float: Copy + PartialEq + Debug { @@ -1029,7 +1053,7 @@ pub fn libm() { 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!(1.0f64.sinh(), 1.1752011936438014f64); assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); @@ -1041,12 +1065,12 @@ pub fn libm() { 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!(1.0f64.cosh(), 1.5430806348152437f64); 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.0f64.tan(), 1.5574077246549023f64); 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); @@ -1091,11 +1115,11 @@ fn test_fast() { pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1103,11 +1127,11 @@ fn test_fast() { pub fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1115,11 +1139,11 @@ fn test_fast() { pub fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1127,11 +1151,11 @@ fn test_fast() { pub fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1153,41 +1177,41 @@ fn test_algebraic() { #[inline(never)] pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } test_operations_f16(11., 2.); @@ -1245,3 +1269,106 @@ fn test_min_max_nondet() { ensure_both(|| f128::min(0.0, -0.0).is_sign_positive()); ensure_both(|| f128::max(0.0, -0.0).is_sign_positive()); } + +fn test_non_determinism() { + use std::intrinsics::{ + fadd_algebraic, fadd_fast, fdiv_algebraic, fdiv_fast, fmul_algebraic, fmul_fast, + frem_algebraic, frem_fast, fsub_algebraic, fsub_fast, + }; + use std::{f32, f64}; + // TODO: Also test powi and powf when the non-determinism is implemented for them + + /// Ensure that the operation is non-deterministic + #[track_caller] + fn ensure_nondet(f: impl Fn() -> T) { + let rounds = 16; + let first = f(); + for _ in 1..rounds { + if f() != first { + // We saw two different values! + return; + } + } + // We saw the same thing N times. + panic!("expected non-determinism, got {rounds} times the same result: {first:?}"); + } + + macro_rules! test_operations_f { + ($a:expr, $b:expr) => { + ensure_nondet(|| fadd_algebraic($a, $b)); + ensure_nondet(|| fsub_algebraic($a, $b)); + ensure_nondet(|| fmul_algebraic($a, $b)); + ensure_nondet(|| fdiv_algebraic($a, $b)); + ensure_nondet(|| frem_algebraic($a, $b)); + + unsafe { + ensure_nondet(|| fadd_fast($a, $b)); + ensure_nondet(|| fsub_fast($a, $b)); + ensure_nondet(|| fmul_fast($a, $b)); + ensure_nondet(|| fdiv_fast($a, $b)); + ensure_nondet(|| frem_fast($a, $b)); + } + }; + } + + pub fn test_operations_f16(a: f16, b: f16) { + test_operations_f!(a, b); + } + pub fn test_operations_f32(a: f32, b: f32) { + test_operations_f!(a, b); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 10f32.exp2()); + ensure_nondet(|| f32::consts::E.ln()); + ensure_nondet(|| 1f32.ln_1p()); + ensure_nondet(|| 10f32.log10()); + ensure_nondet(|| 8f32.log2()); + ensure_nondet(|| 27.0f32.cbrt()); + ensure_nondet(|| 3.0f32.hypot(4.0f32)); + ensure_nondet(|| 1f32.sin()); + ensure_nondet(|| 0f32.cos()); + ensure_nondet(|| 1.0f32.sinh()); + ensure_nondet(|| 1.0f32.asinh()); + ensure_nondet(|| 1.0f32.cosh()); + ensure_nondet(|| 2.0f32.acosh()); + ensure_nondet(|| 1.0f32.tan()); + ensure_nondet(|| 1.0f32.tanh()); + ensure_nondet(|| 1.0f32.atan2(2.0f32)); + ensure_nondet(|| 0.5f32.atanh()); + ensure_nondet(|| 5.0f32.gamma()); + ensure_nondet(|| 5.0f32.erf()); + ensure_nondet(|| 5.0f32.erfc()); + } + pub fn test_operations_f64(a: f64, b: f64) { + test_operations_f!(a, b); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 50f64.exp2()); + ensure_nondet(|| 3f64.ln()); + ensure_nondet(|| 1f64.ln_1p()); + ensure_nondet(|| f64::consts::E.log10()); + ensure_nondet(|| f64::consts::E.log2()); + ensure_nondet(|| 1f64.sin()); + ensure_nondet(|| 0f64.cos()); + ensure_nondet(|| 27.0f64.cbrt()); + ensure_nondet(|| 3.0f64.hypot(4.0f64)); + ensure_nondet(|| 1.0f64.sinh()); + ensure_nondet(|| 1.0f64.asinh()); + ensure_nondet(|| 1.0f64.cosh()); + ensure_nondet(|| 3.0f64.acosh()); + ensure_nondet(|| 1.0f64.tan()); + ensure_nondet(|| 1.0f64.tanh()); + ensure_nondet(|| 0.5f64.atanh()); + ensure_nondet(|| 5.0f64.gamma()); + ensure_nondet(|| 5.0f64.erf()); + ensure_nondet(|| 5.0f64.erfc()); + } + pub fn test_operations_f128(a: f128, b: f128) { + test_operations_f!(a, b); + } + + test_operations_f16(5., 7.); + test_operations_f32(12., 5.); + test_operations_f64(19., 11.); + test_operations_f128(25., 18.); +}