From 49530534ee97e540582e557aec51cd69821614b9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 17 May 2019 03:47:00 +0900 Subject: [PATCH] Add Format wrapper and format_hexf functions --- Cargo.toml | 4 +- format/Cargo.toml | 20 +++++++ format/src/lib.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 format/Cargo.toml create mode 100644 format/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 68880e2..caa6446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,5 @@ proc-macro = true [dependencies] hexf-parse = { version = "0.2.0", path = "parse/" } -syn = { version = "1.0.41", default-features = false, features = ["parsing", "proc-macro"] } - +hexf-format = { version = "0.2.0", path = "format/" } +syn = { version = "1.0.41", default-features = false, features = ["parsing", "proc-macro"] } \ No newline at end of file diff --git a/format/Cargo.toml b/format/Cargo.toml new file mode 100644 index 0000000..256eee5 --- /dev/null +++ b/format/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "hexf-format" +version = "0.2.0" +authors = ["Jeong YunWon "] + +description = "Format hexadecimal floats (see also hexf)" +homepage = "https://github.com/lifthrasiir/hexf" +documentation = "https://docs.rs/hexf-format/" +repository = "https://github.com/lifthrasiir/hexf" +license = "CC0-1.0" + +edition = "2018" + +[dependencies] +num-traits = "0.2" + +[dev-dependencies] +rand = "0.5" +hexf-parse = { version = "0.2.0", path = "../parse/" } + diff --git a/format/src/lib.rs b/format/src/lib.rs new file mode 100644 index 0000000..8920a58 --- /dev/null +++ b/format/src/lib.rs @@ -0,0 +1,139 @@ +//! Format hexadecimal floats. +//! There are two functions `format_hexf32` and `format_hexf64` provided for each type. +//! +//! ```rust +//! use hexf_format::*; +//! assert_eq!(format!("{:x}", Format(0.1f32)), "0x1.99999ap-4"); +//! assert_eq!(format!("{:x}", Format(0.1f64)), "0x1.999999999999ap-4"); +//! ``` + +mod internal { + use num_traits::{float::Float, Signed, Zero}; + use std::fmt; + + pub trait FormatHexf: Signed + Float + Zero { + fn sign_string(&self) -> &'static str { + if self.is_negative() { + "-" + } else { + "" + } + } + fn fmt_normal(&self, f: &mut fmt::Formatter) -> fmt::Result; + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + v if v.is_zero() => write!(f, "{}0x0.0p+0", v.sign_string()), + v if v.is_infinite() => write!(f, "{}inf", v.sign_string()), + v if v.is_nan() => write!(f, "NaN"), + _ => self.fmt_normal(f), + } + } + } + + impl FormatHexf for f32 { + fn fmt_normal(&self, f: &mut fmt::Formatter) -> fmt::Result { + const BITS: i16 = 23; + const FRACT_MASK: u64 = 0x7f_ffff; + let (mantissa, exponent, _) = self.integer_decode(); + write!( + f, + "{}0x{:x}.{:06x}p{:+}", + self.sign_string(), + mantissa >> BITS, + (mantissa & FRACT_MASK) << 1, + exponent + BITS + ) + } + } + + impl FormatHexf for f64 { + fn fmt_normal(&self, f: &mut fmt::Formatter) -> fmt::Result { + const BITS: i16 = 52; + const FRACT_MASK: u64 = 0xf_ffff_ffff_ffff; + let (mantissa, exponent, _) = self.integer_decode(); + write!( + f, + "{}0x{:x}.{:013x}p{:+}", + self.sign_string(), + mantissa >> BITS, + mantissa & FRACT_MASK, + exponent + BITS + ) + } + } +} + +use std::fmt; + +pub struct Format(pub T); + +impl fmt::LowerHex for Format { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Format an f32 or f64 to hexadecimal. +pub fn to_hex_string(value: F) -> String { + format!("{:x}", Format(value)) +} + +#[test] +fn test_format_normal_hexf32() { + use hexf_parse::parse_hexf32; + use rand::Rng; + + for _ in 0..20000 { + let bytes = rand::thread_rng().gen::<[u32; 1]>(); + let f = f32::from_bits(bytes[0]); + if !f.is_finite() { + continue; + } + + let hex = format!("{:x}", Format(f)); + // println!("{} -> {}", f, hex); + let roundtrip = parse_hexf32(&hex, false).unwrap(); + // println!(" -> {}", roundtrip); + assert_eq!(f, roundtrip, "{} {} {}", f, hex, roundtrip); + } +} + +#[test] +fn test_format_normal_hexf64() { + use hexf_parse::parse_hexf64; + use rand::Rng; + + for _ in 0..20000 { + let bytes = rand::thread_rng().gen::<[u64; 1]>(); + let f = f64::from_bits(bytes[0]); + if !f.is_finite() { + continue; + } + let hex = format!("{:x}", Format(f)); + // println!("{} -> {}", f, hex); + let roundtrip = parse_hexf64(&hex, false).unwrap(); + // println!(" -> {}", roundtrip); + assert_eq!(f, roundtrip, "{} {} {}", f, hex, roundtrip); + } +} + +#[test] +fn test_format_hexf() { + assert_eq!(to_hex_string(0.0f64), "0x0.0p+0"); + assert_eq!(to_hex_string(0.0f32), "0x0.0p+0"); + assert_eq!(to_hex_string(-0.0f64), "-0x0.0p+0"); + assert_eq!(to_hex_string(-0.0f32), "-0x0.0p+0"); + + assert_eq!( + to_hex_string(f64::INFINITY), + "inf".parse::().unwrap().to_string() + ); + assert_eq!( + to_hex_string(-f64::INFINITY), + "-inf".parse::().unwrap().to_string() + ); + assert_eq!( + to_hex_string(f64::NAN), + "NaN".parse::().unwrap().to_string() + ); +}