Hexadecimal float support for Rust 1.15 or later.
#[macro_use] extern crate hexf; assert_eq!(hexf64!("0x1.999999999999ap-4"), 0.1f64);
The literal is explicitly typed,
and should match to the pattern
SIGN "0x" INTEGRAL "." FRACTIONAL "p" EXPSIGN EXPDIGITS, where:
All Latin letters are matched case-insensitively;
FRACTIONALare one or more hexadecimal digits, optionally separated by or ending with exactly one underscore (
_) (but cannot begin with it);
At least one of
FRACTIONALshould be present (
EXPDIGITSis decimal digits, optionally separated by or beginning or ending with exactly one underscore (
It is a compile-time error to put an invalid literal.
// hexf32! failed: invalid hexadecimal float literal let invalid = hexf32!("42");
It is also a compile-time error to put a literal that would be not exactly representable in the target type.
// hexf32! failed: cannot exactly represent float in target type let inexact = hexf32!("0x1.99999bp-4"); // hexf32! failed: cannot exactly represent float in target type let inexact_subnormal = hexf32!("0x1.8p-149"); // hexf64! failed: cannot exactly represent float in target type let overflow = hexf64!("0x1.0p1024"); // hexf64! failed: cannot exactly represent float in target type let underflow = hexf64!("0x1.0p-1075");
The crate (and also a standalone
hexf-parse crate) provides
which allows parsing failures (reported via a
These functions will allow for interleaved underscores only if the second parameter is true;
this is added for the consistency, because Rust allows for underscores in numeric literals,
but not in the standard library (
"3_4".parse::<i32>() is an error).
How does it work?
This crate heavily relies on the fact that the recent enough Rust compiler can correctly print and read a floating point number. So the actual implementation of this crate is, well, done by printing the parsed hexadecimal float back to the correct decimal digits, which is picked up by the compiler to produce an exact bit pattern.
Wait, then what's the point of hexadecimal floats? The answer is that they are "invented" by ISO C99 to avoid implementation pitfalls. Ideally it should be possible to enumerate enough fractional digits to get the correctly rounded bit pattern, but many implementations didn't (quite understandably, because it is actually quite hard). So the Standard has made a compromise: in the conforming implementation decimal floats should parse to very close to, but not exactly, the correctly rounded number:
The significand part is interpreted as a (decimal or hexadecimal) rational number; the digit sequence in the exponent part is interpreted as a decimal integer. [...] For decimal floating constants, and also for hexadecimal floating constants when FLT_RADIX is not a power of 2, the result is either the nearest representable value, or the larger or smaller representable value immediately adjacent to the nearest representable value, chosen in an implementation-defined manner. [...]
—ISO C99, Section 18.104.22.168 Floating constants, Paragraph 3 (emphases mine)
Indeed, it is relatively easier to parse decimal floats in that accuracy. Hexadecimal floats are born out of this legacy, but Rust doesn't have to! Hexadecimal floats can be still useful for manually writing float bits down, or for converting from other languages, however. This crate exists for those rarer use cases.
See rust-lang/rust#1433 for the more context.