Assert_eq!() giving weird results with f32/f64 on macOS!

So, I have a test case that uses an array of f32 values. It creates a clone of that array, "randomizes" the order of the values, and then brings them back into the "correct" order (by swapping the elements as needed). Finally, I will compare the "test" array (clone) to the original one, element by element, to verify that each element indeed ended up at the initial position.

Code looks like this:

fn assert_arrays_equal_f32(array_a: &[f32], array_b: &[f32]) {
    assert_eq!(array_a.len(), array_b.len());
    for (a, b) in array_a.iter().copied().zip(array_b.iter().copied()) {
        assert_eq!(a, b);
    }
}

Now, the weird thing is that this test works perfectly fine on Linux and on Windows, but fails on MacOS only! I understand that assert_eq!(a, b) can be "problematic" if floating point values are "very close" but not exactly the same. But this can not be the problem here, as I just move around the values, not do any FP math with them. They should match exactly!


I have added some extra debug outputs:

fn assert_arrays_equal_f32(array_a: &[f32], array_b: &[f32]) {
    assert_eq!(array_a.len(), array_b.len());
    for (a, b) in array_a.iter().copied().zip(array_b.iter().copied()) {
        if !(a == b) {
            println!("{:.16} != {:.16}", a, b);
            println!("{:08X} != {:08X}", a.to_bits(), b.to_bits());
            panic!("Did *not* match!");
        }
        assert_eq!(a, b);
    }
}

Output on MacOS, where the test fails:

-1.0000000000000000 != -1.0000000000000000
BF800000 != BF800000

thread 'test_float_3a' (11369) panicked at tests/float_test.rs:18:13:
Did *not* match!

How the heck is this possible? :exploding_head:

Clearly, the floating point numbers are exactly the same. Their bit-representation is the same!

Still, they compare not equal ???

And why does this only happen on the MacOS runner, not on Linux or Windows? :face_with_raised_eyebrow:

This is with Rust 1.92.0, byte the way.

Any ideas ???

Thank you and best regards.

1 Like

x86_64 or arm64? Do you use any unsafe code that could potentially have UB? Do you have a standalone reproducer?

While this is the case for NaNs (NaNs are not equal to themselves), based on the information you have given that is not the case here. So it does indeed seem strange. Do you have UB somwhere in your code? Try using Miri.

x86_64 or arm64? Do you use any unsafe code that could potentially have UB? Do you have a standalone reproducer?

I use macos-latest runner on GitHub. Should be arm64, I think:

And I do not use "unsafe" code.

Do you have a dependency that uses unsafe internally (apart from std)?

The only dev-dependencies I have are:

  • itertools (to create all possible permutations of the array)
  • rand
  • rand_pcg

(there are no non-dev dependencies involved)

I think the most valuable thing to do next will be to create a self-contained program that demonstrates the problem. Then other people can test it on their hardware and compilers.

5 Likes

Test program:

use itertools::Itertools;
use std::{cmp::Ordering, fmt::Debug};

// --------------------------------------------------------------------------
// Utility functions
// --------------------------------------------------------------------------

fn assert_arrays_equal<T: FloatType>(array_a: &[FloatOrd<T>], array_b: &[T]) {
    assert_eq!(array_a.len(), array_b.len());
    for (a, b) in array_a.iter().map(|val| val.into_inner()).zip(array_b.iter().copied()) {
        /*if !(a == b) {
            println!("{:.16} != {:.16}", a, b);
            println!("{:08X} != {:08X}", a.to_bits(), b.to_bits());
            panic!("Did *not* match!");
        }*/
        assert_eq!(a, b);
    }
}

// --------------------------------------------------------------------------
// Generic float type
// --------------------------------------------------------------------------

pub trait FloatType: Copy + Clone + PartialEq + PartialOrd + Default + Debug {
    fn is_nan(self) -> bool;
}

impl FloatType for f32 {
    #[inline]
    fn is_nan(self) -> bool {
        f32::is_nan(self)
    }
}

impl FloatType for f64 {
    #[inline]
    fn is_nan(self) -> bool {
        f64::is_nan(self)
    }
}

// --------------------------------------------------------------------------
// Ordered wrapper
// --------------------------------------------------------------------------

#[derive(Debug, Clone, Copy)]
pub struct FloatOrd<T: FloatType>(T);

impl<T: FloatType> FloatOrd<T> {
    #[inline]
    pub fn new(value: T) -> Result<Self, ()> {
        if value.is_nan() {
            return Err(());
        }
        Ok(Self(value))
    }

    #[inline]
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T: FloatType> PartialEq for FloatOrd<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T: FloatType> Eq for FloatOrd<T> {}

impl<T: FloatType> PartialOrd for FloatOrd<T> {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T: FloatType> Ord for FloatOrd<T> {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        if self.0 < other.0 {
            Ordering::Less
        } else if self.0 > other.0 {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

// --------------------------------------------------------------------------
// Tests
// --------------------------------------------------------------------------

#[test]
fn test_float_1a() {
    static VALUES: [f32; 11usize] = [
        f32::NEG_INFINITY,
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::MIN_POSITIVE,
        0.0f32,
        f32::MIN_POSITIVE,
        1.0f32,
        16777215.0f32,
        f32::MAX,
        f32::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_1b() {
    static VALUES: [f64; 11usize] = [
        f64::NEG_INFINITY,
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::MIN_POSITIVE,
        0.0f64,
        f64::MIN_POSITIVE,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
        f64::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2a() {
    static VALUES: [f32; 11usize] = [
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::EPSILON,
        -f32::MIN_POSITIVE / 2.0f32,
        0.0f32,
        f32::MIN_POSITIVE / 2.0f32,
        f32::EPSILON,
        1.0f32,
        16777215.0f32,
        f32::MAX,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2b() {
    static VALUES: [f64; 11usize] = [
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::EPSILON,
        -f64::MIN_POSITIVE / 2.0f64,
        0.0f64,
        f64::MIN_POSITIVE / 2.0f64,
        f64::EPSILON,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

Output on MacOS runner:

running 4 tests
test test_float_1a ... ok
test test_float_1b ... ok
test test_float_2a ... ok
test test_float_2b ... FAILED

failures:

---- test_float_2b stdout ----

thread 'test_float_2b' panicked at tests/float_test.rs:20:9:
assertion `left == right` failed
  left: 0.0
 right: 0.0
stack backtrace:
   0: __rustc::rust_begin_unwind
error: test failed, to rerun pass `--test float_test`
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: float_test::assert_arrays_equal
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    test_float_2b

test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 27.83s

Error: Process completed with exit code 101.

Second attempt, slightly different error:

running 4 tests
test test_float_1a ... ok
test test_float_1b ... ok
test test_float_2a ... ok
test test_float_2b ... FAILED
error: test failed, to rerun pass `--test float_test`

failures:

---- test_float_2b stdout ----

thread 'test_float_2b' (39127) panicked at tests/float_test.rs:20:9:
assertion `left == right` failed
  left: -9007199254740991.0
 right: -9007199254740991.0
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: float_test::assert_arrays_equal
   5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    test_float_2b

test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.60s

Error: Process completed with exit code 101.

Meanwhile, everything is fine on Linux and Windows :face_with_raised_eyebrow:

FWIW, these tests pass on my Apple M4 Max aarch64-apple-darwin.

1 Like

Your Eq implementation is wrong. The documentation states explicitly that for Eq to be implemented PartialEq implementation must be reflexive: a == a for any a.

I do not have your tests failing, but can you try with

use itertools::Itertools;
use std::{cmp::Ordering, fmt::Debug};

// --------------------------------------------------------------------------
// Utility functions
// --------------------------------------------------------------------------

fn assert_arrays_equal<T: FloatType>(array_a: &[FloatOrd<T>], array_b: &[T]) {
    assert_eq!(array_a.len(), array_b.len());
    for (a, b) in array_a.iter().map(|val| val.into_inner()).zip(array_b.iter().copied()) {
        /*if !(a == b) {
            println!("{:.16} != {:.16}", a, b);
            println!("{:08X} != {:08X}", a.to_bits(), b.to_bits());
            panic!("Did *not* match!");
        }*/
        assert_eq!(a, b);
    }
}

// --------------------------------------------------------------------------
// Generic float type
// --------------------------------------------------------------------------

pub trait FloatType: Copy + Clone + PartialEq + PartialOrd + Default + Debug {
    type Bits: Copy + Clone + PartialEq + Eq + Debug;

    fn is_nan(self) -> bool;

    fn to_bits(self) -> Self::Bits;
}

impl FloatType for f32 {
    type Bits = u32;

    #[inline]
    fn is_nan(self) -> bool {
        f32::is_nan(self)
    }

    #[inline]
    fn to_bits(self) -> Self::Bits {
        self.to_bits()
    }
}

impl FloatType for f64 {
    type Bits = u64;

    #[inline]
    fn is_nan(self) -> bool {
        f64::is_nan(self)
    }

    #[inline]
    fn to_bits(self) -> Self::Bits {
        self.to_bits()
    }
}

// --------------------------------------------------------------------------
// Ordered wrapper
// --------------------------------------------------------------------------

#[derive(Debug, Clone, Copy)]
pub struct FloatOrd<T: FloatType>(T);

impl<T: FloatType> FloatOrd<T> {
    #[inline]
    pub fn new(value: T) -> Result<Self, ()> {
        if value.is_nan() {
            return Err(());
        }
        Ok(Self(value))
    }

    #[inline]
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T: FloatType> PartialEq for FloatOrd<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.0.to_bits() == other.0.to_bits()
    }
}

impl<T: FloatType> Eq for FloatOrd<T> {}

impl<T: FloatType> PartialOrd for FloatOrd<T> {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T: FloatType> Ord for FloatOrd<T> {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        if self.0 < other.0 {
            Ordering::Less
        } else if self.0 > other.0 {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

// --------------------------------------------------------------------------
// Tests
// --------------------------------------------------------------------------

#[test]
fn test_float_1a() {
    static VALUES: [f32; 11usize] = [
        f32::NEG_INFINITY,
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::MIN_POSITIVE,
        0.0f32,
        f32::MIN_POSITIVE,
        1.0f32,
        16777215.0f32,
        f32::MAX,
        f32::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_1b() {
    static VALUES: [f64; 11usize] = [
        f64::NEG_INFINITY,
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::MIN_POSITIVE,
        0.0f64,
        f64::MIN_POSITIVE,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
        f64::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2a() {
    static VALUES: [f32; 11usize] = [
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::EPSILON,
        -f32::MIN_POSITIVE / 2.0f32,
        0.0f32,
        f32::MIN_POSITIVE / 2.0f32,
        f32::EPSILON,
        1.0f32,
        16777215.0f32,
        f32::MAX,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2b() {
    static VALUES: [f64; 11usize] = [
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::EPSILON,
        -f64::MIN_POSITIVE / 2.0f64,
        0.0f64,
        f64::MIN_POSITIVE / 2.0f64,
        f64::EPSILON,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    for mut array in ordered.iter().copied().permutations(VALUES.len()) {
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

? This adds to_bits to FloatType trait and changes PartialEq for FloatOrd so that it compares bits.

Their FloatOrd::new does not allow NaN values, and otherwise reflexive equality should be fine.

3 Likes

Their FloatOrd::new does not allow NaN values, and otherwise reflexive equality should be fine.

I agree.

Also, at the point where the error occurs, we compare the "plain" f32 or f64 values, not the wrapped vales! The FloatOrd wrapper is only needed (used) to make sort() work.

You should try to make a minimal example that fails. Remove all the code that is unnecessary to demonstrate failure, and make the failing code as simple as possible. This would make it a lot easier to analyze.

2 Likes

I've provided a minimal, yet complete (self-contained), example above.

While one normally doesn't mean "minimal example" to be "the proven minimum number of bytes needed for a compilable example to illustrate the bug", I find it hard to believe this is anywhere close.

Presumably you tried much simpler examples like assert_eq(-1.0f32, -1.0f32) rather than the use of all these traits and external crates like rand? Presumably this bug occurs when you remove all the test functions sans test_float_2b? Is inline required? Can you illustrate this bug without itertools?

You seem to have found some problematic f32/f64 values that may be sufficient to hardcode without having to randomly generate other values. I imagine super simple examples won't work since I'd guess such a bug would have been reported already, so it wouldn't surprise me if itertools is needed especially if it relies on unsafe code. It'd at least be nice to know what simple examples you've tried on this M1 runner on GitHub that don't exhibit this bug.

5 Likes

I have seen that first message has f32s:

-1.0000000000000000 != -1.0000000000000000
BF800000 != BF800000

thread 'test_float_3a' (11369) panicked at tests/float_test.rs:18:13:
Did *not* match!

and message with “minimal” test program fails only on f64s, but on different ones during different runs. Wondering whether to_bits (in the assert_arrays_equal, not where I originially suggested) will even help, or is it compiler optimizing some comparisons out which it should not have.

New test program without itertools:

use std::{cmp::Ordering, fmt::Debug};

// --------------------------------------------------------------------------
// Utility functions
// --------------------------------------------------------------------------

fn assert_arrays_equal<T: FloatType>(array_a: &[FloatOrd<T>], array_b: &[T]) {
    assert_eq!(array_a.len(), array_b.len());
    for (a, b) in array_a.iter().map(|val| val.into_inner()).zip(array_b.iter().copied()) {
        /*if !(a == b) {
            println!("{:.16} != {:.16}", a, b);
            println!("{:08X} != {:08X}", a.to_bits(), b.to_bits());
            panic!("Did *not* match!");
        }*/
        assert_eq!(a, b);
    }
}

// --------------------------------------------------------------------------
// Generic float type
// --------------------------------------------------------------------------

pub trait FloatType: Copy + Clone + PartialEq + PartialOrd + Default + Debug {
    fn is_nan(self) -> bool;
}

impl FloatType for f32 {
    #[inline]
    fn is_nan(self) -> bool {
        f32::is_nan(self)
    }
}

impl FloatType for f64 {
    #[inline]
    fn is_nan(self) -> bool {
        f64::is_nan(self)
    }
}

// --------------------------------------------------------------------------
// Ordered wrapper
// --------------------------------------------------------------------------

#[derive(Debug, Clone, Copy)]
pub struct FloatOrd<T: FloatType>(T);

impl<T: FloatType> FloatOrd<T> {
    #[inline]
    pub fn new(value: T) -> Result<Self, ()> {
        if value.is_nan() {
            return Err(());
        }
        Ok(Self(value))
    }

    #[inline]
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T: FloatType> PartialEq for FloatOrd<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T: FloatType> Eq for FloatOrd<T> {}

impl<T: FloatType> PartialOrd for FloatOrd<T> {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T: FloatType> Ord for FloatOrd<T> {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        if self.0 < other.0 {
            Ordering::Less
        } else if self.0 > other.0 {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

// --------------------------------------------------------------------------
// Tests
// --------------------------------------------------------------------------

#[test]
fn test_float_1a() {
    static VALUES: [f32; 11usize] = [
        f32::NEG_INFINITY,
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::MIN_POSITIVE,
        0.0f32,
        f32::MIN_POSITIVE,
        1.0f32,
        16777215.0f32,
        f32::MAX,
        f32::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    let n = ordered.len();
    for i in 0..999999999usize {
        let mut array = ordered.clone();
        array.rotate_left(i % n);
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_1b() {
    static VALUES: [f64; 11usize] = [
        f64::NEG_INFINITY,
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::MIN_POSITIVE,
        0.0f64,
        f64::MIN_POSITIVE,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
        f64::INFINITY,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    let n = ordered.len();
    for i in 0..999999999usize {
        let mut array = ordered.clone();
        array.rotate_left(i % n);
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2a() {
    static VALUES: [f32; 11usize] = [
        f32::MIN,
        -16777215.0f32,
        -1.0f32,
        -f32::EPSILON,
        -f32::MIN_POSITIVE / 2.0f32,
        0.0f32,
        f32::MIN_POSITIVE / 2.0f32,
        f32::EPSILON,
        1.0f32,
        16777215.0f32,
        f32::MAX,
    ];

    let ordered: Vec<FloatOrd<f32>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    let n = ordered.len();
    for i in 0..999999999usize {
        let mut array = ordered.clone();
        array.rotate_left(i % n);
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

#[test]
fn test_float_2b() {
    static VALUES: [f64; 11usize] = [
        f64::MIN,
        -9007199254740991.0f64,
        -1.0f64,
        -f64::EPSILON,
        -f64::MIN_POSITIVE / 2.0f64,
        0.0f64,
        f64::MIN_POSITIVE / 2.0f64,
        f64::EPSILON,
        1.0f64,
        9007199254740991.0f64,
        f64::MAX,
    ];

    let ordered: Vec<FloatOrd<f64>> = VALUES.iter().copied().map(|val| FloatOrd::new(val).unwrap()).collect();
    let n = ordered.len();
    for i in 0..999999999usize {
        let mut array = ordered.clone();
        array.rotate_left(i % n);
        array.sort();
        assert_arrays_equal(&array[..], &VALUES[..]);
    }
}

Result on MacOS runner:

running 4 tests
test test_float_1a ... FAILED
test test_float_2a ... FAILED
test test_float_1b ... ok
test test_float_2b ... ok

failures:

---- test_float_1a stdout ----

thread 'test_float_1a' panicked at tests/float_test.rs:15:9:
assertion `left == right` failed
  left: -3.4028235e38
 right: -3.4028235e38
stack backtrace:
   0: __rustc::rust_begin_unwind
error: test failed, to rerun pass `--test float_test`
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

---- test_float_2a stdout ----

thread 'test_float_2a' panicked at tests/float_test.rs:15:9:
assertion `left == right` failed
  left: -5.877472e-39
 right: -5.877472e-39
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    test_float_1a
    test_float_2a

test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.67s

Error: Process completed with exit code 101.

Attempt #2:

running 4 tests
test test_float_2a ... FAILED
test test_float_1a ... ok
test test_float_1b ... ok
test test_float_2b ... ok

failures:

---- test_float_2a stdout ----

thread 'test_float_2a' panicked at tests/float_test.rs:15:9:
assertion `left == right` failed
  left: -16777215.0
 right: -16777215.0
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    test_float_2a

test result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 20.43s

error: test failed, to rerun pass `--test float_test`
Error: Process completed with exit code 101.

Kind of inconsistent :face_with_diagonal_mouth:

Needless to say, Linux and Windows runners finish without problem.

1 Like

You should print out i so you know how fast it fails. I'm getting the feeling there's something wrong with the runner, though. I would also try black_box before cloning or comparing, comparing u32s, using a different comparison like >=, and using a different sort function.

In your tests, can you print /proc/cpuinfo on failure?

That won’t work. /proc is a Linux thing[1] that does not exist on macOS.

The system_profiler command will dump a lot of information about the system including the CPU model and core count, but I don’t know if that includes what you were hoping for.


  1. though I think it might have been imitated by some other Unixes? ↩︎