1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-03 00:59:02 +00:00
hls_m3u8/src/types/ufloat.rs
2021-10-01 13:37:19 +02:00

320 lines
9.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use core::cmp::Ordering;
use core::convert::TryFrom;
use core::str::FromStr;
use derive_more::{AsRef, Deref, Display};
use crate::Error;
/// A wrapper type around an [`f32`], that can not be constructed
/// with a negative float (e.g. `-1.1`), [`NaN`], [`INFINITY`] or
/// [`NEG_INFINITY`].
///
/// [`NaN`]: core::f32::NAN
/// [`INFINITY`]: core::f32::INFINITY
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
#[derive(AsRef, Deref, Default, Debug, Copy, Clone, Display)]
pub struct UFloat(f32);
impl UFloat {
/// Makes a new [`UFloat`] from an [`f32`].
///
/// # Panics
///
/// If the given float is negative, infinite or [`NaN`].
///
/// # Examples
///
/// ```
/// # use hls_m3u8::types::UFloat;
/// let float = UFloat::new(1.0);
/// ```
///
/// This would panic:
///
/// ```should_panic
/// # use hls_m3u8::types::UFloat;
/// let float = UFloat::new(-1.0);
/// ```
///
/// [`NaN`]: core::f32::NAN
#[must_use]
pub fn new(float: f32) -> Self {
if float.is_infinite() {
panic!("float must be finite: `{}`", float);
}
if float.is_nan() {
panic!("float must not be `NaN`");
}
if float.is_sign_negative() {
panic!("float must be positive: `{}`", float);
}
Self(float)
}
/// Returns the underlying [`f32`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::UFloat;
/// assert_eq!(UFloat::new(1.1_f32).as_f32(), 1.1_f32);
/// ```
#[must_use]
pub const fn as_f32(self) -> f32 { self.0 }
}
impl FromStr for UFloat {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let float = f32::from_str(input).map_err(|e| Error::parse_float(input, e))?;
Self::try_from(float)
}
}
impl TryFrom<f32> for UFloat {
type Error = Error;
fn try_from(float: f32) -> Result<Self, Self::Error> {
if float.is_infinite() {
return Err(Error::custom(format!("float must be finite: `{}`", float)));
}
if float.is_nan() {
return Err(Error::custom("float must not be `NaN`"));
}
if float.is_sign_negative() {
return Err(Error::custom(format!(
"float must be positive: `{}`",
float
)));
}
Ok(Self(float))
}
}
macro_rules! implement_from {
( $( $type:tt ),+ ) => {
$(
impl ::core::convert::From<$type> for UFloat {
fn from(value: $type) -> Self {
Self(value as f32)
}
}
)+
}
}
implement_from!(u16, u8);
// This has to be implemented explicitly, because `Hash` is also implemented
// manually and both implementations have to agree according to clippy.
impl PartialEq for UFloat {
#[inline]
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}
// convenience implementation to compare f32 with a Float.
impl PartialEq<f32> for UFloat {
#[inline]
fn eq(&self, other: &f32) -> bool { &self.0 == other }
}
// In order to implement `Eq` a struct has to satisfy
// the following requirements:
// - reflexive: a == a;
// - symmetric: a == b implies b == a; and
// - transitive: a == b and b == c implies a == c.
//
// The symmetric and transitive parts are already satisfied
// through `PartialEq`. The reflexive part is not satisfied for f32,
// because `f32::NAN` never equals `f32::NAN`. (`assert!(f32::NAN, f32::NAN)`)
//
// It is ensured, that this struct can not be constructed
// with NaN so all of the above requirements are satisfied and therefore Eq can
// be soundly implemented.
impl Eq for UFloat {}
impl PartialOrd for UFloat {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for UFloat {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
if self.0 < other.0 {
Ordering::Less
} else if self == other {
Ordering::Equal
} else {
Ordering::Greater
}
}
}
/// The output of Hash cannot be relied upon to be stable. The same version of
/// rust can return different values in different architectures. This is not a
/// property of the Hasher that youre using but instead of the way Hash happens
/// to be implemented for the type youre using (e.g., the current
/// implementation of Hash for slices of integers returns different values in
/// big and little-endian architectures).
///
/// See <https://internals.rust-lang.org/t/f32-f64-should-implement-hash/5436/33>
#[doc(hidden)]
impl ::core::hash::Hash for UFloat {
fn hash<H>(&self, state: &mut H)
where
H: ::core::hash::Hasher,
{
// this implementation assumes, that the internal float is:
// - positive
// - not NaN
// - neither negative nor positive infinity
// to validate those assumptions debug_assertions are here
// (those will be removed in a release build)
debug_assert!(self.0.is_sign_positive());
debug_assert!(self.0.is_finite());
debug_assert!(!self.0.is_nan());
// this implementation is based on
// https://internals.rust-lang.org/t/f32-f64-should-implement-hash/5436/33
//
// The important points are:
// - NaN == NaN (UFloat does not allow NaN, so this should be satisfied)
// - +0 != -0 (UFloat does not allow negative numbers, so this is fine too)
// I do not think it matters to differentiate between architectures, that use
// big endian by default and those, that use little endian.
state.write(&self.to_be_bytes());
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::hash::{Hash, Hasher};
use pretty_assertions::assert_eq;
#[allow(clippy::all, clippy::unreadable_literal)]
const PI: f32 = 3.14159265359;
#[test]
fn test_display() {
assert_eq!(UFloat::new(22.0).to_string(), "22".to_string());
assert_eq!(UFloat::new(PI).to_string(), "3.1415927".to_string());
}
#[test]
fn test_parser() {
assert_eq!(UFloat::new(22.0), UFloat::from_str("22").unwrap());
assert_eq!(UFloat::new(PI), UFloat::from_str("3.14159265359").unwrap());
assert!(UFloat::from_str("1#").is_err());
assert!(UFloat::from_str("-1.0").is_err());
assert!(UFloat::from_str("NaN").is_err());
assert!(UFloat::from_str("inf").is_err());
assert!(UFloat::from_str("-inf").is_err());
}
#[test]
fn test_hash() {
let mut hasher_left = std::collections::hash_map::DefaultHasher::new();
let mut hasher_right = std::collections::hash_map::DefaultHasher::new();
assert_eq!(
UFloat::new(1.0).hash(&mut hasher_left),
UFloat::new(1.0).hash(&mut hasher_right)
);
assert_eq!(hasher_left.finish(), hasher_right.finish());
}
#[test]
fn test_ord() {
assert_eq!(UFloat::new(1.1).cmp(&UFloat::new(1.1)), Ordering::Equal);
assert_eq!(UFloat::new(1.1).cmp(&UFloat::new(2.1)), Ordering::Less);
assert_eq!(UFloat::new(1.1).cmp(&UFloat::new(0.1)), Ordering::Greater);
}
#[test]
fn test_partial_ord() {
assert_eq!(
UFloat::new(1.1).partial_cmp(&UFloat::new(1.1)),
Some(Ordering::Equal)
);
assert_eq!(
UFloat::new(1.1).partial_cmp(&UFloat::new(2.1)),
Some(Ordering::Less)
);
assert_eq!(
UFloat::new(1.1).partial_cmp(&UFloat::new(0.1)),
Some(Ordering::Greater)
);
}
#[test]
fn test_partial_eq() {
assert_eq!(UFloat::new(1.0).eq(&UFloat::new(1.0)), true);
assert_eq!(UFloat::new(1.0).eq(&UFloat::new(33.3)), false);
assert_eq!(UFloat::new(1.1), 1.1);
}
#[test]
#[should_panic = "float must be positive: `-1.1`"]
fn test_new_negative() { let _ = UFloat::new(-1.1); }
#[test]
#[should_panic = "float must be positive: `-0`"]
fn test_new_negative_zero() { let _ = UFloat::new(-0.0); }
#[test]
#[should_panic = "float must be finite: `inf`"]
fn test_new_infinite() { let _ = UFloat::new(::core::f32::INFINITY); }
#[test]
#[should_panic = "float must be finite: `-inf`"]
fn test_new_neg_infinite() { let _ = UFloat::new(::core::f32::NEG_INFINITY); }
#[test]
#[should_panic = "float must not be `NaN`"]
fn test_new_nan() { let _ = UFloat::new(::core::f32::NAN); }
#[test]
fn test_as_f32() {
assert_eq!(UFloat::new(1.1).as_f32(), 1.1_f32);
}
#[test]
fn test_from() {
assert_eq!(UFloat::from(1_u8), UFloat::new(1.0));
assert_eq!(UFloat::from(1_u16), UFloat::new(1.0));
}
#[test]
fn test_try_from() {
assert_eq!(UFloat::try_from(1.1_f32).unwrap(), UFloat::new(1.1));
assert_eq!(
UFloat::try_from(-1.1_f32),
Err(Error::custom("float must be positive: `-1.1`"))
);
assert!(UFloat::try_from(::core::f32::INFINITY).is_err());
assert!(UFloat::try_from(::core::f32::NAN).is_err());
assert!(UFloat::try_from(::core::f32::NEG_INFINITY).is_err());
}
#[test]
const fn test_eq() {
struct _AssertEq
where
UFloat: Eq;
}
}