use std::{f32, f64, fmt, isize, str};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseHexfError {
kind: ParseHexfErrorKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ParseHexfErrorKind {
Empty,
Invalid,
Inexact,
}
const EMPTY: ParseHexfError = ParseHexfError {
kind: ParseHexfErrorKind::Empty,
};
const INVALID: ParseHexfError = ParseHexfError {
kind: ParseHexfErrorKind::Invalid,
};
const INEXACT: ParseHexfError = ParseHexfError {
kind: ParseHexfErrorKind::Inexact,
};
impl ParseHexfError {
fn text(&self) -> &'static str {
match self.kind {
ParseHexfErrorKind::Empty => "cannot parse float from empty string",
ParseHexfErrorKind::Invalid => "invalid hexadecimal float literal",
ParseHexfErrorKind::Inexact => "cannot exactly represent float in target type",
}
}
}
impl fmt::Display for ParseHexfError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.text(), f)
}
}
impl std::error::Error for ParseHexfError {
fn description(&self) -> &'static str {
self.text()
}
}
fn parse(s: &[u8], allow_underscore: bool) -> Result<(bool, u64, isize), ParseHexfError> {
let (s, negative) = match s.split_first() {
Some((&b'+', s)) => (s, false),
Some((&b'-', s)) => (s, true),
Some(_) => (s, false),
None => return Err(EMPTY),
};
if !(s.starts_with(b"0x") || s.starts_with(b"0X")) {
return Err(INVALID);
}
let mut s = &s[2..];
let mut acc = 0; let mut digit_seen = false;
loop {
let (s_, digit) = match s.split_first() {
Some((&c @ b'0'..=b'9', s)) => (s, c - b'0'),
Some((&c @ b'a'..=b'f', s)) => (s, c - b'a' + 10),
Some((&c @ b'A'..=b'F', s)) => (s, c - b'A' + 10),
Some((&b'_', s_)) if allow_underscore && digit_seen => {
s = s_;
continue;
}
_ => break,
};
s = s_;
digit_seen = true;
if acc >> 60 != 0 {
return Err(INEXACT);
}
acc = acc << 4 | digit as u64;
}
let mut nfracs = 0isize; let mut nzeroes = 0isize;
let mut frac_digit_seen = false;
if s.starts_with(b".") {
s = &s[1..];
loop {
let (s_, digit) = match s.split_first() {
Some((&c @ b'0'..=b'9', s)) => (s, c - b'0'),
Some((&c @ b'a'..=b'f', s)) => (s, c - b'a' + 10),
Some((&c @ b'A'..=b'F', s)) => (s, c - b'A' + 10),
Some((&b'_', s_)) if allow_underscore && frac_digit_seen => {
s = s_;
continue;
}
_ => break,
};
s = s_;
frac_digit_seen = true;
if digit == 0 {
nzeroes = nzeroes.checked_add(1).ok_or(INEXACT)?;
} else {
let nnewdigits = nzeroes.checked_add(1).ok_or(INEXACT)?;
nfracs = nfracs.checked_add(nnewdigits).ok_or(INEXACT)?;
nzeroes = 0;
if acc != 0 {
if nnewdigits >= 16 || acc >> (64 - nnewdigits * 4) != 0 {
return Err(INEXACT);
}
acc = acc << (nnewdigits * 4);
}
acc |= digit as u64;
}
}
}
if !(digit_seen || frac_digit_seen) {
return Err(INVALID);
}
let s = match s.split_first() {
Some((&b'P', s)) | Some((&b'p', s)) => s,
_ => return Err(INVALID),
};
let (mut s, negative_exponent) = match s.split_first() {
Some((&b'+', s)) => (s, false),
Some((&b'-', s)) => (s, true),
Some(_) => (s, false),
None => return Err(INVALID),
};
let mut digit_seen = false;
let mut exponent = 0isize; loop {
let (s_, digit) = match s.split_first() {
Some((&c @ b'0'..=b'9', s)) => (s, c - b'0'),
Some((&b'_', s_)) if allow_underscore => {
s = s_;
continue;
}
None if digit_seen => break,
_ => return Err(INVALID),
};
s = s_;
digit_seen = true;
if acc != 0 {
exponent = exponent
.checked_mul(10)
.and_then(|v| v.checked_add(digit as isize))
.ok_or(INEXACT)?;
}
}
if negative_exponent {
exponent = -exponent;
}
if acc == 0 {
Ok((negative, 0, 0))
} else {
let exponent = nfracs
.checked_mul(4)
.and_then(|v| exponent.checked_sub(v))
.ok_or(INEXACT)?;
Ok((negative, acc, exponent))
}
}
#[test]
fn test_parse() {
assert_eq!(parse(b"", false), Err(EMPTY));
assert_eq!(parse(b" ", false), Err(INVALID));
assert_eq!(parse(b"3.14", false), Err(INVALID));
assert_eq!(parse(b"0x3.14", false), Err(INVALID));
assert_eq!(parse(b"0x3.14fp+3", false), Ok((false, 0x314f, 3 - 12)));
assert_eq!(parse(b" 0x3.14p+3", false), Err(INVALID));
assert_eq!(parse(b"0x3.14p+3 ", false), Err(INVALID));
assert_eq!(parse(b"+0x3.14fp+3", false), Ok((false, 0x314f, 3 - 12)));
assert_eq!(parse(b"-0x3.14fp+3", false), Ok((true, 0x314f, 3 - 12)));
assert_eq!(parse(b"0xAbC.p1", false), Ok((false, 0xabc, 1)));
assert_eq!(parse(b"0x0.7p1", false), Ok((false, 0x7, 1 - 4)));
assert_eq!(parse(b"0x.dEfP-1", false), Ok((false, 0xdef, -1 - 12)));
assert_eq!(parse(b"0x.p1", false), Err(INVALID));
assert_eq!(parse(b"0x.P1", false), Err(INVALID));
assert_eq!(parse(b"0xp1", false), Err(INVALID));
assert_eq!(parse(b"0xP1", false), Err(INVALID));
assert_eq!(parse(b"0x0p", false), Err(INVALID));
assert_eq!(parse(b"0xp", false), Err(INVALID));
assert_eq!(parse(b"0x.p", false), Err(INVALID));
assert_eq!(parse(b"0x0p1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0P1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.p1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.P1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.0p1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.0P1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x.0p1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x.0P1", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0p0", false), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.p999999999", false), Ok((false, 0, 0)));
assert_eq!(
parse(b"0x0.p99999999999999999999999999999", false),
Ok((false, 0, 0))
);
assert_eq!(
parse(b"0x0.p-99999999999999999999999999999", false),
Ok((false, 0, 0))
);
assert_eq!(
parse(b"0x1.p99999999999999999999999999999", false),
Err(INEXACT)
);
assert_eq!(
parse(b"0x1.p-99999999999999999999999999999", false),
Err(INEXACT)
);
assert_eq!(
parse(b"0x4.00000000000000000000p55", false),
Ok((false, 4, 55))
);
assert_eq!(
parse(b"0x4.00001000000000000000p55", false),
Ok((false, 0x400001, 55 - 20))
);
assert_eq!(parse(b"0x4.00000000000000000001p55", false), Err(INEXACT));
assert_eq!(
parse(b"-0x3____.1_4___p+___5___", true),
Ok((true, 0x314, 5 - 8))
);
assert_eq!(parse(b"-_0x3.14p+5", true), Err(INVALID));
assert_eq!(parse(b"_0x3.14p+5", true), Err(INVALID));
assert_eq!(parse(b"0x_3.14p+5", true), Err(INVALID));
assert_eq!(parse(b"0x3._14p+5", true), Err(INVALID));
assert_eq!(parse(b"0x3.14p_+5", true), Err(INVALID));
assert_eq!(parse(b"-0x____.1_4___p+___5___", true), Err(INVALID));
assert_eq!(parse(b"-0x3____.____p+___5___", true), Err(INVALID));
assert_eq!(parse(b"-0x3____.1_4___p+______", true), Err(INVALID));
assert_eq!(parse(b"0x_p0", false), Err(INVALID));
assert_eq!(parse(b"0x_0p0", true), Err(INVALID));
assert_eq!(parse(b"0x_p0", true), Err(INVALID));
assert_eq!(parse(b"0x._p0", true), Err(INVALID));
assert_eq!(parse(b"0x._0p0", true), Err(INVALID));
assert_eq!(parse(b"0x0._0p0", true), Err(INVALID));
assert_eq!(parse(b"0x0_p0", true), Ok((false, 0, 0)));
assert_eq!(parse(b"0x.0_p0", true), Ok((false, 0, 0)));
assert_eq!(parse(b"0x0.0_p0", true), Ok((false, 0, 0)));
assert_eq!(parse(b"0x1p-149", false), parse(b"0x1.0p-149", false));
}
macro_rules! define_convert {
($name:ident => $f:ident) => {
fn $name(negative: bool, mantissa: u64, exponent: isize) -> Result<$f, ParseHexfError> {
if exponent < -0xffff || exponent > 0xffff {
return Err(INEXACT);
}
let trailing = mantissa.trailing_zeros() & 63; let mantissa = mantissa >> trailing;
let exponent = exponent + trailing as isize;
let leading = mantissa.leading_zeros();
let normalexp = exponent + (63 - leading as isize);
let mantissasize = if normalexp < $f::MIN_EXP as isize - $f::MANTISSA_DIGITS as isize {
return Err(INEXACT);
} else if normalexp < ($f::MIN_EXP - 1) as isize {
$f::MANTISSA_DIGITS as isize - $f::MIN_EXP as isize + normalexp + 1
} else if normalexp < $f::MAX_EXP as isize {
$f::MANTISSA_DIGITS as isize
} else {
return Err(INEXACT);
};
if mantissa >> mantissasize == 0 {
let mut mantissa = mantissa as $f;
if negative {
mantissa = -mantissa;
}
Ok(mantissa * (2.0 as $f).powf(exponent as $f))
} else {
Err(INEXACT)
}
}
};
}
define_convert!(convert_hexf32 => f32);
define_convert!(convert_hexf64 => f64);
#[test]
fn test_convert_hexf32() {
assert_eq!(convert_hexf32(false, 0, 0), Ok(0.0));
assert_eq!(convert_hexf32(false, 1, 0), Ok(1.0));
assert_eq!(convert_hexf32(false, 10, 0), Ok(10.0));
assert_eq!(convert_hexf32(false, 10, 1), Ok(20.0));
assert_eq!(convert_hexf32(false, 10, -1), Ok(5.0));
assert_eq!(convert_hexf32(true, 0, 0), Ok(-0.0));
assert_eq!(convert_hexf32(true, 1, 0), Ok(-1.0));
assert_eq!(convert_hexf32(false, 0, 0).unwrap().signum(), 1.0);
assert_eq!(convert_hexf32(true, 0, 0).unwrap().signum(), -1.0);
assert_eq!(
convert_hexf32(false, 0x0000_0000_00ff_ffff, 0),
Ok(16777215.0)
);
assert_eq!(
convert_hexf32(false, 0x0000_0000_01ff_ffff, 0),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0xffff_ff00_0000_0000, -40),
Ok(16777215.0)
);
assert_eq!(
convert_hexf32(false, 0xffff_ff80_0000_0000, -40),
Err(INEXACT)
);
assert!(convert_hexf32(false, 0x0000_0000_007f_ffff, -149).is_ok());
assert!(convert_hexf32(false, 0x0000_0000_00ff_ffff, -150).is_err());
assert!(convert_hexf32(false, 0x0000_0000_00ff_fffe, -150).is_ok());
assert!(convert_hexf32(false, 0xffff_ff00_0000_0000, -190).is_err());
assert!(convert_hexf32(false, 0xffff_fe00_0000_0000, -190).is_ok());
assert!(convert_hexf32(false, 0x0000_0000_0000_0001, -149).is_ok());
assert!(convert_hexf32(false, 0x0000_0000_0000_0001, -150).is_err());
assert!(convert_hexf32(false, 0x0000_0000_0000_0002, -150).is_ok());
assert!(convert_hexf32(false, 0x0000_0000_0000_0002, -151).is_err());
assert!(convert_hexf32(false, 0x0000_0000_0000_0003, -150).is_err());
assert!(convert_hexf32(false, 0x0000_0000_0000_0003, -151).is_err());
assert!(convert_hexf32(false, 0x8000_0000_0000_0000, -212).is_ok());
assert!(convert_hexf32(false, 0x8000_0000_0000_0000, -213).is_err());
assert_eq!(
convert_hexf32(false, 0x0000_0000_00ff_ffff, 104),
Ok(f32::MAX)
);
assert_eq!(
convert_hexf32(false, 0x0000_0000_01ff_ffff, 104),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0x0000_0000_01ff_fffe, 104),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0x0000_0000_0000_0001, 128),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0x8000_0000_0000_0000, 65),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0xffff_ff00_0000_0000, 64),
Ok(f32::MAX)
);
assert_eq!(
convert_hexf32(false, 0xffff_ff80_0000_0000, 64),
Err(INEXACT)
);
}
#[test]
fn test_convert_hexf64() {
assert_eq!(convert_hexf64(false, 0, 0), Ok(0.0));
assert_eq!(convert_hexf64(false, 1, 0), Ok(1.0));
assert_eq!(convert_hexf64(false, 10, 0), Ok(10.0));
assert_eq!(convert_hexf64(false, 10, 1), Ok(20.0));
assert_eq!(convert_hexf64(false, 10, -1), Ok(5.0));
assert_eq!(convert_hexf64(true, 0, 0), Ok(-0.0));
assert_eq!(convert_hexf64(true, 1, 0), Ok(-1.0));
assert_eq!(convert_hexf64(false, 0, 0).unwrap().signum(), 1.0);
assert_eq!(convert_hexf64(true, 0, 0).unwrap().signum(), -1.0);
assert_eq!(
convert_hexf64(false, 0x001f_ffff_ffff_ffff, 0),
Ok(9007199254740991.0)
);
assert_eq!(
convert_hexf64(false, 0x003f_ffff_ffff_ffff, 0),
Err(INEXACT)
);
assert_eq!(
convert_hexf64(false, 0xffff_ffff_ffff_f800, -11),
Ok(9007199254740991.0)
);
assert_eq!(
convert_hexf64(false, 0xffff_ffff_ffff_fc00, -11),
Err(INEXACT)
);
assert!(convert_hexf64(false, 0x000f_ffff_ffff_ffff, -1074).is_ok());
assert!(convert_hexf64(false, 0x001f_ffff_ffff_ffff, -1075).is_err());
assert!(convert_hexf64(false, 0x001f_ffff_ffff_fffe, -1075).is_ok());
assert!(convert_hexf64(false, 0xffff_ffff_ffff_f800, -1086).is_err());
assert!(convert_hexf64(false, 0xffff_ffff_ffff_f000, -1086).is_ok());
assert!(convert_hexf64(false, 0x0000_0000_0000_0001, -1074).is_ok());
assert!(convert_hexf64(false, 0x0000_0000_0000_0001, -1075).is_err());
assert!(convert_hexf64(false, 0x0000_0000_0000_0002, -1075).is_ok());
assert!(convert_hexf64(false, 0x0000_0000_0000_0002, -1076).is_err());
assert!(convert_hexf64(false, 0x0000_0000_0000_0003, -1075).is_err());
assert!(convert_hexf64(false, 0x0000_0000_0000_0003, -1076).is_err());
assert!(convert_hexf64(false, 0x8000_0000_0000_0000, -1137).is_ok());
assert!(convert_hexf64(false, 0x8000_0000_0000_0000, -1138).is_err());
assert_eq!(
convert_hexf64(false, 0x001f_ffff_ffff_ffff, 971),
Ok(f64::MAX)
);
assert_eq!(
convert_hexf64(false, 0x003f_ffff_ffff_ffff, 971),
Err(INEXACT)
);
assert_eq!(
convert_hexf64(false, 0x003f_ffff_ffff_fffe, 971),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0x0000_0000_0000_0001, 1024),
Err(INEXACT)
);
assert_eq!(
convert_hexf32(false, 0x8000_0000_0000_0000, 961),
Err(INEXACT)
);
assert_eq!(
convert_hexf64(false, 0xffff_ffff_ffff_f800, 960),
Ok(f64::MAX)
);
assert_eq!(
convert_hexf64(false, 0xffff_ffff_ffff_fc00, 960),
Err(INEXACT)
);
}
pub fn parse_hexf32(s: &str, allow_underscore: bool) -> Result<f32, ParseHexfError> {
let (negative, mantissa, exponent) = parse(s.as_bytes(), allow_underscore)?;
convert_hexf32(negative, mantissa, exponent)
}
pub fn parse_hexf64(s: &str, allow_underscore: bool) -> Result<f64, ParseHexfError> {
let (negative, mantissa, exponent) = parse(s.as_bytes(), allow_underscore)?;
convert_hexf64(negative, mantissa, exponent)
}
#[test]
fn test_parse_hexf() {
assert!(parse_hexf64("0x.000000000000000000102", false).is_err());
}