Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [1.37.0, stable, nightly]
rust: [1.56.0, stable, nightly]
steps:
- uses: actions/checkout@v2
with:
Expand All @@ -23,6 +23,23 @@ jobs:
- run: cargo test
- run: cd extras/data-tests && cargo run --release

msrv:
name: Rust ${{matrix.rust}}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust: [1.37.0]
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
- run: cargo check
- run: cargo build

cross:
name: Rust ${{matrix.target}}
runs-on: ubuntu-latest
Expand Down
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "fast-float"
version = "0.2.0"
authors = ["Ivan Smirnov <[email protected]>"]
repository = "https://github.com/aldanor/fast-float-rust"
documentation = "https://docs.rs/fast-float"
name = "fast-float2"
version = "0.2.1"
authors = ["Ivan Smirnov <[email protected]>", "Alex Huszagh <[email protected]>"]
repository = "https://github.com/Alexhuszagh/fast-float-rust"
documentation = "https://docs.rs/fast-float2"
description = "Fast floating-point number parser."
keywords = ["parser", "parsing", "parse", "float", "no-std"]
categories = ["parser-implementations", "parsing", "text-processing", "algorithms", "no-std"]
Expand All @@ -12,6 +12,8 @@ license = "MIT OR Apache-2.0"
autobenches = false
edition = "2018"
exclude = ["benches/*", "extras/*"]
# FIXME: rust-version is not supported until 1.56.0.
rust-version = "1.37"

[features]
default = ["std"]
Expand Down
41 changes: 22 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
fast-float
==========
fast-float2
===========

[![Build](https://github.com/aldanor/fast-float-rust/workflows/CI/badge.svg)](https://github.com/aldanor/fast-float-rust/actions?query=branch%3Amaster)
[![Latest Version](https://img.shields.io/crates/v/fast-float.svg)](https://crates.io/crates/fast-float)
[![Documentation](https://docs.rs/fast-float/badge.svg)](https://docs.rs/fast-float)
[![Build](https://github.com/Alexhuszagh/fast-float-rust/workflows/CI/badge.svg)](https://github.com/Alexhuszagh/fast-float-rust/actions?query=branch%3Amaster)
[![Latest Version](https://img.shields.io/crates/v/fast-float2.svg)](https://crates.io/crates/fast-float2)
[![Documentation](https://docs.rs/fast-float2/badge.svg)](https://docs.rs/fast-float2)
[![Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Rustc 1.37+](https://img.shields.io/badge/rustc-1.37+-lightgray.svg)](https://blog.rust-lang.org/2019/08/15/Rust-1.37.0.html)
Expand All @@ -12,7 +12,7 @@ This crate provides a super-fast decimal number parser from strings into floats.

```toml
[dependencies]
fast-float = "0.2"
fast-float2 = "0.2.1"
```

There are no dependencies and the crate can be used in a no_std context by disabling the "std" feature.
Expand All @@ -21,10 +21,10 @@ There are no dependencies and the crate can be used in a no_std context by disab

## Usage

There's two top-level functions provided:
[`parse()`](https://docs.rs/fast-float/latest/fast_float/fn.parse.html) and
There's two top-level functions provided:
[`parse()`](https://docs.rs/fast-float/latest/fast_float/fn.parse.html) and
[`parse_partial()`](https://docs.rs/fast-float/latest/fast_float/fn.parse_partial.html), both taking
either a string or a bytes slice and parsing the input into either `f32` or `f64`:
either a string or a bytes slice and parsing the input into either `f32` or `f64`:

- `parse()` treats the whole string as a decimal number and returns an error if there are
invalid characters or if the string is empty.
Expand All @@ -39,12 +39,12 @@ Example:
```rust
// Parse the entire string as a decimal number.
let s = "1.23e-02";
let x: f32 = fast_float::parse(s).unwrap();
let x: f32 = fast_float2::parse(s).unwrap();
assert_eq!(x, 0.0123);

// Parse as many characters as possible as a decimal number.
let s = "1.23e-02foo";
let (x, n) = fast_float::parse_partial::<f32, _>(s).unwrap();
let (x, n) = fast_float2::parse_partial::<f32, _>(s).unwrap();
assert_eq!(x, 0.0123);
assert_eq!(n, 8);
assert_eq!(&s[n..], "foo");
Expand All @@ -53,19 +53,22 @@ assert_eq!(&s[n..], "foo");
## Details

This crate is a direct port of Daniel Lemire's [`fast_float`](https://github.com/fastfloat/fast_float)
C++ library (valuable discussions with Daniel while porting it helped shape the crate and get it to
C++ library (valuable discussions with Daniel while porting it helped shape the crate and get it to
the performance level it's at now), with some Rust-specific tweaks. Please see the original
repository for many useful details regarding the algorithm and the implementation.

The parser is locale-independent. The resulting value is the closest floating-point values (using either
`f32` or `f64`), using the "round to even" convention for values that would otherwise fall right in-between
two values. That is, we provide exact parsing according to the IEEE standard.
The parser is locale-independent. The resulting value is the closest floating-point values (using either
`f32` or `f64`), using the "round to even" convention for values that would otherwise fall right in-between
two values. That is, we provide exact parsing according to the IEEE standard.

Infinity and NaN values can be parsed, along with scientific notation.

Both little-endian and big-endian platforms are equally supported, with extra optimizations enabled
on little-endian architectures.

Since [fast-float-rust](https://github.com/aldanor/fast-float-rust) is unmaintained, this is a fork
containing the patches and security updates.

## Testing

There are a few ways this crate is tested:
Expand All @@ -80,7 +83,7 @@ There are a few ways this crate is tested:
## Performance

The presented parser seems to beat all of the existing C/C++/Rust float parsers known to us at the
moment by a large margin, in all of the datasets we tested it on so far – see detailed benchmarks
moment by a large margin, in all of the datasets we tested it on so far – see detailed benchmarks
below (the only exception being the original fast_float C++ library, of course – performance of
which is within noise bounds of this crate). On modern machines like Apple M1, parsing throughput
can reach up to 1.5 GB/s.
Expand All @@ -103,7 +106,7 @@ C++ library, here are few brief notes:

## Benchmarks

Below are tables of best timings in nanoseconds for parsing a single number
Below are tables of best timings in nanoseconds for parsing a single number
into a 64-bit float.

#### Intel i7-4771
Expand Down Expand Up @@ -169,12 +172,12 @@ AMD Rome, Linux, Rust 1.49.

#### Notes

- The two test files referred above can be found in
- The two test files referred above can be found in
[this](https://github.com/lemire/simple_fastfloat_benchmark) repository.
- The Rust part of the table (along with a few other benchmarks) can be generated via
the benchmark tool that can be found under `extras/simple-bench` of this repo.
- The C/C++ part of the table (along with a few other benchmarks and parsers) can be
generated via a C++ utility that can be found in
generated via a C++ utility that can be found in
[this](https://github.com/lemire/simple_fastfloat_benchmark) repository.

<br>
Expand Down
2 changes: 1 addition & 1 deletion extras/data-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ license = "MIT OR Apache-2.0"
publish = false

[dependencies]
fast-float = { path = "../.." }
fast-float2 = { path = "../.." }
2 changes: 1 addition & 1 deletion extras/data-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl TestCase {
}
}

fn execute_one<F: fast_float::FastFloat>(&self, expected: F) {
fn execute_one<F: fast_float2::FastFloat>(&self, expected: F) {
let r = F::parse_float_partial(&self.string);
if !r.is_ok() {
dbg!(self);
Expand Down
2 changes: 1 addition & 1 deletion extras/simple-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
publish = false

[dependencies]
fast-float = { path = "../.." }
fast-float2 = { path = "../.." }
structopt = "0.3"
anyhow = "1.0"
lexical = "5.2"
Expand Down
6 changes: 3 additions & 3 deletions extras/simple-bench/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This crate provides a utility for benchmarking the `fast-float` crate against
This crate provides a utility for benchmarking the `fast-float2` crate against
`lexical_core` and standard library's `FromStr`.

To run a file-based test:
Expand All @@ -18,8 +18,8 @@ To run a randomized test:
cargo run --release -- random uniform
```

For more details and options (choosing a different random generator, storing
randomized inputs to a file, changing the number of runs, or switching between
For more details and options (choosing a different random generator, storing
randomized inputs to a file, changing the number of runs, or switching between
32-bit and 64-bit floats), refer to help:

```
Expand Down
4 changes: 2 additions & 2 deletions extras/simple-bench/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use fastrand::Rng;
use lexical::FromLexical;
use structopt::StructOpt;

use fast_float::FastFloat;
use fast_float2::FastFloat;

use random::RandomGen;

Expand Down Expand Up @@ -138,7 +138,7 @@ impl Method {
let data = &input.data;
let times = match self {
Self::FastFloat => run_bench(data, repeat, |s: &str| {
fast_float::parse_partial::<T, _>(s).unwrap_or_default().0
fast_float2::parse_partial::<T, _>(s).unwrap_or_default().0
}),
Self::Lexical => run_bench(data, repeat, |s: &str| {
lexical_core::parse_partial::<T>(s.as_bytes())
Expand Down
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/fast_float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ fn black_box<T>(dummy: T) -> T {
}

fuzz_target!(|data: &[u8]| {
let _ = black_box(::fast_float::parse::<f32, _>(data));
let _ = black_box(::fast_float::parse::<f64, _>(data));
let _ = black_box(::fast_float2::parse::<f32, _>(data));
let _ = black_box(::fast_float2::parse::<f64, _>(data));
});
2 changes: 1 addition & 1 deletion fuzz/fuzz_targets/roundtrip_f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use libfuzzer_sys::fuzz_target;
// is small enough that we can test it exhaustively

fn check_roundtrip(float: f64, string: impl AsRef<str>) {
let result = ::fast_float::parse::<f64, _>(string.as_ref()).unwrap();
let result = ::fast_float2::parse::<f64, _>(string.as_ref()).unwrap();
if float.is_nan() {
assert!(result.is_nan());
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
//! ```rust
//! // Parse the entire string as a decimal number.
//! let s = "1.23e-02";
//! let x: f32 = fast_float::parse(s).unwrap();
//! let x: f32 = fast_float2::parse(s).unwrap();
//! assert_eq!(x, 0.0123);
//!
//! // Parse as many characters as possible as a decimal number.
//! let s = "1.23e-02foo";
//! let (x, n) = fast_float::parse_partial::<f32, _>(s).unwrap();
//! let (x, n) = fast_float2::parse_partial::<f32, _>(s).unwrap();
//! assert_eq!(x, 0.0123);
//! assert_eq!(n, 8);
//! assert_eq!(&s[n..], "foo");
Expand Down
2 changes: 1 addition & 1 deletion tests/test_api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fast_float::{parse, parse_partial, FastFloat};
use fast_float2::{parse, parse_partial, FastFloat};

macro_rules! check_ok {
($s:expr, $x:expr) => {
Expand Down
6 changes: 3 additions & 3 deletions tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! check {
let string = String::from($s);
let s = string.as_bytes();
let expected: $ty = $e;
let result = fast_float::parse::<$ty, _>(s).unwrap();
let result = fast_float2::parse::<$ty, _>(s).unwrap();
assert_eq!(result, expected);
let lex = lexical_core::parse::<$ty>(s).unwrap();
assert_eq!(result, lex);
Expand Down Expand Up @@ -411,7 +411,7 @@ fn test_f64_pow10() {
for i in -308..=308 {
let s = format!("1e{}", i);
let v = f64::from_str(&s).unwrap();
assert_eq!(fast_float::parse::<f64, _>(s).unwrap(), v);
assert_eq!(fast_float2::parse::<f64, _>(s).unwrap(), v);
}
}

Expand All @@ -420,6 +420,6 @@ fn test_f32_pow10() {
for i in -38..=38 {
let s = format!("1e{}", i);
let v = f32::from_str(&s).unwrap();
assert_eq!(fast_float::parse::<f32, _>(s).unwrap(), v);
assert_eq!(fast_float2::parse::<f32, _>(s).unwrap(), v);
}
}
2 changes: 1 addition & 1 deletion tests/test_exhaustive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ fn test_f32_exhaustive_ryu() {
for i in 0..0xFFFF_FFFF_u32 {
let a: f32 = unsafe { core::mem::transmute(i) };
let s = buf.format(a);
let b: f32 = fast_float::parse(s).unwrap();
let b: f32 = fast_float2::parse(s).unwrap();
assert!(a == b || (a.is_nan() && b.is_nan()));
}
}
2 changes: 1 addition & 1 deletion tests/test_random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn test_f64_random_from_u64() {
let i: u64 = rng.u64(0..0xFFFF_FFFF_FFFF_FFFF);
let a: f64 = unsafe { core::mem::transmute(i) };
let s = buf.format(a);
let b: f64 = fast_float::parse(s).unwrap();
let b: f64 = fast_float2::parse(s).unwrap();
assert!(a == b || (a.is_nan() && b.is_nan()));
}
}
Loading