Auto merge of #138062 - LorrensP-2158466:miri-enable-float-nondet, r=RalfJung
Enable Non-determinism of float operations in Miri and change std tests Links to [#4208](https://github.com/rust-lang/miri/issues/4208) and [#3555](https://github.com/rust-lang/miri/issues/3555) in Miri. Non-determinism of floating point operations was disabled in rust-lang/rust#137594 because it breaks the tests and doc-tests in core/coretests and std. This PR enables some of them. This pr includes the following changes: - Enables the float non-determinism but with a lower relative error of 4ULP instead of 16ULP - These operations now have a fixed output based on the C23 standard, except the pow operations, this is tracked in [#4286](https://github.com/rust-lang/miri/issues/4286#issue-3010677983) - Changes tests that made incorrect assumptions about the operations, not to make that assumption anymore (from `assert_eq!` to `assert_approx_eq!`. - Changed the doctests of the stdlib of these operations to compare against fixed constants instead of `f*::EPSILON`, which now succeed with Miri and `-Zmiri-many-seeds` - Added a constant `APPROX_DELTA` in `std/tests/floats/f32.rs` which is used for approximation tests, but with a different value when run in Miri. This is to make these tests succeed. - Added tests in the float tests of Miri to test the C23 behaviour. Fixes https://github.com/rust-lang/miri/issues/4208
This commit is contained in:
commit
c6768de2d6
12 changed files with 386 additions and 107 deletions
|
|
@ -1879,7 +1879,7 @@ pub mod math {
|
|||
///
|
||||
/// let x = 2.0_f32;
|
||||
/// let abs_difference = (f32::math::powi(x, 2) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-5);
|
||||
///
|
||||
/// assert_eq!(f32::math::powi(f32::NAN, 0), 1.0);
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -1877,7 +1877,7 @@ pub mod math {
|
|||
///
|
||||
/// let x = 2.0_f64;
|
||||
/// let abs_difference = (f64::math::powi(x, 2) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f64::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
///
|
||||
/// assert_eq!(f64::math::powi(f64::NAN, 0), 1.0);
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ const NAN_MASK1: u32 = 0x002a_aaaa;
|
|||
/// Second pattern over the mantissa
|
||||
const NAN_MASK2: u32 = 0x0055_5555;
|
||||
|
||||
/// Miri adds some extra errors to float functions; make sure the tests still pass.
|
||||
/// These values are purely used as a canary to test against and are thus not a stable guarantee Rust provides.
|
||||
/// They serve as a way to get an idea of the real precision of floating point operations on different platforms.
|
||||
const APPROX_DELTA: f32 = if cfg!(miri) { 1e-4 } else { 1e-6 };
|
||||
|
||||
#[test]
|
||||
fn test_num_f32() {
|
||||
super::test_num(10f32, 2f32);
|
||||
|
|
@ -437,8 +442,8 @@ fn test_powi() {
|
|||
let nan: f32 = f32::NAN;
|
||||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
assert_biteq!(1.0f32.powi(1), 1.0);
|
||||
assert_approx_eq!((-3.1f32).powi(2), 9.61);
|
||||
assert_approx_eq!(1.0f32.powi(1), 1.0);
|
||||
assert_approx_eq!((-3.1f32).powi(2), 9.61, APPROX_DELTA);
|
||||
assert_approx_eq!(5.9f32.powi(-2), 0.028727);
|
||||
assert_biteq!(8.3f32.powi(0), 1.0);
|
||||
assert!(nan.powi(2).is_nan());
|
||||
|
|
|
|||
|
|
@ -422,7 +422,7 @@ fn test_powi() {
|
|||
let nan: f64 = f64::NAN;
|
||||
let inf: f64 = f64::INFINITY;
|
||||
let neg_inf: f64 = f64::NEG_INFINITY;
|
||||
assert_biteq!(1.0f64.powi(1), 1.0);
|
||||
assert_approx_eq!(1.0f64.powi(1), 1.0);
|
||||
assert_approx_eq!((-3.1f64).powi(2), 9.61);
|
||||
assert_approx_eq!(5.9f64.powi(-2), 0.028727);
|
||||
assert_biteq!(8.3f64.powi(0), 1.0);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ fn test_f16_integer_decode() {
|
|||
fn test_f32_integer_decode() {
|
||||
assert_eq!(3.14159265359f32.integer_decode(), (13176795, -22, 1));
|
||||
assert_eq!((-8573.5918555f32).integer_decode(), (8779358, -10, -1));
|
||||
assert_eq!(2f32.powf(100.0).integer_decode(), (8388608, 77, 1));
|
||||
// Set 2^100 directly instead of using powf, because it doesn't guarentee precision
|
||||
assert_eq!(1.2676506e30_f32.integer_decode(), (8388608, 77, 1));
|
||||
assert_eq!(0f32.integer_decode(), (0, -150, 1));
|
||||
assert_eq!((-0f32).integer_decode(), (0, -150, -1));
|
||||
assert_eq!(f32::INFINITY.integer_decode(), (8388608, 105, 1));
|
||||
|
|
@ -39,7 +40,8 @@ fn test_f32_integer_decode() {
|
|||
fn test_f64_integer_decode() {
|
||||
assert_eq!(3.14159265359f64.integer_decode(), (7074237752028906, -51, 1));
|
||||
assert_eq!((-8573.5918555f64).integer_decode(), (4713381968463931, -39, -1));
|
||||
assert_eq!(2f64.powf(100.0).integer_decode(), (4503599627370496, 48, 1));
|
||||
// Set 2^100 directly instead of using powf, because it doesn't guarentee precision
|
||||
assert_eq!(1.2676506002282294e30_f64.integer_decode(), (4503599627370496, 48, 1));
|
||||
assert_eq!(0f64.integer_decode(), (0, -1075, 1));
|
||||
assert_eq!((-0f64).integer_decode(), (0, -1075, -1));
|
||||
assert_eq!(f64::INFINITY.integer_decode(), (4503599627370496, 972, 1));
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ impl f32 {
|
|||
/// ```
|
||||
/// let x = 2.0_f32;
|
||||
/// let abs_difference = (x.powi(2) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-5);
|
||||
///
|
||||
/// assert_eq!(f32::powi(f32::NAN, 0), 1.0);
|
||||
/// ```
|
||||
|
|
@ -328,7 +328,7 @@ impl f32 {
|
|||
/// ```
|
||||
/// let x = 2.0_f32;
|
||||
/// let abs_difference = (x.powf(2.0) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-5);
|
||||
///
|
||||
/// assert_eq!(f32::powf(1.0, f32::NAN), 1.0);
|
||||
/// assert_eq!(f32::powf(f32::NAN, 0.0), 1.0);
|
||||
|
|
@ -388,7 +388,7 @@ impl f32 {
|
|||
/// // ln(e) - 1 == 0
|
||||
/// let abs_difference = (e.ln() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[rustc_allow_incoherent_impl]
|
||||
#[must_use = "method returns a new number and does not mutate the original value"]
|
||||
|
|
@ -413,7 +413,7 @@ impl f32 {
|
|||
/// // 2^2 - 4 == 0
|
||||
/// let abs_difference = (f.exp2() - 4.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-5);
|
||||
/// ```
|
||||
#[rustc_allow_incoherent_impl]
|
||||
#[must_use = "method returns a new number and does not mutate the original value"]
|
||||
|
|
@ -442,7 +442,7 @@ impl f32 {
|
|||
/// // ln(e) - 1 == 0
|
||||
/// let abs_difference = (e.ln() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
///
|
||||
/// Non-positive values:
|
||||
|
|
@ -479,7 +479,7 @@ impl f32 {
|
|||
/// // log5(5) - 1 == 0
|
||||
/// let abs_difference = (five.log(5.0) - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
///
|
||||
/// Non-positive values:
|
||||
|
|
@ -512,7 +512,7 @@ impl f32 {
|
|||
/// // log2(2) - 1 == 0
|
||||
/// let abs_difference = (two.log2() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
///
|
||||
/// Non-positive values:
|
||||
|
|
@ -545,7 +545,7 @@ impl f32 {
|
|||
/// // log10(10) - 1 == 0
|
||||
/// let abs_difference = (ten.log10() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
///
|
||||
/// Non-positive values:
|
||||
|
|
@ -652,7 +652,7 @@ impl f32 {
|
|||
/// // sqrt(x^2 + y^2)
|
||||
/// let abs_difference = (x.hypot(y) - (x.powi(2) + y.powi(2)).sqrt()).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[rustc_allow_incoherent_impl]
|
||||
#[must_use = "method returns a new number and does not mutate the original value"]
|
||||
|
|
@ -676,7 +676,7 @@ impl f32 {
|
|||
///
|
||||
/// let abs_difference = (x.sin() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[rustc_allow_incoherent_impl]
|
||||
#[must_use = "method returns a new number and does not mutate the original value"]
|
||||
|
|
@ -700,7 +700,7 @@ impl f32 {
|
|||
///
|
||||
/// let abs_difference = (x.cos() - 1.0).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[rustc_allow_incoherent_impl]
|
||||
#[must_use = "method returns a new number and does not mutate the original value"]
|
||||
|
|
@ -754,7 +754,7 @@ impl f32 {
|
|||
/// // asin(sin(pi/2))
|
||||
/// let abs_difference = (f.sin().asin() - std::f32::consts::FRAC_PI_2).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-3);
|
||||
/// ```
|
||||
#[doc(alias = "arcsin")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
@ -784,7 +784,7 @@ impl f32 {
|
|||
/// // acos(cos(pi/4))
|
||||
/// let abs_difference = (f.cos().acos() - std::f32::consts::FRAC_PI_4).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[doc(alias = "arccos")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
@ -884,8 +884,8 @@ impl f32 {
|
|||
/// let abs_difference_0 = (f.0 - x.sin()).abs();
|
||||
/// let abs_difference_1 = (f.1 - x.cos()).abs();
|
||||
///
|
||||
/// assert!(abs_difference_0 <= f32::EPSILON);
|
||||
/// assert!(abs_difference_1 <= f32::EPSILON);
|
||||
/// assert!(abs_difference_0 <= 1e-6);
|
||||
/// assert!(abs_difference_1 <= 1e-6);
|
||||
/// ```
|
||||
#[doc(alias = "sincos")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
@ -1067,7 +1067,7 @@ impl f32 {
|
|||
///
|
||||
/// let abs_difference = (f - x).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-7);
|
||||
/// ```
|
||||
#[doc(alias = "arcsinh")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
@ -1095,7 +1095,7 @@ impl f32 {
|
|||
///
|
||||
/// let abs_difference = (f - x).abs();
|
||||
///
|
||||
/// assert!(abs_difference <= f32::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-6);
|
||||
/// ```
|
||||
#[doc(alias = "arccosh")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ impl f64 {
|
|||
/// ```
|
||||
/// let x = 2.0_f64;
|
||||
/// let abs_difference = (x.powi(2) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f64::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-14);
|
||||
///
|
||||
/// assert_eq!(f64::powi(f64::NAN, 0), 1.0);
|
||||
/// ```
|
||||
|
|
@ -328,7 +328,7 @@ impl f64 {
|
|||
/// ```
|
||||
/// let x = 2.0_f64;
|
||||
/// let abs_difference = (x.powf(2.0) - (x * x)).abs();
|
||||
/// assert!(abs_difference <= f64::EPSILON);
|
||||
/// assert!(abs_difference <= 1e-14);
|
||||
///
|
||||
/// assert_eq!(f64::powf(1.0, f64::NAN), 1.0);
|
||||
/// assert_eq!(f64::powf(f64::NAN, 0.0), 1.0);
|
||||
|
|
@ -754,7 +754,7 @@ impl f64 {
|
|||
/// // asin(sin(pi/2))
|
||||
/// let abs_difference = (f.sin().asin() - std::f64::consts::FRAC_PI_2).abs();
|
||||
///
|
||||
/// assert!(abs_difference < 1e-10);
|
||||
/// assert!(abs_difference < 1e-7);
|
||||
/// ```
|
||||
#[doc(alias = "arcsin")]
|
||||
#[rustc_allow_incoherent_impl]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
use std::f32::consts;
|
||||
|
||||
/// Miri adds some extra errors to float functions; make sure the tests still pass.
|
||||
/// These values are purely used as a canary to test against and are thus not a stable guarantee Rust provides.
|
||||
/// They serve as a way to get an idea of the real precision of floating point operations on different platforms.
|
||||
const APPROX_DELTA: f32 = if cfg!(miri) { 1e-3 } else { 1e-6 };
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! assert_f32_biteq {
|
||||
($left : expr, $right : expr) => {
|
||||
|
|
@ -17,9 +22,9 @@ fn test_powf() {
|
|||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
assert_eq!(1.0f32.powf(1.0), 1.0);
|
||||
assert_approx_eq!(3.4f32.powf(4.5), 246.408218);
|
||||
assert_approx_eq!(3.4f32.powf(4.5), 246.408218, APPROX_DELTA);
|
||||
assert_approx_eq!(2.7f32.powf(-3.2), 0.041652);
|
||||
assert_approx_eq!((-3.1f32).powf(2.0), 9.61);
|
||||
assert_approx_eq!((-3.1f32).powf(2.0), 9.61, APPROX_DELTA);
|
||||
assert_approx_eq!(5.9f32.powf(-2.0), 0.028727);
|
||||
assert_eq!(8.3f32.powf(0.0), 1.0);
|
||||
assert!(nan.powf(2.0).is_nan());
|
||||
|
|
@ -30,8 +35,8 @@ fn test_powf() {
|
|||
#[test]
|
||||
fn test_exp() {
|
||||
assert_eq!(1.0, 0.0f32.exp());
|
||||
assert_approx_eq!(2.718282, 1.0f32.exp());
|
||||
assert_approx_eq!(148.413162, 5.0f32.exp());
|
||||
assert_approx_eq!(2.718282, 1.0f32.exp(), APPROX_DELTA);
|
||||
assert_approx_eq!(148.413162, 5.0f32.exp(), APPROX_DELTA);
|
||||
|
||||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
|
|
@ -43,7 +48,7 @@ fn test_exp() {
|
|||
|
||||
#[test]
|
||||
fn test_exp2() {
|
||||
assert_eq!(32.0, 5.0f32.exp2());
|
||||
assert_approx_eq!(32.0, 5.0f32.exp2(), APPROX_DELTA);
|
||||
assert_eq!(1.0, 0.0f32.exp2());
|
||||
|
||||
let inf: f32 = f32::INFINITY;
|
||||
|
|
@ -66,7 +71,7 @@ fn test_ln() {
|
|||
assert!((-2.3f32).ln().is_nan());
|
||||
assert_eq!((-0.0f32).ln(), neg_inf);
|
||||
assert_eq!(0.0f32.ln(), neg_inf);
|
||||
assert_approx_eq!(4.0f32.ln(), 1.386294);
|
||||
assert_approx_eq!(4.0f32.ln(), 1.386294, APPROX_DELTA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -74,9 +79,9 @@ fn test_log() {
|
|||
let nan: f32 = f32::NAN;
|
||||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
assert_eq!(10.0f32.log(10.0), 1.0);
|
||||
assert_approx_eq!(10.0f32.log(10.0), 1.0);
|
||||
assert_approx_eq!(2.3f32.log(3.5), 0.664858);
|
||||
assert_eq!(1.0f32.exp().log(1.0f32.exp()), 1.0);
|
||||
assert_approx_eq!(1.0f32.exp().log(1.0f32.exp()), 1.0, APPROX_DELTA);
|
||||
assert!(1.0f32.log(1.0).is_nan());
|
||||
assert!(1.0f32.log(-13.9).is_nan());
|
||||
assert!(nan.log(2.3).is_nan());
|
||||
|
|
@ -92,9 +97,9 @@ fn test_log2() {
|
|||
let nan: f32 = f32::NAN;
|
||||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
assert_approx_eq!(10.0f32.log2(), 3.321928);
|
||||
assert_approx_eq!(10.0f32.log2(), 3.321928, APPROX_DELTA);
|
||||
assert_approx_eq!(2.3f32.log2(), 1.201634);
|
||||
assert_approx_eq!(1.0f32.exp().log2(), 1.442695);
|
||||
assert_approx_eq!(1.0f32.exp().log2(), 1.442695, APPROX_DELTA);
|
||||
assert!(nan.log2().is_nan());
|
||||
assert_eq!(inf.log2(), inf);
|
||||
assert!(neg_inf.log2().is_nan());
|
||||
|
|
@ -108,7 +113,7 @@ fn test_log10() {
|
|||
let nan: f32 = f32::NAN;
|
||||
let inf: f32 = f32::INFINITY;
|
||||
let neg_inf: f32 = f32::NEG_INFINITY;
|
||||
assert_eq!(10.0f32.log10(), 1.0);
|
||||
assert_approx_eq!(10.0f32.log10(), 1.0);
|
||||
assert_approx_eq!(2.3f32.log10(), 0.361728);
|
||||
assert_approx_eq!(1.0f32.exp().log10(), 0.434294);
|
||||
assert_eq!(1.0f32.log10(), 0.0);
|
||||
|
|
@ -158,7 +163,7 @@ fn test_acosh() {
|
|||
assert_approx_eq!(3.0f32.acosh(), 1.76274717403908605046521864995958461f32);
|
||||
|
||||
// test for low accuracy from issue 104548
|
||||
assert_approx_eq!(60.0f32, 60.0f32.cosh().acosh());
|
||||
assert_approx_eq!(60.0f32, 60.0f32.cosh().acosh(), APPROX_DELTA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -237,7 +242,7 @@ fn test_real_consts() {
|
|||
let ln_10: f32 = consts::LN_10;
|
||||
|
||||
assert_approx_eq!(frac_pi_2, pi / 2f32);
|
||||
assert_approx_eq!(frac_pi_3, pi / 3f32);
|
||||
assert_approx_eq!(frac_pi_3, pi / 3f32, APPROX_DELTA);
|
||||
assert_approx_eq!(frac_pi_4, pi / 4f32);
|
||||
assert_approx_eq!(frac_pi_6, pi / 6f32);
|
||||
assert_approx_eq!(frac_pi_8, pi / 8f32);
|
||||
|
|
@ -249,5 +254,5 @@ fn test_real_consts() {
|
|||
assert_approx_eq!(log2_e, e.log2());
|
||||
assert_approx_eq!(log10_e, e.log10());
|
||||
assert_approx_eq!(ln_2, 2f32.ln());
|
||||
assert_approx_eq!(ln_10, 10f32.ln());
|
||||
assert_approx_eq!(ln_10, 10f32.ln(), APPROX_DELTA);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ fn test_exp() {
|
|||
|
||||
#[test]
|
||||
fn test_exp2() {
|
||||
assert_eq!(32.0, 5.0f64.exp2());
|
||||
assert_approx_eq!(32.0, 5.0f64.exp2());
|
||||
assert_eq!(1.0, 0.0f64.exp2());
|
||||
|
||||
let inf: f64 = f64::INFINITY;
|
||||
|
|
@ -74,9 +74,9 @@ fn test_log() {
|
|||
let nan: f64 = f64::NAN;
|
||||
let inf: f64 = f64::INFINITY;
|
||||
let neg_inf: f64 = f64::NEG_INFINITY;
|
||||
assert_eq!(10.0f64.log(10.0), 1.0);
|
||||
assert_approx_eq!(10.0f64.log(10.0), 1.0);
|
||||
assert_approx_eq!(2.3f64.log(3.5), 0.664858);
|
||||
assert_eq!(1.0f64.exp().log(1.0f64.exp()), 1.0);
|
||||
assert_approx_eq!(1.0f64.exp().log(1.0f64.exp()), 1.0);
|
||||
assert!(1.0f64.log(1.0).is_nan());
|
||||
assert!(1.0f64.log(-13.9).is_nan());
|
||||
assert!(nan.log(2.3).is_nan());
|
||||
|
|
@ -108,7 +108,7 @@ fn test_log10() {
|
|||
let nan: f64 = f64::NAN;
|
||||
let inf: f64 = f64::INFINITY;
|
||||
let neg_inf: f64 = f64::NEG_INFINITY;
|
||||
assert_eq!(10.0f64.log10(), 1.0);
|
||||
assert_approx_eq!(10.0f64.log10(), 1.0);
|
||||
assert_approx_eq!(2.3f64.log10(), 0.361728);
|
||||
assert_approx_eq!(1.0f64.exp().log10(), 0.434294);
|
||||
assert_eq!(1.0f64.log10(), 0.0);
|
||||
|
|
|
|||
|
|
@ -3,17 +3,20 @@
|
|||
mod atomic;
|
||||
mod simd;
|
||||
|
||||
use std::ops::Neg;
|
||||
|
||||
use rand::Rng;
|
||||
use rustc_abi::Size;
|
||||
use rustc_apfloat::{Float, Round};
|
||||
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
|
||||
use rustc_apfloat::{self, 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_to_imm;
|
||||
use crate::math::{IeeeExt, apply_random_float_error_ulp};
|
||||
use crate::*;
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
|
|
@ -187,31 +190,39 @@ 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 (but it's fine, these operations do not have
|
||||
// guaranteed precision).
|
||||
let host = f.to_host();
|
||||
let res = match intrinsic_name {
|
||||
"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.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res,
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{
|
||||
// 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" => 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 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = apply_random_float_error_ulp(
|
||||
this,
|
||||
res,
|
||||
2, // log2(4)
|
||||
);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
clamp_float_value(intrinsic_name, res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
| "sinf64"
|
||||
| "cosf64"
|
||||
|
|
@ -223,28 +234,35 @@ 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 (but it's fine, these operations do not have
|
||||
// guaranteed precision).
|
||||
let host = f.to_host();
|
||||
let res = match intrinsic_name {
|
||||
"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.
|
||||
// FIXME: temporarily disabled as it breaks std tests.
|
||||
// let res = apply_random_float_error_ulp(
|
||||
// this,
|
||||
// res,
|
||||
// 4, // log2(16)
|
||||
// );
|
||||
|
||||
let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{
|
||||
// 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" => 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 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
let res = apply_random_float_error_ulp(
|
||||
this,
|
||||
res,
|
||||
2, // log2(4)
|
||||
);
|
||||
|
||||
// Clamp the result to the guaranteed range of this function according to the C standard,
|
||||
// if any.
|
||||
clamp_float_value(intrinsic_name, res)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -302,43 +320,75 @@ 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()?;
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f1.to_host().powf(f2.to_host()).to_soft();
|
||||
|
||||
let res = fixed_float_value(intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f1.to_host().powf(f2.to_host()).to_soft();
|
||||
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
apply_random_float_error_ulp(
|
||||
this, res, 2, // log2(4)
|
||||
)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f1, f2]);
|
||||
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()?;
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f1.to_host().powf(f2.to_host()).to_soft();
|
||||
|
||||
let res = fixed_float_value(intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f1.to_host().powf(f2.to_host()).to_soft();
|
||||
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
apply_random_float_error_ulp(
|
||||
this, res, 2, // log2(4)
|
||||
)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f1, f2]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
"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()?;
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
let res = fixed_powi_float_value(f, i).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
apply_random_float_error_ulp(
|
||||
this, res, 2, // log2(4)
|
||||
)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
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()?;
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
let res = fixed_powi_float_value(f, i).unwrap_or_else(|| {
|
||||
// Using host floats (but it's fine, this operation does not have guaranteed precision).
|
||||
let res = f.to_host().powi(i).to_soft();
|
||||
|
||||
// Apply a relative error of 4ULP to introduce some non-determinism
|
||||
// simulating imprecise implementations and optimizations.
|
||||
apply_random_float_error_ulp(
|
||||
this, res, 2, // log2(4)
|
||||
)
|
||||
});
|
||||
let res = this.adjust_nan(res, &[f]);
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
|
@ -425,3 +475,97 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(EmulateItemResult::NeedsReturn)
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a random ULP floating point error to `val` and returns the new value.
|
||||
/// So if you want an X ULP error, `ulp_exponent` should be log2(X).
|
||||
///
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// For the intrinsics:
|
||||
/// - sinf32, sinf64
|
||||
/// - cosf32, cosf64
|
||||
/// - expf32, expf64, exp2f32, exp2f64
|
||||
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
|
||||
/// - powf32, powf64
|
||||
///
|
||||
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
|
||||
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
|
||||
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
|
||||
/// implementation. Returns `None` if no specific value is guaranteed.
|
||||
fn fixed_float_value<S: Semantics>(
|
||||
intrinsic_name: &str,
|
||||
args: &[IeeeFloat<S>],
|
||||
) -> Option<IeeeFloat<S>> {
|
||||
let one = IeeeFloat::<S>::one();
|
||||
match (intrinsic_name, args) {
|
||||
// cos(+- 0) = 1
|
||||
("cosf32" | "cosf64", [input]) if input.is_zero() => Some(one),
|
||||
|
||||
// e^0 = 1
|
||||
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => Some(one),
|
||||
|
||||
// 1^y = 1 for any y, even a NaN.
|
||||
("powf32" | "powf64", [base, _]) if *base == one => Some(one),
|
||||
|
||||
// (-1)^(±INF) = 1
|
||||
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => Some(one),
|
||||
|
||||
// FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
|
||||
// the NaN. We should return either 1 or the NaN non-deterministically here.
|
||||
// But for now, just handle them all the same.
|
||||
// x^(±0) = 1 for any x, even a NaN
|
||||
("powf32" | "powf64", [_, exp]) if exp.is_zero() => Some(one),
|
||||
|
||||
// There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
|
||||
// which are not affected by the applied error.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
|
||||
/// (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
|
||||
fn fixed_powi_float_value<S: Semantics>(base: IeeeFloat<S>, exp: i32) -> Option<IeeeFloat<S>> {
|
||||
match (base.category(), exp) {
|
||||
// x^0 = 1, if x is not a Signaling NaN
|
||||
// FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
|
||||
// the NaN. We should return either 1 or the NaN non-deterministically here.
|
||||
// But for now, just handle them all the same.
|
||||
(_, 0) => Some(IeeeFloat::<S>::one()),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an floating-point operation and a floating-point value, clamps the result to the output
|
||||
/// range of the given operation.
|
||||
fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
|
||||
match intrinsic_name {
|
||||
// sin and cos: [-1, 1]
|
||||
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
|
||||
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
|
||||
// exp: [0, +INF]
|
||||
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
|
||||
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
|
||||
_ => val,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,20 @@ pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFl
|
|||
}
|
||||
}
|
||||
|
||||
/// Extend functionality of rustc_apfloat softfloats
|
||||
pub trait IeeeExt: rustc_apfloat::Float {
|
||||
#[inline]
|
||||
fn one() -> Self {
|
||||
Self::from_u128(1).value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp(self, min: Self, max: Self) -> Self {
|
||||
self.maximum(min).minimum(max)
|
||||
}
|
||||
}
|
||||
impl<S: rustc_apfloat::ieee::Semantics> IeeeExt for IeeeFloat<S> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, QuadS, SingleS};
|
||||
|
|
|
|||
|
|
@ -45,6 +45,31 @@ macro_rules! assert_approx_eq {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/// From IEEE 754 a Signaling NaN for single precision has the following representation:
|
||||
/// ```
|
||||
/// s | 1111 1111 | 0x..x
|
||||
/// ````
|
||||
/// Were at least one `x` is a 1.
|
||||
///
|
||||
/// This sNaN has the following representation and is used for testing purposes.:
|
||||
/// ```
|
||||
/// 0 | 1111111 | 01..0
|
||||
/// ```
|
||||
const SNAN_F32: f32 = f32::from_bits(0x7fa00000);
|
||||
|
||||
/// From IEEE 754 a Signaling NaN for double precision has the following representation:
|
||||
/// ```
|
||||
/// s | 1111 1111 111 | 0x..x
|
||||
/// ````
|
||||
/// Were at least one `x` is a 1.
|
||||
///
|
||||
/// This sNaN has the following representation and is used for testing purposes.:
|
||||
/// ```
|
||||
/// 0 | 1111 1111 111 | 01..0
|
||||
/// ```
|
||||
const SNAN_F64: f64 = f64::from_bits(0x7ff4000000000000);
|
||||
|
||||
fn main() {
|
||||
basic();
|
||||
casts();
|
||||
|
|
@ -1008,17 +1033,84 @@ pub fn libm() {
|
|||
assert_approx_eq!(25f32.powf(-2f32), 0.0016f32);
|
||||
assert_approx_eq!(400f64.powf(0.5f64), 20f64);
|
||||
|
||||
// Some inputs to powf and powi result in fixed outputs
|
||||
// and thus must be exactly equal to that value.
|
||||
// C standard says:
|
||||
// 1^y = 1 for any y, even a NaN.
|
||||
assert_eq!(1f32.powf(10.0), 1.0);
|
||||
assert_eq!(1f64.powf(100.0), 1.0);
|
||||
assert_eq!(1f32.powf(f32::INFINITY), 1.0);
|
||||
assert_eq!(1f64.powf(f64::INFINITY), 1.0);
|
||||
assert_eq!(1f32.powf(f32::NAN), 1.0);
|
||||
assert_eq!(1f64.powf(f64::NAN), 1.0);
|
||||
|
||||
// f*::NAN is a quiet NAN and should return 1 as well.
|
||||
assert_eq!(f32::NAN.powf(0.0), 1.0);
|
||||
assert_eq!(f64::NAN.powf(0.0), 1.0);
|
||||
|
||||
assert_eq!(42f32.powf(0.0), 1.0);
|
||||
assert_eq!(42f64.powf(0.0), 1.0);
|
||||
assert_eq!(f32::INFINITY.powf(0.0), 1.0);
|
||||
assert_eq!(f64::INFINITY.powf(0.0), 1.0);
|
||||
|
||||
// f*::NAN is a quiet NAN and should return 1 as well.
|
||||
assert_eq!(f32::NAN.powi(0), 1.0);
|
||||
assert_eq!(f64::NAN.powi(0), 1.0);
|
||||
|
||||
assert_eq!(10.0f32.powi(0), 1.0);
|
||||
assert_eq!(10.0f64.powi(0), 1.0);
|
||||
assert_eq!(f32::INFINITY.powi(0), 1.0);
|
||||
assert_eq!(f64::INFINITY.powi(0), 1.0);
|
||||
|
||||
assert_eq!((-1f32).powf(f32::INFINITY), 1.0);
|
||||
assert_eq!((-1f64).powf(f64::INFINITY), 1.0);
|
||||
assert_eq!((-1f32).powf(f32::NEG_INFINITY), 1.0);
|
||||
assert_eq!((-1f64).powf(f64::NEG_INFINITY), 1.0);
|
||||
|
||||
// For pow (powf in rust) the C standard says:
|
||||
// x^0 = 1 for all x even a sNaN
|
||||
// FIXME(#4286): this does not match the behavior of all implementations.
|
||||
assert_eq!(SNAN_F32.powf(0.0), 1.0);
|
||||
assert_eq!(SNAN_F64.powf(0.0), 1.0);
|
||||
|
||||
// For pown (powi in rust) the C standard says:
|
||||
// x^0 = 1 for all x even a sNaN
|
||||
// FIXME(#4286): this does not match the behavior of all implementations.
|
||||
assert_eq!(SNAN_F32.powi(0), 1.0);
|
||||
assert_eq!(SNAN_F64.powi(0), 1.0);
|
||||
|
||||
assert_eq!(0f32.powi(10), 0.0);
|
||||
assert_eq!(0f64.powi(100), 0.0);
|
||||
assert_eq!(0f32.powi(9), 0.0);
|
||||
assert_eq!(0f64.powi(99), 0.0);
|
||||
|
||||
assert_biteq((-0f32).powf(10.0), 0.0, "-0^x = +0 where x is positive");
|
||||
assert_biteq((-0f64).powf(100.0), 0.0, "-0^x = +0 where x is positive");
|
||||
assert_biteq((-0f32).powf(9.0), -0.0, "-0^x = -0 where x is negative");
|
||||
assert_biteq((-0f64).powf(99.0), -0.0, "-0^x = -0 where x is negative");
|
||||
|
||||
assert_biteq((-0f32).powi(10), 0.0, "-0^x = +0 where x is positive");
|
||||
assert_biteq((-0f64).powi(100), 0.0, "-0^x = +0 where x is positive");
|
||||
assert_biteq((-0f32).powi(9), -0.0, "-0^x = -0 where x is negative");
|
||||
assert_biteq((-0f64).powi(99), -0.0, "-0^x = -0 where x is negative");
|
||||
|
||||
assert_approx_eq!(1f32.exp(), f32::consts::E);
|
||||
assert_approx_eq!(1f64.exp(), f64::consts::E);
|
||||
assert_eq!(0f32.exp(), 1.0);
|
||||
assert_eq!(0f64.exp(), 1.0);
|
||||
|
||||
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_eq!(0f32.exp2(), 1.0);
|
||||
assert_eq!(0f64.exp2(), 1.0);
|
||||
|
||||
assert_approx_eq!(f32::consts::E.ln(), 1f32);
|
||||
assert_approx_eq!(1f64.ln(), 0f64);
|
||||
assert_approx_eq!(f64::consts::E.ln(), 1f64);
|
||||
assert_eq!(1f32.ln(), 0.0);
|
||||
assert_eq!(1f64.ln(), 0.0);
|
||||
|
||||
assert_approx_eq!(0f32.ln_1p(), 0f32);
|
||||
assert_approx_eq!(0f64.ln_1p(), 0f64);
|
||||
|
|
@ -1047,7 +1139,8 @@ pub fn libm() {
|
|||
|
||||
// Trigonometric functions.
|
||||
|
||||
assert_approx_eq!(0f32.sin(), 0f32);
|
||||
assert_eq!(0f32.sin(), 0f32);
|
||||
assert_eq!(0f64.sin(), 0f64);
|
||||
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);
|
||||
|
|
@ -1059,7 +1152,23 @@ pub fn libm() {
|
|||
assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32);
|
||||
assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64);
|
||||
|
||||
assert_approx_eq!(0f32.cos(), 1f32);
|
||||
// Ensure `sin` always returns something that is a valid input for `asin`, and same for
|
||||
// `cos` and `acos`.
|
||||
let halve_pi_f32 = std::f32::consts::FRAC_PI_2;
|
||||
let halve_pi_f64 = std::f64::consts::FRAC_PI_2;
|
||||
let pi_f32 = std::f32::consts::PI;
|
||||
let pi_f64 = std::f64::consts::PI;
|
||||
for _ in 0..64 {
|
||||
// sin() should be clamped to [-1, 1] so asin() can never return NaN
|
||||
assert!(!halve_pi_f32.sin().asin().is_nan());
|
||||
assert!(!halve_pi_f64.sin().asin().is_nan());
|
||||
// cos() should be clamped to [-1, 1] so acos() can never return NaN
|
||||
assert!(!pi_f32.cos().acos().is_nan());
|
||||
assert!(!pi_f64.cos().acos().is_nan());
|
||||
}
|
||||
|
||||
assert_eq!(0f32.cos(), 1f32);
|
||||
assert_eq!(0f64.cos(), 1f64);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue