ZeroToOne now checks when dividing

Refs #120
This commit is contained in:
Jonathan Johnson 2023-12-31 07:36:54 -08:00
parent 15960da098
commit 0fb93c7be8
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
2 changed files with 80 additions and 5 deletions

View file

@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
incorrectly.
- A potential edge case where a `DynamicReader` would not return after being
disconnected has been removed.
- [#120][120]: Dividing a `ZeroToOne` now properly checks for `NaN` and `0.`.
### Changed
@ -71,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
basis using `RunningWindow::kludgine_id()` as the key.
[99]: https://github.com/khonsulabs/cushy/issues/99
[120]: https://github.com/khonsulabs/cushy/issues/120
## v0.2.0

View file

@ -1146,6 +1146,21 @@ impl ZeroToOne {
pub fn one_minus(self) -> Self {
Self(1. - self.0)
}
fn checked_div<Float: PossiblyNaN>(&mut self, rhs: Float) {
let rhs = rhs.into();
if Float::CAN_BE_NAN {
assert!(!rhs.is_nan());
}
if rhs > 0. {
self.0 /= rhs;
} else if *self > 0. {
// The limit of f(x) -> x/0 is infinity, but the highest value we
// can represent is 1.0.
*self = Self::ONE;
}
// If both lhs and rhs are 0, this is a noop.
}
}
impl Zero for ZeroToOne {
@ -1171,12 +1186,28 @@ impl FromStr for ZeroToOne {
}
impl From<f32> for ZeroToOne {
/// Returns a `ZeroToOne` clamped between 0.0 and 1.0.
///
/// # Panics
///
/// This function panics if `value` is not a number.
fn from(value: f32) -> Self {
Self::new(value)
}
}
impl From<ZeroToOne> for f32 {
fn from(value: ZeroToOne) -> Self {
value.0
}
}
impl From<f64> for ZeroToOne {
/// Returns a `ZeroToOne` clamped between 0.0 and 1.0.
///
/// # Panics
///
/// This function panics if `value` is not a number.
fn from(value: f64) -> Self {
Self::new(value.cast())
}
@ -1258,25 +1289,55 @@ impl MulAssign for ZeroToOne {
impl Div for ZeroToOne {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
self / rhs.0
fn div(mut self, rhs: Self) -> Self::Output {
self /= rhs;
self
}
}
impl DivAssign for ZeroToOne {
fn div_assign(&mut self, rhs: Self) {
self.0 /= rhs.0;
self.checked_div(rhs);
}
}
impl Div<f32> for ZeroToOne {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self(self.0 / rhs)
/// Divides `self` by `rhs`.
///
/// # Panics
///
/// This function panics if `rhs` is not a number.
fn div(mut self, rhs: f32) -> Self::Output {
self /= rhs;
self
}
}
impl DivAssign<f32> for ZeroToOne {
/// Divides `self` by `rhs`.
///
/// # Panics
///
/// This function panics if `rhs` is not a number.
fn div_assign(&mut self, rhs: f32) {
self.checked_div(rhs);
}
}
trait PossiblyNaN: Into<f32> {
const CAN_BE_NAN: bool;
}
impl PossiblyNaN for ZeroToOne {
const CAN_BE_NAN: bool = false;
}
impl PossiblyNaN for f32 {
const CAN_BE_NAN: bool = true;
}
impl Ranged for ZeroToOne {
const MAX: Self = Self::ONE;
const MIN: Self = Self::ZERO;
@ -1305,6 +1366,18 @@ impl From<ZeroToOne> for Component {
}
}
#[test]
fn zero_to_one_div() {
std::panic::catch_unwind(|| ZeroToOne::ONE / f32::NAN).expect_err("div by nan");
let mut value = ZeroToOne::ONE;
std::panic::catch_unwind(move || value /= f32::NAN).expect_err("div by nan");
assert_eq!(ZeroToOne::ZERO / ZeroToOne::ZERO, ZeroToOne::ZERO);
assert_eq!(ZeroToOne::new(0.5) / ZeroToOne::ZERO, ZeroToOne::ONE);
assert_eq!(ZeroToOne::ZERO / 0., ZeroToOne::ZERO);
assert_eq!(ZeroToOne::new(0.5) / 0., ZeroToOne::ONE);
}
/// An easing function for customizing animations.
#[derive(Debug, Clone)]
pub enum EasingFunction {