Skip to content

Commit

Permalink
Proper README, updated docs, added .travis.yml.
Browse files Browse the repository at this point in the history
  • Loading branch information
lifthrasiir committed Apr 15, 2017
1 parent 7ebe2ce commit aca384a
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 14 deletions.
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: rust
sudo: false
rust:
- 1.15.0
- stable
- beta
- nightly
os:
- linux
- osx
matrix:
allow_failures:
- rust: nightly
env:
global:
- LD_LIBRARY_PATH: /usr/local/lib
script:
- cargo test -p hexf-parse -v
- cargo test -v
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Kang Seonghoon <[email protected]>"]

description = "Hexadecimal float support for Rust"
homepage = "https://github.com/lifthrasiir/hexf"
#documentation = "https://docs.rs/hexf/"
documentation = "https://docs.rs/hexf/"
repository = "https://github.com/lifthrasiir/hexf"
readme = "README.md"
license = "CC0-1.0"
Expand All @@ -14,7 +14,7 @@ license = "CC0-1.0"
members = ["parse/", "impl/"]

[dependencies]
proc-macro-hack = "0.3"
hexf-parse = { path = "parse/" }
hexf-impl = { path = "impl/" }
proc-macro-hack = "0.3.3"
hexf-parse = { version = "0.1.0", path = "parse/" }
hexf-impl = { version = "0.1.0", path = "impl/" }

98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# hexf

[![Chrono on Travis CI][travis-image]][travis]
[![Chrono on crates.io][cratesio-image]][cratesio]
[![Chrono on docs.rs][docsrs-image]][docsrs]

[travis-image]: https://travis-ci.org/lifthrasiir/hexf.svg?branch=master
[travis]: https://travis-ci.org/lifthrasiir/hexf
[cratesio-image]: https://img.shields.io/crates/v/hexf.svg
[cratesio]: https://crates.io/crates/hexf
[docsrs-image]: https://docs.rs/hexf/badge.svg
[docsrs]: https://docs.rs/hexf/

Hexadecimal float support for Rust 1.15 or later.

```rust
Expand All @@ -8,5 +19,90 @@ Hexadecimal float support for Rust 1.15 or later.
assert_eq!(hexf64!("0x1.999999999999ap-4"), 0.1f64);
```

See [rust-lang/rust#1433](https://github.com/rust-lang/rust/issues/1433#issuecomment-288184018) for the context.
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;

* `SIGN` and `EXPSIGN` are either `+`, `-` or empty;

* `INTEGRAL` and `FRACTIONAL` are one or more hexadecimal digits,
optionally separated by or ending with exactly one underscore (`_`) (but cannot begin with it);

* At least one of `INTEGRAL` or `FRACTIONAL` should be present
(`1.0` or `.0` or `1.` is allowed, `1` is not);

* `EXPDIGITS` is decimal digits,
optionally separated by or beginning or ending with exactly one underscore (`_`).

It is a compile-time error to put an invalid literal.

```rust,ignore
// 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.

```rust,ignore
// 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
`parse_hexf32` and `parse_hexf64` functions,
which allows parsing failures (reported via a `ParseHexfError` type).
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][dec2flt-paper]).
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 6.4.4.2 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][issue-1433] for the more context.

[dec2flt-paper]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.4152
[issue-1433]: https://github.com/rust-lang/rust/issues/1433#issuecomment-288184018

6 changes: 3 additions & 3 deletions impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ authors = ["Kang Seonghoon <[email protected]>"]

description = "Hexadecimal float support for Rust (auxiliary crate; see also hexf)"
homepage = "https://github.com/lifthrasiir/hexf"
#documentation = "https://docs.rs/hexf/"
documentation = "https://docs.rs/hexf/"
repository = "https://github.com/lifthrasiir/hexf"
license = "CC0-1.0"

[lib]
proc-macro = true

[dependencies]
proc-macro-hack = "0.3"
proc-macro-hack = "0.3.3"
syn = "0.11"
hexf-parse = { path = "../parse/" }
hexf-parse = { version = "0.1.0", path = "../parse/" }

6 changes: 6 additions & 0 deletions impl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
//! Support library for `hexf`. Do not use directly.

#[macro_use] extern crate proc_macro_hack;
extern crate syn;
extern crate hexf_parse;

proc_macro_expr_impl! {
/// Support function for `hexf32!` macro. Do not use directly.
#[doc(hidden)]
pub fn hexf32_impl(input: &str) -> String {
let lit = syn::parse::string(input).expect("hexf32! requires a single string literal");
match hexf_parse::parse_hexf32(&lit.value, true) {
Expand All @@ -13,6 +17,8 @@ proc_macro_expr_impl! {
}

proc_macro_expr_impl! {
/// Support function for `hexf64!` macro. Do not use directly.
#[doc(hidden)]
pub fn hexf64_impl(input: &str) -> String {
let lit = syn::parse::string(input).expect("hexf64! requires a single string literal");
match hexf_parse::parse_hexf64(&lit.value, true) {
Expand Down
4 changes: 2 additions & 2 deletions parse/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name = "hexf-parse"
version = "0.1.0"
authors = ["Kang Seonghoon <[email protected]>"]

description = "Hexadecimal float support for Rust (auxiliary crate; see also hexf)"
description = "Parses hexadecimal floats (see also hexf)"
homepage = "https://github.com/lifthrasiir/hexf"
#documentation = "https://docs.rs/hexf/"
documentation = "https://docs.rs/hexf-parse/"
repository = "https://github.com/lifthrasiir/hexf"
license = "CC0-1.0"

24 changes: 24 additions & 0 deletions parse/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
//! Parses hexadecimal float literals.
//! There are two functions `parse_hexf32` and `parse_hexf64` provided for each type.
//!
//! ```rust
//! use hexf_parse::*;
//! assert_eq!(parse_hexf32("0x1.99999ap-4", false), Ok(0.1f32));
//! assert_eq!(parse_hexf64("0x1.999999999999ap-4", false), Ok(0.1f64));
//! ```
//!
//! An additional `bool` parameter can be set to true if you want to allow underscores.
//!
//! ```rust
//! use hexf_parse::*;
//! assert!(parse_hexf64("0x0.1_7p8", false).is_err());
//! assert_eq!(parse_hexf64("0x0.1_7p8", true), Ok(23.0f64));
//! ```
//!
//! The error is reported via an opaque `ParseHexfError` type.

use std::{fmt, str, f32, f64, isize};

/// An opaque error type from `parse_hexf32` and `parse_hexf64`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseHexfError {
kind: ParseHexfErrorKind,
Expand Down Expand Up @@ -361,11 +381,15 @@ fn test_convert_hexf64() {
assert_eq!(convert_hexf64(false, 0xffff_ffff_ffff_fc00, 960), Err(INEXACT));
}

/// Tries to parse a hexadecimal float literal to `f32`.
/// The underscore is allowed only when `allow_underscore` is true.
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)
}

/// Tries to parse a hexadecimal float literal to `f64`.
/// The underscore is allowed only when `allow_underscore` is true.
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)
Expand Down
40 changes: 37 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
//! Hexadecimal float support for Rust 1.15 or later.
//!
//! ```rust
//! #[macro_use] extern crate hexf;
//!
//! # fn main() {
//! assert_eq!(hexf32!("0x1.99999ap-4"), 0.1f32);
//! assert_eq!(hexf64!("0x1.999999999999ap-4"), 0.1f64);
//! # }
//! ```

#[macro_use] extern crate proc_macro_hack;
#[macro_use] #[allow(unused_imports)] extern crate hexf_impl;
extern crate hexf_parse;

pub use hexf_parse::{ParseHexfError, parse_hexf32, parse_hexf64};
pub use hexf_impl::*;
#[doc(hidden)] pub use hexf_impl::*;

proc_macro_expr_decl! {
/// Expands to a `f32` value with given hexadecimal representation.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate hexf; fn main() {
/// assert_eq!(hexf32!("0x1.99999ap-4"), 0.1f32);
/// # }
/// ```
hexf32! => hexf32_impl
}

proc_macro_expr_decl!(hexf32! => hexf32_impl);
proc_macro_expr_decl!(hexf64! => hexf64_impl);
proc_macro_expr_decl! {
/// Expands to a `f64` value with given hexadecimal representation.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate hexf; fn main() {
/// assert_eq!(hexf64!("0x1.999999999999ap-4"), 0.1f64);
/// # }
/// ```
hexf64! => hexf64_impl
}

9 changes: 8 additions & 1 deletion tests/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
use std::f64;

#[test]
fn test() {
fn basic() {
assert_eq!(hexf32!("0x1.99999ap-4"), 0.1f32);
assert_eq!(hexf64!("0x1.999999999999ap-4"), 0.1f64);
assert_eq!(hexf64!("0x1.999999999998ap-4"), 0.1f64 - f64::EPSILON);
}

#[test]
fn zeroes() {
assert_eq!(1.0f64 / hexf64!("0x0.0p0"), f64::INFINITY);
assert_eq!(1.0f64 / hexf64!("-0x0.0p0"), f64::NEG_INFINITY);
}

0 comments on commit aca384a

Please sign in to comment.