-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Format wrapper and format_hexf functions
- Loading branch information
1 parent
86feb49
commit 4953053
Showing
3 changed files
with
161 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/" } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
); | ||
} |