Skip to content

Commit

Permalink
Add Format wrapper and format_hexf functions
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowone committed Mar 28, 2021
1 parent 86feb49 commit 4953053
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
20 changes: 20 additions & 0 deletions format/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "hexf-format"
version = "0.2.0"
authors = ["Jeong YunWon <[email protected]>"]

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/" }

139 changes: 139 additions & 0 deletions format/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T: internal::FormatHexf>(pub T);

impl<F: internal::FormatHexf> fmt::LowerHex for Format<F> {
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<F: internal::FormatHexf>(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::<f64>().unwrap().to_string()
);
assert_eq!(
to_hex_string(-f64::INFINITY),
"-inf".parse::<f64>().unwrap().to_string()
);
assert_eq!(
to_hex_string(f64::NAN),
"NaN".parse::<f64>().unwrap().to_string()
);
}

0 comments on commit 4953053

Please sign in to comment.