use compact_str::{format_compact,CompactString};
use crate::num::constants::{NAN,INFINITY};
use crate::macros::{
return_bad_float,str_u64,str_i64,
impl_common,impl_const,
impl_usize,impl_isize,
impl_math,impl_traits,
impl_impl_math,
};
use crate::str::Str;
#[allow(unused_imports)]
use crate::num::{Int,Unsigned};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Float(f64, Str<{ Float::MAX_LEN }>);
const LEN: usize = 22;
impl_math!(Float, f64);
impl_traits!(Float, f64);
impl Float {
pub const ZERO: Self = Self(0.0, Str::from_static_str("0.000"));
pub const NAN: Self = Self(f64::NAN, Str::from_static_str(NAN));
pub const INFINITY: Self = Self(f64::INFINITY, Str::from_static_str(INFINITY));
pub const UNKNOWN: Self = Self(0.0, Str::from_static_str("?.???"));
pub const MAX_LEN: usize = LEN;
}
macro_rules! impl_new {
( $num:tt ) => {
paste::item! {
#[doc = "Same as [`Float::from`] but with `" $num "` floating point."]
#[must_use]
pub fn [<from_ $num>](f: f64) -> Self {
return_bad_float!(f, Self::NAN, Self::INFINITY);
let fract = &format_compact!(concat!("{:.", $num, "}"), f.fract())[2..];
let string = format_compact!("{}.{}", str_u64!(f as u64), fract);
if string.len() > Self::MAX_LEN {
Self::UNKNOWN
} else {
let mut s = Str::new();
s.push_str_panic(string);
Self(f, s)
}
}
}
}
}
impl Float {
impl_common!(f64);
impl_const!();
impl_usize!();
impl_isize!();
#[inline]
#[must_use]
pub fn is_nan(&self) -> bool {
self.0.is_nan()
}
#[inline]
#[must_use]
pub fn is_infinite(&self) -> bool {
self.0.is_infinite()
}
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self.as_str().as_bytes(), b"?.???")
}
#[inline]
#[must_use]
pub fn from_0(f: f64) -> Self {
return_bad_float!(f, Self::NAN, Self::INFINITY);
let string = crate::num::Unsigned::from_priv_inner(f as u64);
if string.len() > Self::MAX_LEN {
Self::UNKNOWN
} else {
let mut s = Str::new();
s.push_str_panic(string);
Self(f, s)
}
}
seq_macro::seq!(N in 1..=14 {
impl_new!(N);
});
}
macro_rules! impl_u {
($( $number:ty ),*) => {
$(
impl From<$number> for Float {
#[inline]
fn from(number: $number) -> Self {
let string = format_compact!("{}.000", str_u64!(number as u64));
if string.len() > Self::MAX_LEN {
Self::UNKNOWN
} else {
let mut s = Str::new();
s.push_str_panic(string);
Self(number as f64, s)
}
}
}
)*
}
}
impl_u!(u8,u16,u32,u64,usize);
macro_rules! impl_i {
($($number:ty),*) => {
$(
impl From<$number> for Float {
#[inline]
fn from(number: $number) -> Self {
let string = format_compact!("{}.000", str_i64!(number as i64));
if string.len() > Self::MAX_LEN {
Self::UNKNOWN
} else {
let mut s = Str::new();
s.push_str_panic(string);
Self(number as f64, s)
}
}
}
)*
}
}
impl_i!(i8,i16,i32,i64,isize);
impl From<f32> for Float {
#[inline]
fn from(f: f32) -> Self {
return_bad_float!(f, Self::NAN, Self::INFINITY);
#[allow(clippy::cast_lossless)]
Self::from(f as f64)
}
}
impl From<f64> for Float {
#[inline]
fn from(f: f64) -> Self {
return_bad_float!(f, Self::NAN, Self::INFINITY);
let fract = &format_compact!("{:.3}", f.fract())[2..];
let string = format_compact!("{}.{}", str_u64!(f as u64), fract);
if string.len() > Self::MAX_LEN {
Self::UNKNOWN
} else {
let mut s = Str::new();
s.push_str_panic(string);
Self(f, s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn special() {
assert_eq!(Float::from(0.0), "0.000");
assert_eq!(Float::ZERO, "0.000");
assert_eq!(Float::NAN, NAN);
assert_eq!(Float::INFINITY, INFINITY);
assert_eq!(Float::from(f64::NAN), NAN);
assert_eq!(Float::from(f64::INFINITY), INFINITY);
assert_eq!(Float::from(f64::NEG_INFINITY), INFINITY);
assert_eq!(Float::from(f32::NAN), NAN);
assert_eq!(Float::from(f32::INFINITY), INFINITY);
assert_eq!(Float::from(f32::NEG_INFINITY), INFINITY);
}
#[test]
fn float() {
assert_eq!(Float::from_0(0.1), "0");
assert_eq!(Float::from_1(0.1), "0.1");
assert_eq!(Float::from_2(0.01), "0.01");
assert_eq!(Float::from(0.001), "0.001");
assert_eq!(Float::from_4(0.0001), "0.0001");
assert_eq!(Float::from_5(0.00001), "0.00001");
assert_eq!(Float::from_6(0.000001), "0.000001");
assert_eq!(Float::from_7(0.0000001), "0.0000001");
assert_eq!(Float::from_8(0.00000001), "0.00000001");
assert_eq!(Float::from_9(0.000000001), "0.000000001");
assert_eq!(Float::from_10(0.0000000001), "0.0000000001");
assert_eq!(Float::from_11(0.00000000001), "0.00000000001");
assert_eq!(Float::from_12(0.000000000001), "0.000000000001");
assert_eq!(Float::from_13(0.0000000000001), "0.0000000000001");
assert_eq!(Float::from_14(0.00000000000001), "0.00000000000001");
}
#[test]
#[cfg(feature = "serde")]
fn serde() {
let this: Float = Float::from(1.0);
let json = serde_json::to_string(&this).unwrap();
assert_eq!(json, r#"[1.0,"1.000"]"#);
let this: Float = serde_json::from_str(&json).unwrap();
assert_eq!(this, 1.0);
assert_eq!(this, "1.000");
assert!(serde_json::from_str::<Float>(&"---").is_err());
let json = serde_json::to_string(&Float::UNKNOWN).unwrap();
assert_eq!(json, r#"[0.0,"?.???"]"#);
assert!(serde_json::from_str::<Float>(&json).unwrap().is_unknown());
}
#[test]
#[cfg(feature = "bincode")]
fn bincode() {
let this: Float = Float::from(1.0);
let config = bincode::config::standard();
let bytes = bincode::encode_to_vec(&this, config).unwrap();
let this: Float = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert_eq!(this, 1.0);
assert_eq!(this, "1.000");
let bytes = bincode::encode_to_vec(&Float::UNKNOWN, config).unwrap();
let this: Float = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert!(this.is_unknown());
}
#[test]
#[cfg(feature = "borsh")]
fn borsh() {
let this: Float = Float::from(1.0);
let bytes = borsh::to_vec(&this).unwrap();
let this: Float = borsh::from_slice(&bytes).unwrap();
assert_eq!(this, 1.0);
assert_eq!(this, "1.000");
assert!(borsh::from_slice::<Float>(b"bad .-;[]124/ bytes").is_err());
let bytes = borsh::to_vec(&Float::UNKNOWN).unwrap();
let this: Float = borsh::from_slice(&bytes).unwrap();
assert!(this.is_unknown());
}
}