2020-02-16 16:09:40 +00:00
|
|
|
|
use core::cmp::Ordering;
|
2020-02-10 12:13:41 +00:00
|
|
|
|
use core::convert::TryFrom;
|
|
|
|
|
use core::str::FromStr;
|
|
|
|
|
|
2020-02-24 11:19:37 +00:00
|
|
|
|
use derive_more::{AsRef, Deref, Display};
|
2020-02-10 12:13:41 +00:00
|
|
|
|
|
|
|
|
|
use crate::Error;
|
|
|
|
|
|
2020-02-24 11:19:37 +00:00
|
|
|
|
/// A wrapper type around an [`f32`], that can not be constructed
|
|
|
|
|
/// with a negative float (e.g. `-1.1`), [`NaN`], [`INFINITY`] or
|
2020-02-10 12:13:41 +00:00
|
|
|
|
/// [`NEG_INFINITY`].
|
|
|
|
|
///
|
2020-02-10 12:20:39 +00:00
|
|
|
|
/// [`NaN`]: core::f32::NAN
|
2020-02-10 12:13:41 +00:00
|
|
|
|
/// [`INFINITY`]: core::f32::INFINITY
|
|
|
|
|
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
|
2020-02-24 11:19:37 +00:00
|
|
|
|
#[derive(AsRef, Deref, Default, Debug, Copy, Clone, PartialOrd, Display)]
|
2020-02-10 12:13:41 +00:00
|
|
|
|
pub struct UFloat(f32);
|
|
|
|
|
|
|
|
|
|
impl UFloat {
|
|
|
|
|
/// Makes a new [`UFloat`] from an [`f32`].
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// If the given float is negative, infinite or [`NaN`].
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2020-02-24 11:19:37 +00:00
|
|
|
|
/// # use hls_m3u8::types::UFloat;
|
2020-02-10 12:13:41 +00:00
|
|
|
|
/// let float = UFloat::new(1.0);
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// This would panic:
|
|
|
|
|
///
|
|
|
|
|
/// ```should_panic
|
2020-02-24 11:19:37 +00:00
|
|
|
|
/// # use hls_m3u8::types::UFloat;
|
2020-02-10 12:13:41 +00:00
|
|
|
|
/// let float = UFloat::new(-1.0);
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// [`NaN`]: core::f32::NAN
|
2020-02-24 15:30:43 +00:00
|
|
|
|
#[must_use]
|
2020-02-10 12:13:41 +00:00
|
|
|
|
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`].
|
2020-02-24 11:19:37 +00:00
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// # use hls_m3u8::types::UFloat;
|
|
|
|
|
/// assert_eq!(UFloat::new(1.1_f32).as_f32(), 1.1_f32);
|
|
|
|
|
/// ```
|
2020-02-24 15:30:43 +00:00
|
|
|
|
#[must_use]
|
2020-02-10 12:13:41 +00:00
|
|
|
|
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> {
|
2020-02-14 12:01:42 +00:00
|
|
|
|
let float = f32::from_str(input).map_err(|e| Error::parse_float(input, e))?;
|
2020-02-10 12:13:41 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2020-02-21 19:42:14 +00:00
|
|
|
|
// 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 }
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 12:13:41 +00:00
|
|
|
|
// convenience implementation to compare f32 with a Float.
|
|
|
|
|
impl PartialEq<f32> for UFloat {
|
2020-02-21 19:42:14 +00:00
|
|
|
|
#[inline]
|
2020-02-10 12:13:41 +00:00
|
|
|
|
fn eq(&self, other: &f32) -> bool { &self.0 == other }
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-16 16:09:40 +00:00
|
|
|
|
// 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 Ord for UFloat {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
2020-02-21 19:42:14 +00:00
|
|
|
|
if self.0 < other.0 {
|
2020-02-16 16:09:40 +00:00
|
|
|
|
Ordering::Less
|
2020-02-21 19:42:14 +00:00
|
|
|
|
} else if self == other {
|
2020-02-16 16:09:40 +00:00
|
|
|
|
Ordering::Equal
|
|
|
|
|
} else {
|
|
|
|
|
Ordering::Greater
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 19:42:14 +00:00
|
|
|
|
/// 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 you’re using but instead of the way Hash happens
|
|
|
|
|
/// to be implemented for the type you’re 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>
|
2020-02-16 16:09:40 +00:00
|
|
|
|
#[doc(hidden)]
|
|
|
|
|
impl ::core::hash::Hash for UFloat {
|
|
|
|
|
fn hash<H>(&self, state: &mut H)
|
|
|
|
|
where
|
|
|
|
|
H: ::core::hash::Hasher,
|
|
|
|
|
{
|
2020-02-21 19:42:14 +00:00
|
|
|
|
// 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())
|
2020-02-16 16:09:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 12:13:41 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2020-02-23 17:57:13 +00:00
|
|
|
|
use core::hash::{Hash, Hasher};
|
2020-02-10 12:13:41 +00:00
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_display() {
|
|
|
|
|
assert_eq!(UFloat::new(22.0).to_string(), "22".to_string());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
UFloat::new(3.14159265359).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(3.14159265359),
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-23 17:57:13 +00:00
|
|
|
|
#[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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 12:13:41 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic = "float must be positive: `-1.1`"]
|
|
|
|
|
fn test_new_negative() { UFloat::new(-1.1); }
|
|
|
|
|
|
2020-02-21 19:42:14 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic = "float must be positive: `0`"]
|
|
|
|
|
fn test_new_negative_zero() { UFloat::new(-0.0); }
|
|
|
|
|
|
2020-02-10 12:13:41 +00:00
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic = "float must be finite: `inf`"]
|
|
|
|
|
fn test_new_infinite() { UFloat::new(::core::f32::INFINITY); }
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic = "float must be finite: `-inf`"]
|
|
|
|
|
fn test_new_neg_infinite() { UFloat::new(::core::f32::NEG_INFINITY); }
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic = "float must not be `NaN`"]
|
|
|
|
|
fn test_new_nan() { 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));
|
|
|
|
|
|
2020-02-23 17:57:13 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
UFloat::try_from(-1.1_f32),
|
|
|
|
|
Err(Error::custom("float must be positive: `-1.1`"))
|
|
|
|
|
);
|
2020-02-10 12:13:41 +00:00
|
|
|
|
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());
|
|
|
|
|
}
|
2020-02-16 16:09:40 +00:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_eq() {
|
|
|
|
|
struct _AssertEq
|
|
|
|
|
where
|
|
|
|
|
UFloat: Eq;
|
|
|
|
|
}
|
2020-02-10 12:13:41 +00:00
|
|
|
|
}
|