diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d7eb0e..271f136 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 5da5c9d..7073ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "fast-float" -version = "0.2.0" -authors = ["Ivan Smirnov "] -repository = "https://github.com/aldanor/fast-float-rust" -documentation = "https://docs.rs/fast-float" +name = "fast-float2" +version = "0.2.1" +authors = ["Ivan Smirnov ", "Alex Huszagh "] +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"] @@ -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"] diff --git a/README.md b/README.md index fde2755..f65df04 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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. @@ -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. @@ -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::(s).unwrap(); +let (x, n) = fast_float2::parse_partial::(s).unwrap(); assert_eq!(x, 0.0123); assert_eq!(n, 8); assert_eq!(&s[n..], "foo"); @@ -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: @@ -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. @@ -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 @@ -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.
diff --git a/extras/data-tests/Cargo.toml b/extras/data-tests/Cargo.toml index e04d88f..5cb96b5 100644 --- a/extras/data-tests/Cargo.toml +++ b/extras/data-tests/Cargo.toml @@ -8,4 +8,4 @@ license = "MIT OR Apache-2.0" publish = false [dependencies] -fast-float = { path = "../.." } +fast-float2 = { path = "../.." } diff --git a/extras/data-tests/src/main.rs b/extras/data-tests/src/main.rs index 7487b0c..3be4d02 100644 --- a/extras/data-tests/src/main.rs +++ b/extras/data-tests/src/main.rs @@ -22,7 +22,7 @@ impl TestCase { } } - fn execute_one(&self, expected: F) { + fn execute_one(&self, expected: F) { let r = F::parse_float_partial(&self.string); if !r.is_ok() { dbg!(self); diff --git a/extras/simple-bench/Cargo.toml b/extras/simple-bench/Cargo.toml index 5cd59c8..1a3e648 100644 --- a/extras/simple-bench/Cargo.toml +++ b/extras/simple-bench/Cargo.toml @@ -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" diff --git a/extras/simple-bench/README.md b/extras/simple-bench/README.md index 10fdbbc..fa6a3b8 100644 --- a/extras/simple-bench/README.md +++ b/extras/simple-bench/README.md @@ -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: @@ -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: ``` diff --git a/extras/simple-bench/src/main.rs b/extras/simple-bench/src/main.rs index 9428fad..5dbe06d 100644 --- a/extras/simple-bench/src/main.rs +++ b/extras/simple-bench/src/main.rs @@ -10,7 +10,7 @@ use fastrand::Rng; use lexical::FromLexical; use structopt::StructOpt; -use fast_float::FastFloat; +use fast_float2::FastFloat; use random::RandomGen; @@ -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::(s).unwrap_or_default().0 + fast_float2::parse_partial::(s).unwrap_or_default().0 }), Self::Lexical => run_bench(data, repeat, |s: &str| { lexical_core::parse_partial::(s.as_bytes()) diff --git a/fuzz/fuzz_targets/fast_float.rs b/fuzz/fuzz_targets/fast_float.rs index 733c5f8..581e5d7 100644 --- a/fuzz/fuzz_targets/fast_float.rs +++ b/fuzz/fuzz_targets/fast_float.rs @@ -11,6 +11,6 @@ fn black_box(dummy: T) -> T { } fuzz_target!(|data: &[u8]| { - let _ = black_box(::fast_float::parse::(data)); - let _ = black_box(::fast_float::parse::(data)); + let _ = black_box(::fast_float2::parse::(data)); + let _ = black_box(::fast_float2::parse::(data)); }); diff --git a/fuzz/fuzz_targets/roundtrip_f64.rs b/fuzz/fuzz_targets/roundtrip_f64.rs index 4c92e9f..bb3506c 100644 --- a/fuzz/fuzz_targets/roundtrip_f64.rs +++ b/fuzz/fuzz_targets/roundtrip_f64.rs @@ -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) { - let result = ::fast_float::parse::(string.as_ref()).unwrap(); + let result = ::fast_float2::parse::(string.as_ref()).unwrap(); if float.is_nan() { assert!(result.is_nan()); } else { diff --git a/src/lib.rs b/src/lib.rs index aef86be..a90ad36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::(s).unwrap(); +//! let (x, n) = fast_float2::parse_partial::(s).unwrap(); //! assert_eq!(x, 0.0123); //! assert_eq!(n, 8); //! assert_eq!(&s[n..], "foo"); diff --git a/tests/test_api.rs b/tests/test_api.rs index d0e1615..ff04b74 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -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) => { diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 235990f..f6eb4c8 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -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); @@ -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::(s).unwrap(), v); + assert_eq!(fast_float2::parse::(s).unwrap(), v); } } @@ -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::(s).unwrap(), v); + assert_eq!(fast_float2::parse::(s).unwrap(), v); } } diff --git a/tests/test_exhaustive.rs b/tests/test_exhaustive.rs index 308b9b4..3b77b39 100644 --- a/tests/test_exhaustive.rs +++ b/tests/test_exhaustive.rs @@ -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())); } } diff --git a/tests/test_random.rs b/tests/test_random.rs index 1dadd74..4d54dee 100644 --- a/tests/test_random.rs +++ b/tests/test_random.rs @@ -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())); } }