bevy/crates/bevy_math/src/float_ord.rs

178 lines
4.7 KiB
Rust
Raw Normal View History

Add `core` and `alloc` over `std` Lints (#15281) # Objective - Fixes #6370 - Closes #6581 ## Solution - Added the following lints to the workspace: - `std_instead_of_core` - `std_instead_of_alloc` - `alloc_instead_of_core` - Used `cargo +nightly fmt` with [item level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A) to split all `use` statements into single items. - Used `cargo clippy --workspace --all-targets --all-features --fix --allow-dirty` to _attempt_ to resolve the new linting issues, and intervened where the lint was unable to resolve the issue automatically (usually due to needing an `extern crate alloc;` statement in a crate root). - Manually removed certain uses of `std` where negative feature gating prevented `--all-features` from finding the offending uses. - Used `cargo +nightly fmt` with [crate level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A) to re-merge all `use` statements matching Bevy's previous styling. - Manually fixed cases where the `fmt` tool could not re-merge `use` statements due to conditional compilation attributes. ## Testing - Ran CI locally ## Migration Guide The MSRV is now 1.81. Please update to this version or higher. ## Notes - This is a _massive_ change to try and push through, which is why I've outlined the semi-automatic steps I used to create this PR, in case this fails and someone else tries again in the future. - Making this change has no impact on user code, but does mean Bevy contributors will be warned to use `core` and `alloc` instead of `std` where possible. - This lint is a critical first step towards investigating `no_std` options for Bevy. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-09-27 00:59:59 +00:00
use core::{
2020-06-10 22:35:23 +00:00
cmp::Ordering,
hash::{Hash, Hasher},
ops::Neg,
2020-06-10 22:35:23 +00:00
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
///
/// This is a work around for the fact that the IEEE 754-2008 standard,
/// implemented by Rust's [`f32`] type,
/// doesn't define an ordering for [`NaN`](f32::NAN),
/// and `NaN` is not considered equal to any other `NaN`.
///
/// Wrapping a float with `FloatOrd` breaks conformance with the standard
/// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash)
)]
2020-06-10 22:35:23 +00:00
pub struct FloatOrd(pub f32);
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
impl PartialOrd for FloatOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn lt(&self, other: &Self) -> bool {
!other.le(self)
}
// If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
// If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
// Otherwise, a standard float comparison happens.
fn le(&self, other: &Self) -> bool {
self.0.is_nan() || self.0 <= other.0
}
fn gt(&self, other: &Self) -> bool {
!self.le(other)
}
fn ge(&self, other: &Self) -> bool {
other.le(self)
}
}
2020-06-10 22:35:23 +00:00
impl Ord for FloatOrd {
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
#[allow(clippy::comparison_chain)]
2020-06-10 22:35:23 +00:00
fn cmp(&self, other: &Self) -> Ordering {
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
if self > other {
Ordering::Greater
} else if self < other {
Ordering::Less
} else {
Ordering::Equal
}
2020-06-10 22:35:23 +00:00
}
}
impl PartialEq for FloatOrd {
fn eq(&self, other: &Self) -> bool {
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
if self.0.is_nan() {
other.0.is_nan()
2020-06-10 22:35:23 +00:00
} else {
self.0 == other.0
}
}
}
impl Eq for FloatOrd {}
impl Hash for FloatOrd {
fn hash<H: Hasher>(&self, state: &mut H) {
if self.0.is_nan() {
// Ensure all NaN representations hash to the same value
state.write(&f32::to_ne_bytes(f32::NAN));
} else if self.0 == 0.0 {
// Ensure both zeroes hash to the same value
state.write(&f32::to_ne_bytes(0.0f32));
} else {
state.write(&f32::to_ne_bytes(self.0));
}
2020-06-10 22:35:23 +00:00
}
}
impl Neg for FloatOrd {
type Output = FloatOrd;
2020-07-28 21:24:03 +00:00
fn neg(self) -> Self::Output {
FloatOrd(-self.0)
}
2020-07-10 08:37:06 +00:00
}
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
#[cfg(test)]
mod tests {
use super::*;
const NAN: FloatOrd = FloatOrd(f32::NAN);
const ZERO: FloatOrd = FloatOrd(0.0);
const ONE: FloatOrd = FloatOrd(1.0);
#[test]
fn float_ord_eq() {
assert_eq!(NAN, NAN);
assert_ne!(NAN, ZERO);
assert_ne!(ZERO, NAN);
assert_eq!(ZERO, ZERO);
}
#[test]
fn float_ord_cmp() {
assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
}
#[test]
#[allow(clippy::nonminimal_bool)]
fn float_ord_cmp_operators() {
assert!(!(NAN < NAN));
assert!(NAN < ZERO);
assert!(!(ZERO < NAN));
assert!(!(ZERO < ZERO));
assert!(ZERO < ONE);
assert!(!(ONE < ZERO));
assert!(!(NAN > NAN));
assert!(!(NAN > ZERO));
assert!(ZERO > NAN);
assert!(!(ZERO > ZERO));
assert!(!(ZERO > ONE));
assert!(ONE > ZERO);
assert!(NAN <= NAN);
assert!(NAN <= ZERO);
assert!(!(ZERO <= NAN));
assert!(ZERO <= ZERO);
assert!(ZERO <= ONE);
assert!(!(ONE <= ZERO));
assert!(NAN >= NAN);
assert!(!(NAN >= ZERO));
assert!(ZERO >= NAN);
assert!(ZERO >= ZERO);
assert!(!(ZERO >= ONE));
assert!(ONE >= ZERO);
}
Add `no_std` Support to `bevy_math` (#15810) # Objective - Contributes to #15460 ## Solution - Added two new features, `std` (default) and `alloc`, gating `std` and `alloc` behind them respectively. - Added missing `f32` functions to `std_ops` as required. These `f32` methods have been added to the `clippy.toml` deny list to aid in `no_std` development. ## Testing - CI - `cargo clippy -p bevy_math --no-default-features --features libm --target "x86_64-unknown-none"` - `cargo test -p bevy_math --no-default-features --features libm` - `cargo test -p bevy_math --no-default-features --features "libm, alloc"` - `cargo test -p bevy_math --no-default-features --features "libm, alloc, std"` - `cargo test -p bevy_math --no-default-features --features "std"` ## Notes The following items require the `alloc` feature to be enabled: - `CubicBSpline` - `CubicBezier` - `CubicCardinalSpline` - `CubicCurve` - `CubicGenerator` - `CubicHermite` - `CubicNurbs` - `CyclicCubicGenerator` - `RationalCurve` - `RationalGenerator` - `BoxedPolygon` - `BoxedPolyline2d` - `BoxedPolyline3d` - `SampleCurve` - `SampleAutoCurve` - `UnevenSampleCurve` - `UnevenSampleAutoCurve` - `EvenCore` - `UnevenCore` - `ChunkedUnevenCore` This requirement could be relaxed in certain cases, but I had erred on the side of gating rather than modifying. Since `no_std` is a new set of platforms we are adding support to, and the `alloc` feature is enabled by default, this is not a breaking change. --------- Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com> Co-authored-by: Matty <2975848+mweatherley@users.noreply.github.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
2024-12-03 17:14:51 +00:00
#[cfg(feature = "std")]
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
#[test]
fn float_ord_hash() {
let hash = |num| {
Add `no_std` Support to `bevy_math` (#15810) # Objective - Contributes to #15460 ## Solution - Added two new features, `std` (default) and `alloc`, gating `std` and `alloc` behind them respectively. - Added missing `f32` functions to `std_ops` as required. These `f32` methods have been added to the `clippy.toml` deny list to aid in `no_std` development. ## Testing - CI - `cargo clippy -p bevy_math --no-default-features --features libm --target "x86_64-unknown-none"` - `cargo test -p bevy_math --no-default-features --features libm` - `cargo test -p bevy_math --no-default-features --features "libm, alloc"` - `cargo test -p bevy_math --no-default-features --features "libm, alloc, std"` - `cargo test -p bevy_math --no-default-features --features "std"` ## Notes The following items require the `alloc` feature to be enabled: - `CubicBSpline` - `CubicBezier` - `CubicCardinalSpline` - `CubicCurve` - `CubicGenerator` - `CubicHermite` - `CubicNurbs` - `CyclicCubicGenerator` - `RationalCurve` - `RationalGenerator` - `BoxedPolygon` - `BoxedPolyline2d` - `BoxedPolyline3d` - `SampleCurve` - `SampleAutoCurve` - `UnevenSampleCurve` - `UnevenSampleAutoCurve` - `EvenCore` - `UnevenCore` - `ChunkedUnevenCore` This requirement could be relaxed in certain cases, but I had erred on the side of gating rather than modifying. Since `no_std` is a new set of platforms we are adding support to, and the `alloc` feature is enabled by default, this is not a breaking change. --------- Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com> Co-authored-by: Matty <2975848+mweatherley@users.noreply.github.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
2024-12-03 17:14:51 +00:00
let mut h = std::hash::DefaultHasher::new();
Fix `Ord` and `PartialOrd` differing for `FloatOrd` and optimize implementation (#12711) # Objective - `FloatOrd` currently has a different comparison behavior between its derived `PartialOrd` impl and manually implemented `Ord` impl (The [`Ord` doc](https://doc.rust-lang.org/std/cmp/trait.Ord.html) says this is a logic error). This might be a problem for some `std` containers/algorithms if they rely on both matching, and a footgun for Bevy users. ## Solution - Replace the `PartialEq` and `Ord` impls of `FloatOrd` with some equivalent ones producing [better assembly.](https://godbolt.org/z/jaWbjnMKx) - Manually derive `PartialOrd` with the same behavior as `Ord`, implement the comparison operators. - Add some tests. I first tried using a match-based implementation similar to the `PartialOrd` impl [of the std](https://doc.rust-lang.org/src/core/cmp.rs.html#1457) (with added NaN ordering) but I couldn't get it to produce non-branching assembly. The current implementation is based on [the one from the `ordered_float` crate](https://github.com/reem/rust-ordered-float/blob/3641f59e314030b2ffa3e6321c0d51f8ce50b385/src/lib.rs#L121), adapted since it uses a different ordering. Should this be mentionned somewhere in the code? --- ## Changelog ### Fixed - `FloatOrd` now uses the same ordering for its `PartialOrd` and `Ord` implementations. ## Migration Guide - If you were depending on the `PartialOrd` behaviour of `FloatOrd`, it has changed from matching `f32` to matching `FloatOrd`'s `Ord` ordering, never returning `None`.
2024-03-27 00:26:56 +00:00
FloatOrd(num).hash(&mut h);
h.finish()
};
assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
assert_eq!(hash(-0.0), hash(0.0));
let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
assert!(nan_1.is_nan());
let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
assert!(nan_2.is_nan());
assert_ne!(nan_1.to_bits(), nan_2.to_bits());
assert_eq!(hash(nan_1), hash(nan_2));
}
}