diff --git a/.github/workflows/Benchmark.yml b/.github/workflows/Benchmark.yml index c340512b..7d1e4cc5 100644 --- a/.github/workflows/Benchmark.yml +++ b/.github/workflows/Benchmark.yml @@ -22,6 +22,6 @@ jobs: - run: cargo check - run: cargo build - run: | - cd lexical-benchmark + cd extras/benchmark cargo build cargo bench diff --git a/.github/workflows/Simple.yml b/.github/workflows/Simple.yml index 3329c976..082fd8e6 100644 --- a/.github/workflows/Simple.yml +++ b/.github/workflows/Simple.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - rust: [1.63.0] + rust: [1.61.0] steps: - uses: actions/checkout@v4 with: @@ -20,6 +20,7 @@ jobs: toolchain: ${{matrix.rust}} - run: cargo check - run: cargo build + - run: cargo test test: name: Rust ${{matrix.rust}} @@ -38,6 +39,10 @@ jobs: - run: cargo check - run: cargo test - run: cargo test --features=radix,format,compact + - run: | + cd extras + cargo test + cargo test --features=radix,format,compact check: name: Lint code diff --git a/.gitmodules b/.gitmodules index 1e48e4be..f0508c13 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "lexical-parse-float/etc/correctness/test-parse-golang/parse-number-fxx-test-data"] path = lexical-parse-float/etc/correctness/test-parse-golang/parse-number-fxx-test-data url = https://github.com/Alexhuszagh/parse-number-fxx-test-data -[submodule "lexical-benchmark/data"] - path = lexical-benchmark/data +[submodule "extras/benchmark/data"] + path = extras/benchmark/data url = https://github.com/Alexhuszagh/rust-lexical-benchmark-data diff --git a/CHANGELOG b/CHANGELOG index b3edd3f8..9b4eee73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Additional trait impls for `f16` and `bf16` to better match Rust's interface. +### Changed + +- Lowered the MSRV from 1.63.0 to 1.61.0 and adds support for most testing on 1.61.0. + ## [1.0.5] 2024-12-08 ### Fixed diff --git a/assets/size0_nt.json b/assets/size0_nt.json index d04a8f0c..1a1dba5d 100644 --- a/assets/size0_nt.json +++ b/assets/size0_nt.json @@ -1 +1 @@ -{"core": {"pe": {"parse-float-f32": 18224, "parse-float-f64": 18464, "parse-integer-i128": 5496, "parse-integer-i16": 4720, "parse-integer-i32": 4688, "parse-integer-i64": 4704, "parse-integer-i8": 4512, "parse-integer-u128": 5328, "parse-integer-u16": 4720, "parse-integer-u32": 4640, "parse-integer-u64": 4704, "parse-integer-u8": 4624, "write-float-f32": 37480, "write-float-f64": 37544, "write-integer-i128": 16688, "write-integer-i16": 15800, "write-integer-i32": 15240, "write-integer-i64": 15896, "write-integer-i8": 15512, "write-integer-u128": 16672, "write-integer-u16": 15544, "write-integer-u32": 15240, "write-integer-u64": 15256, "write-integer-u8": 15240}}, "lexical": {"pe": {"parse-float-f32": 167816, "parse-float-f64": 168320, "parse-integer-i128": 31832, "parse-integer-i16": 16536, "parse-integer-i32": 28040, "parse-integer-i64": 30056, "parse-integer-i8": 16408, "parse-integer-u128": 31112, "parse-integer-u16": 15976, "parse-integer-u32": 27432, "parse-integer-u64": 29496, "parse-integer-u8": 15832, "write-float-f32": 97864, "write-float-f64": 154064, "write-integer-i128": 210816, "write-integer-i16": 12928, "write-integer-i32": 27880, "write-integer-i64": 57392, "write-integer-i8": 7232, "write-integer-u128": 107776, "write-integer-u16": 8032, "write-integer-u32": 15568, "write-integer-u64": 30480, "write-integer-u8": 5008}}} \ No newline at end of file +{"core": {"pe": {"parse-float-f32": 18224, "parse-float-f64": 18464, "parse-integer-i128": 5496, "parse-integer-i16": 4720, "parse-integer-i32": 4688, "parse-integer-i64": 4704, "parse-integer-i8": 4512, "parse-integer-u128": 5328, "parse-integer-u16": 4720, "parse-integer-u32": 4640, "parse-integer-u64": 4704, "parse-integer-u8": 4624, "write-float-f32": 37480, "write-float-f64": 37544, "write-integer-i128": 16688, "write-integer-i16": 15800, "write-integer-i32": 15240, "write-integer-i64": 15896, "write-integer-i8": 15512, "write-integer-u128": 16672, "write-integer-u16": 15544, "write-integer-u32": 15240, "write-integer-u64": 15256, "write-integer-u8": 15240}}, "lexical": {"pe": {"parse-float-f32": 167816, "parse-float-f64": 168320, "parse-integer-i128": 31832, "parse-integer-i16": 16536, "parse-integer-i32": 28040, "parse-integer-i64": 30056, "parse-integer-i8": 16408, "parse-integer-u128": 31112, "parse-integer-u16": 15976, "parse-integer-u32": 27432, "parse-integer-u64": 29496, "parse-integer-u8": 15832, "write-float-f32": 97864, "write-float-f64": 155664, "write-integer-i128": 212784, "write-integer-i16": 12992, "write-integer-i32": 27944, "write-integer-i64": 58592, "write-integer-i8": 7296, "write-integer-u128": 108736, "write-integer-u16": 8032, "write-integer-u32": 15568, "write-integer-u64": 30960, "write-integer-u8": 5008}}} \ No newline at end of file diff --git a/assets/size1_nt.json b/assets/size1_nt.json index 637d737e..564d98c7 100644 --- a/assets/size1_nt.json +++ b/assets/size1_nt.json @@ -1 +1 @@ -{"core": {"pe": {"parse-float-f32": 17992, "parse-float-f64": 18296, "parse-integer-i128": 1504, "parse-integer-i16": 1304, "parse-integer-i32": 1272, "parse-integer-i64": 1272, "parse-integer-i8": 1256, "parse-integer-u128": 1216, "parse-integer-u16": 632, "parse-integer-u32": 632, "parse-integer-u64": 1128, "parse-integer-u8": 632, "write-float-f32": 25808, "write-float-f64": 25872, "write-integer-i128": 5096, "write-integer-i16": 4248, "write-integer-i32": 3720, "write-integer-i64": 4344, "write-integer-i8": 3976, "write-integer-u128": 5080, "write-integer-u16": 3992, "write-integer-u32": 3720, "write-integer-u64": 3720, "write-integer-u8": 3720}}, "lexical": {"pe": {"parse-float-f32": 36784, "parse-float-f64": 37088, "parse-integer-i128": 4696, "parse-integer-i16": 4248, "parse-integer-i32": 4280, "parse-integer-i64": 4136, "parse-integer-i8": 4248, "parse-integer-u128": 4008, "parse-integer-u16": 3752, "parse-integer-u32": 3592, "parse-integer-u64": 3640, "parse-integer-u8": 3736, "write-float-f32": 14856, "write-float-f64": 38408, "write-integer-i128": 14840, "write-integer-i16": 480, "write-integer-i32": 624, "write-integer-i64": 5152, "write-integer-i8": 416, "write-integer-u128": 8120, "write-integer-u16": 432, "write-integer-u32": 480, "write-integer-u64": 3264, "write-integer-u8": 400}}} \ No newline at end of file +{"core": {"pe": {"parse-float-f32": 17992, "parse-float-f64": 18296, "parse-integer-i128": 1504, "parse-integer-i16": 1304, "parse-integer-i32": 1272, "parse-integer-i64": 1272, "parse-integer-i8": 1256, "parse-integer-u128": 1216, "parse-integer-u16": 632, "parse-integer-u32": 632, "parse-integer-u64": 1128, "parse-integer-u8": 632, "write-float-f32": 25808, "write-float-f64": 25872, "write-integer-i128": 5096, "write-integer-i16": 4248, "write-integer-i32": 3720, "write-integer-i64": 4344, "write-integer-i8": 3976, "write-integer-u128": 5080, "write-integer-u16": 3992, "write-integer-u32": 3720, "write-integer-u64": 3720, "write-integer-u8": 3720}}, "lexical": {"pe": {"parse-float-f32": 36912, "parse-float-f64": 37216, "parse-integer-i128": 4696, "parse-integer-i16": 4248, "parse-integer-i32": 4280, "parse-integer-i64": 4264, "parse-integer-i8": 4248, "parse-integer-u128": 4008, "parse-integer-u16": 3752, "parse-integer-u32": 3720, "parse-integer-u64": 3768, "parse-integer-u8": 3736, "write-float-f32": 14856, "write-float-f64": 38408, "write-integer-i128": 14840, "write-integer-i16": 480, "write-integer-i32": 624, "write-integer-i64": 5152, "write-integer-i8": 416, "write-integer-u128": 8120, "write-integer-u16": 432, "write-integer-u32": 480, "write-integer-u64": 3264, "write-integer-u8": 400}}} \ No newline at end of file diff --git a/assets/size2_nt.json b/assets/size2_nt.json index 38e6f9cb..0d412cb6 100644 --- a/assets/size2_nt.json +++ b/assets/size2_nt.json @@ -1 +1 @@ -{"core": {"pe": {"parse-float-f32": 17856, "parse-float-f64": 18112, "parse-integer-i128": 800, "parse-integer-i16": 608, "parse-integer-i32": 608, "parse-integer-i64": 624, "parse-integer-i8": 624, "parse-integer-u128": 608, "parse-integer-u16": 560, "parse-integer-u32": 560, "parse-integer-u64": 560, "parse-integer-u8": 560, "write-float-f32": 22363, "write-float-f64": 22555, "write-integer-i128": 3539, "write-integer-i16": 2595, "write-integer-i32": 2067, "write-integer-i64": 2691, "write-integer-i8": 2323, "write-integer-u128": 3531, "write-integer-u16": 2339, "write-integer-u32": 2067, "write-integer-u64": 2067, "write-integer-u8": 2227}}, "lexical": {"pe": {"parse-float-f32": 26968, "parse-float-f64": 27304, "parse-integer-i128": 4184, "parse-integer-i16": 3960, "parse-integer-i32": 4344, "parse-integer-i64": 5192, "parse-integer-i8": 3800, "parse-integer-u128": 3768, "parse-integer-u16": 3752, "parse-integer-u32": 3896, "parse-integer-u64": 4232, "parse-integer-u8": 3672, "write-float-f32": 7584, "write-float-f64": 22224, "write-integer-i128": 2240, "write-integer-i16": -64, "write-integer-i32": 64, "write-integer-i64": 496, "write-integer-i8": -112, "write-integer-u128": 976, "write-integer-u16": -112, "write-integer-u32": -64, "write-integer-u64": 144, "write-integer-u8": -144}}} \ No newline at end of file +{"core": {"pe": {"parse-float-f32": 17856, "parse-float-f64": 18112, "parse-integer-i128": 800, "parse-integer-i16": 608, "parse-integer-i32": 608, "parse-integer-i64": 624, "parse-integer-i8": 624, "parse-integer-u128": 608, "parse-integer-u16": 560, "parse-integer-u32": 560, "parse-integer-u64": 560, "parse-integer-u8": 560, "write-float-f32": 22363, "write-float-f64": 22555, "write-integer-i128": 3539, "write-integer-i16": 2595, "write-integer-i32": 2067, "write-integer-i64": 2691, "write-integer-i8": 2323, "write-integer-u128": 3531, "write-integer-u16": 2339, "write-integer-u32": 2067, "write-integer-u64": 2067, "write-integer-u8": 2227}}, "lexical": {"pe": {"parse-float-f32": 26968, "parse-float-f64": 27432, "parse-integer-i128": 4184, "parse-integer-i16": 3960, "parse-integer-i32": 4344, "parse-integer-i64": 5192, "parse-integer-i8": 3800, "parse-integer-u128": 3768, "parse-integer-u16": 3752, "parse-integer-u32": 3896, "parse-integer-u64": 4232, "parse-integer-u8": 3672, "write-float-f32": 7584, "write-float-f64": 22224, "write-integer-i128": 2240, "write-integer-i16": -64, "write-integer-i32": 64, "write-integer-i64": 496, "write-integer-i8": -112, "write-integer-u128": 976, "write-integer-u16": -112, "write-integer-u32": -64, "write-integer-u64": 144, "write-integer-u8": -144}}} \ No newline at end of file diff --git a/assets/size3_nt.json b/assets/size3_nt.json index 2c10182c..ff0bb21a 100644 --- a/assets/size3_nt.json +++ b/assets/size3_nt.json @@ -1 +1 @@ -{"core": {"pe": {"parse-float-f32": 17856, "parse-float-f64": 18128, "parse-integer-i128": 832, "parse-integer-i16": 640, "parse-integer-i32": 640, "parse-integer-i64": 656, "parse-integer-i8": 656, "parse-integer-u128": 640, "parse-integer-u16": 592, "parse-integer-u32": 576, "parse-integer-u64": 592, "parse-integer-u8": 576, "write-float-f32": 22958, "write-float-f64": 23118, "write-integer-i128": 3926, "write-integer-i16": 2982, "write-integer-i32": 2438, "write-integer-i64": 3078, "write-integer-i8": 2710, "write-integer-u128": 3918, "write-integer-u16": 2726, "write-integer-u32": 2438, "write-integer-u64": 2438, "write-integer-u8": 2614}}, "lexical": {"pe": {"parse-float-f32": 27152, "parse-float-f64": 27440, "parse-integer-i128": 7280, "parse-integer-i16": 3984, "parse-integer-i32": 4320, "parse-integer-i64": 5168, "parse-integer-i8": 3776, "parse-integer-u128": 5456, "parse-integer-u16": 3760, "parse-integer-u32": 3888, "parse-integer-u64": 4240, "parse-integer-u8": 3664, "write-float-f32": 7648, "write-float-f64": 22448, "write-integer-i128": 2328, "write-integer-i16": -8, "write-integer-i32": 136, "write-integer-i64": 568, "write-integer-i8": -56, "write-integer-u128": 1096, "write-integer-u16": -56, "write-integer-u32": -8, "write-integer-u64": 216, "write-integer-u8": -88}}} \ No newline at end of file +{"core": {"pe": {"parse-float-f32": 17856, "parse-float-f64": 18128, "parse-integer-i128": 832, "parse-integer-i16": 640, "parse-integer-i32": 640, "parse-integer-i64": 656, "parse-integer-i8": 656, "parse-integer-u128": 640, "parse-integer-u16": 592, "parse-integer-u32": 576, "parse-integer-u64": 592, "parse-integer-u8": 576, "write-float-f32": 22958, "write-float-f64": 23118, "write-integer-i128": 3926, "write-integer-i16": 2982, "write-integer-i32": 2438, "write-integer-i64": 3078, "write-integer-i8": 2710, "write-integer-u128": 3918, "write-integer-u16": 2726, "write-integer-u32": 2438, "write-integer-u64": 2438, "write-integer-u8": 2614}}, "lexical": {"pe": {"parse-float-f32": 27152, "parse-float-f64": 27568, "parse-integer-i128": 7280, "parse-integer-i16": 3984, "parse-integer-i32": 4320, "parse-integer-i64": 5168, "parse-integer-i8": 3776, "parse-integer-u128": 5456, "parse-integer-u16": 3760, "parse-integer-u32": 3888, "parse-integer-u64": 4240, "parse-integer-u8": 3664, "write-float-f32": 7648, "write-float-f64": 22448, "write-integer-i128": 2328, "write-integer-i16": -8, "write-integer-i32": 136, "write-integer-i64": 568, "write-integer-i8": -56, "write-integer-u128": 1096, "write-integer-u16": -56, "write-integer-u32": -8, "write-integer-u64": 216, "write-integer-u8": -88}}} \ No newline at end of file diff --git a/assets/sizes_nt.json b/assets/sizes_nt.json index 7dda95c1..44406821 100644 --- a/assets/sizes_nt.json +++ b/assets/sizes_nt.json @@ -1 +1 @@ -{"core": {"pe": {"parse-float-f32": 17760, "parse-float-f64": 18016, "parse-integer-i128": 656, "parse-integer-i16": 480, "parse-integer-i32": 480, "parse-integer-i64": 480, "parse-integer-i8": 480, "parse-integer-u128": 480, "parse-integer-u16": 432, "parse-integer-u32": 432, "parse-integer-u64": 432, "parse-integer-u8": 432, "write-float-f32": 22112, "write-float-f64": 22304, "write-integer-i128": 3176, "write-integer-i16": 2360, "write-integer-i32": 1832, "write-integer-i64": 2456, "write-integer-i8": 2088, "write-integer-u128": 3168, "write-integer-u16": 2104, "write-integer-u32": 1832, "write-integer-u64": 1832, "write-integer-u8": 1832}}, "lexical": {"pe": {"parse-float-f32": 23824, "parse-float-f64": 24224, "parse-integer-i128": 3376, "parse-integer-i16": 3200, "parse-integer-i32": 3184, "parse-integer-i64": 3184, "parse-integer-i8": 3120, "parse-integer-u128": 3040, "parse-integer-u16": 2976, "parse-integer-u32": 2976, "parse-integer-u64": 2976, "parse-integer-u8": 2960, "write-float-f32": 6912, "write-float-f64": 20976, "write-integer-i128": 11440, "write-integer-i16": -48, "write-integer-i32": 64, "write-integer-i64": 4096, "write-integer-i8": -96, "write-integer-u128": 5648, "write-integer-u16": -96, "write-integer-u32": -48, "write-integer-u64": 2272, "write-integer-u8": -128}}} \ No newline at end of file +{"core": {"pe": {"parse-float-f32": 17760, "parse-float-f64": 18016, "parse-integer-i128": 656, "parse-integer-i16": 480, "parse-integer-i32": 480, "parse-integer-i64": 480, "parse-integer-i8": 480, "parse-integer-u128": 480, "parse-integer-u16": 432, "parse-integer-u32": 432, "parse-integer-u64": 432, "parse-integer-u8": 432, "write-float-f32": 22112, "write-float-f64": 22304, "write-integer-i128": 3176, "write-integer-i16": 2360, "write-integer-i32": 1832, "write-integer-i64": 2456, "write-integer-i8": 2088, "write-integer-u128": 3168, "write-integer-u16": 2104, "write-integer-u32": 1832, "write-integer-u64": 1832, "write-integer-u8": 1832}}, "lexical": {"pe": {"parse-float-f32": 23840, "parse-float-f64": 24240, "parse-integer-i128": 3376, "parse-integer-i16": 3200, "parse-integer-i32": 3184, "parse-integer-i64": 3184, "parse-integer-i8": 3120, "parse-integer-u128": 3040, "parse-integer-u16": 2976, "parse-integer-u32": 2976, "parse-integer-u64": 2976, "parse-integer-u8": 2960, "write-float-f32": 6912, "write-float-f64": 20976, "write-integer-i128": 11440, "write-integer-i16": -48, "write-integer-i32": 64, "write-integer-i64": 4096, "write-integer-i8": -96, "write-integer-u128": 5648, "write-integer-u16": -96, "write-integer-u32": -48, "write-integer-u64": 2272, "write-integer-u8": -128}}} \ No newline at end of file diff --git a/assets/timings_all_nt.svg b/assets/timings_all_nt.svg index 549b43f3..455cdb17 100644 --- a/assets/timings_all_nt.svg +++ b/assets/timings_all_nt.svg @@ -6,7 +6,7 @@ - 2024-12-07T12:40:55.101083 + 2024-12-17T20:22:15.820999 image/svg+xml @@ -40,31 +40,24 @@ z - + - - + - - + + - - - - - - - + + + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + + + + - + - + - + - + + + + @@ -232,7 +275,141 @@ L 379.008 41.472 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -385,28 +562,246 @@ z - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -496,25 +891,6 @@ Q 2381 3103 1925 3103 Q 1469 3103 1208 2742 Q 947 2381 947 1747 z -" transform="scale(0.015625)"/> - + diff --git a/assets/timings_lexical-parse-float_nt.svg b/assets/timings_lexical-parse-float_nt.svg index c1437c28..9bd657c3 100644 --- a/assets/timings_lexical-parse-float_nt.svg +++ b/assets/timings_lexical-parse-float_nt.svg @@ -6,7 +6,7 @@ - 2024-12-07T12:40:55.945545 + 2024-12-17T20:22:16.655694 image/svg+xml @@ -40,31 +40,24 @@ z - + - - + - - + + - - - - - - - + + + + + - + - + - - + + - - - - - - + + + + + - + - + - - + + + + + - + + - + - + - - + + - + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + @@ -385,28 +427,53 @@ z - + + + + + + + + + + + + + + - + - + - + - + @@ -717,7 +784,7 @@ z - + diff --git a/assets/timings_lexical-parse-integer_nt.svg b/assets/timings_lexical-parse-integer_nt.svg index b5231aef..1e2b3f48 100644 --- a/assets/timings_lexical-parse-integer_nt.svg +++ b/assets/timings_lexical-parse-integer_nt.svg @@ -6,7 +6,7 @@ - 2024-12-07T12:40:56.470949 + 2024-12-17T20:22:17.135982 image/svg+xml @@ -40,31 +40,24 @@ z - + - - + - - + + - - - - - - - + + + + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + - - - + + + - + - + - - - + + + + - - - + + + - + - + - - - + + + + + + - + + - + @@ -385,28 +483,53 @@ z - + + + + + + + + + + + + + + - + - + - + - + @@ -692,7 +815,7 @@ z - + diff --git a/assets/timings_lexical-write-float_nt.svg b/assets/timings_lexical-write-float_nt.svg index 6bb707a0..eb440d03 100644 --- a/assets/timings_lexical-write-float_nt.svg +++ b/assets/timings_lexical-write-float_nt.svg @@ -6,7 +6,7 @@ - 2024-12-07T12:40:57.268867 + 2024-12-17T20:22:17.885896 image/svg+xml @@ -40,31 +40,24 @@ z - + - - + - - + + - - - - - - - + + + + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + - - - + + + - + - + - - - + + + + - - - + + + - + - + - - - + + + + + + - + + - + @@ -385,28 +483,53 @@ z - + + + + + + + + + + + + + + - + - + - + - + @@ -712,7 +835,7 @@ z - + diff --git a/assets/timings_lexical-write-integer_nt.svg b/assets/timings_lexical-write-integer_nt.svg index df58072c..e4c011d8 100644 --- a/assets/timings_lexical-write-integer_nt.svg +++ b/assets/timings_lexical-write-integer_nt.svg @@ -6,7 +6,7 @@ - 2024-12-07T12:40:57.811026 + 2024-12-17T20:22:18.411889 image/svg+xml @@ -40,31 +40,24 @@ z - + - - + - - + + - - - - - - - + + + + + - + - + - - + + - - - - - - + + + + + - + - + - - + + + + + - + + - + - + - - + + - + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + @@ -385,28 +427,53 @@ z - + + + + + + + + + + + + + + - + - + - + - + @@ -654,7 +721,7 @@ z - + diff --git a/ci/check.sh b/ci/check.sh index 157f6a38..85b2a68e 100755 --- a/ci/check.sh +++ b/ci/check.sh @@ -6,7 +6,10 @@ set -ex # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" + +export RUSTFLAGS="--deny warnings" scripts/check.sh -RUSTFLAGS="--deny warnings" cargo +nightly build --features=lint +cargo +nightly build --features=lint diff --git a/ci/test.sh b/ci/test.sh index 024674d7..d3033de2 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -11,11 +11,13 @@ home=$(dirname "${script_home}") version="${CARGO_VERSION}" cd "${home}" +export RUSTFLAGS="--deny warnings" + # Print our cargo version, for debugging. cargo ${version} --version # Ensure we have all our benchmark data files -git submodule update --init lexical-benchmark/data +git submodule update --init extras/benchmark/data # Force default tests to disable default feature on NO_STD. if [ ! -z $NO_STD ]; then @@ -44,6 +46,18 @@ check_error() { fi } +check_test() { + pushd "${home}/lexical-${1}" + + cargo ${version} check --tests + if [ -z $DISABLE_EXTRA_TESTS ]; then + cd "${home}/extras/${1}" + cargo ${version} check --tests + fi + + popd +} + # Don't build the target, but ensure the syntax is correct. check() { if [ ! -z $NO_FEATURES ]; then @@ -57,31 +71,25 @@ check() { done # Check each of our sub-crates compiles. - cd lexical-parse-float - cargo ${version} check --tests - - cd ../lexical-parse-integer - cargo ${version} check --tests - - cd ../lexical-write-float - cargo ${version} check --tests - - cd ../lexical-write-integer - cargo ${version} check --tests + check_test "util" + check_test "parse-float" + check_test "parse-integer" + check_test "write-float" + check_test "write-integer" # ensure our partial features aren't allowed, as are unsupported features - cd ../lexical-core + cd "${home}/lexical-core" partial=(parse write floats integers) for feature in "${partial[@]}"; do check_error "${feature}" done - cd ../lexical + cd "${home}/lexical" for feature in "${partial[@]}"; do check_error "${feature}" done - cd .. + cd "${home}" } # Build target. @@ -91,8 +99,29 @@ build() { cargo ${version} build $build_features --release } +run_test() { + cargo ${version} test "$@" + + if [ -z $DISABLE_EXTRA_TESTS ]; then + pushd "${home}/extras" + cargo ${version} test "$@" + popd + fi +} + +run_test_high_level() { + # this fixes an issue where the lexical and lexical-core tests weren't being run + cd "${home}/lexical-core" + cargo ${version} test "$@" + cd "${home}" + + cd "${home}/lexical" + cargo ${version} test "$@" + cd "${home}" +} + # Test target. -test() { +tests() { if [ ! -z $DISABLE_TESTS ]; then return fi @@ -102,29 +131,18 @@ test() { # Default tests. test_features="$DEFAULT_FEATURES --features=$REQUIRED_FEATURES" - cargo ${version} test $test_features $DOCTESTS - cargo ${version} test $test_features $DOCTESTS --release - cargo ${version} test --features=radix,format,compact $DOCTESTS --release + run_test $test_features $DOCTESTS + run_test $test_features $DOCTESTS --release + run_test --features=radix,format,compact $DOCTESTS --release # NOTE: This tests a regressions, related to #96. - cargo ${version} test --features=format $DOCTESTS + run_test --features=format $DOCTESTS --release # Ensure we test radix without the compact feature # See #169 - cargo ${version} test --features=radix,format --release - - # this fixes an issue where the lexical and lexical-core tests weren't being run - cd lexical-core - cargo ${version} test $test_features,format - cargo ${version} test $test_features,radix - cargo ${version} test $test_features,format,radix - cd .. - - # this fixes an issue where the lexical and lexical-core tests weren't being run - cd lexical - cargo ${version} test $test_features,format - cargo ${version} test $test_features,radix - cargo ${version} test $test_features,format,radix - cd .. + run_test --features=radix,format --release + run_test_high_level $test_features,format + run_test_high_level $test_features,radix + run_test_high_level $test_features,format,radix } # Dry-run bench target @@ -143,21 +161,21 @@ bench() { return fi - cd lexical-benchmark + cd "${home}/extras/benchmark" bench_features="$DEFAULT_FEATURES --features=$REQUIRED_FEATURES" cargo ${version} test $bench_features --bench '*' - cd .. + cd "${home}" } main() { check build - test + tests bench if [ ! -z $NIGHTLY ]; then scripts/check.sh - RUSTFLAGS="--deny warnings" cargo +nightly build --features=lint + cargo +nightly build --features=lint fi } diff --git a/docs/Development.md b/docs/Development.md index 8d43469d..c3ff672b 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -1,6 +1,6 @@ # Getting Started -In order to build and test lexical, only a modern Rust toolchain (1.51+) is required. However, for reasons described below, we highly recommend you install a recent (1.55+) nightly toolchain. +In order to build and test lexical, only a modern Rust toolchain (1.65+) (1.61.0 to build, 1.65.0 to test) is required. However, for reasons described below, we highly recommend you install a recent (1.55+) nightly toolchain. ```bash cargo +nightly build @@ -65,6 +65,8 @@ In addition, the following non-Rust dependencies must be installed: - python-magic (python-magic-win64 on Windows) - Valgrind +In order to minimize build times, dependencies, and potential version conflicts, all tests with development dependencies are moved to a [extras](/extras) workspace. This allows testing from that directory without influencing the core crate. This is less than ideal but is required to simplify version and dependency management without affecting test coverage. Each keeps the identical workspace structure so we can perfectly replicate the original unittests. + ## Development Process The [scripts](https://github.com/Alexhuszagh/rust-lexical/tree/main/scripts) directory contains numerous scripts for testing, fuzzing, analyzing, and formatting code. Since many development features are nightly-only, this ensures the proper compiler features are used. This requires a recent version of a nightly compiler (1.65.0+) installed via Rustup, which can be invoked as `cargo +nightly`. diff --git a/extras/Cargo.toml b/extras/Cargo.toml new file mode 100644 index 00000000..0ff0778c --- /dev/null +++ b/extras/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +members = [ + "core", + "parse-integer", + "parse-float", + "util", + "write-integer", + "write-float", +] +resolver = "2" + +[profile.dev] +opt-level = 0 +debug = true +lto = false + +[profile.release] +opt-level = 3 +debug = false +debug-assertions = false +lto = true diff --git a/extras/README.md b/extras/README.md new file mode 100644 index 00000000..8f5b0cae --- /dev/null +++ b/extras/README.md @@ -0,0 +1,5 @@ +# Extras + +This contains our unittests that depend on external, dev dependencies. This avoids having any packaging conflicts for versioning due to external, development packages. Lexical is a rather core crate, and the external dependencies create moving goalposts for versioning, as well as drag in more packages than required. By having a single, core workspace for each test, we can minimize build times and also external dependency logic. + +This also includes logic for benchmarks and more. diff --git a/lexical-asm/Cargo.toml b/extras/asm/Cargo.toml similarity index 89% rename from lexical-asm/Cargo.toml rename to extras/asm/Cargo.toml index 16d8a6b7..5e11ef9c 100644 --- a/lexical-asm/Cargo.toml +++ b/extras/asm/Cargo.toml @@ -5,28 +5,31 @@ authors = ["Alex Huszagh "] edition = "2021" publish = false +[workspace] +members = [] + [dependencies.lexical-util] -path = "../lexical-util" +path = "../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-integer] -path = "../lexical-parse-integer" +path = "../../lexical-parse-integer" default-features = false features = [] [dependencies.lexical-write-integer] -path = "../lexical-write-integer" +path = "../../lexical-write-integer" default-features = false features = [] [dependencies.lexical-parse-float] -path = "../lexical-parse-float" +path = "../../lexical-parse-float" default-features = false features = [] [dependencies.lexical-write-float] -path = "../lexical-write-float" +path = "../../lexical-write-float" default-features = false features = [] @@ -67,8 +70,6 @@ compact = [ "lexical-parse-float/compact" ] -[workspace] - [profile.release] opt-level = 3 debug = false diff --git a/lexical-asm/README.md b/extras/asm/README.md similarity index 100% rename from lexical-asm/README.md rename to extras/asm/README.md diff --git a/lexical-asm/clippy.toml b/extras/asm/clippy.toml similarity index 100% rename from lexical-asm/clippy.toml rename to extras/asm/clippy.toml diff --git a/lexical-asm/rustfmt.toml b/extras/asm/rustfmt.toml similarity index 100% rename from lexical-asm/rustfmt.toml rename to extras/asm/rustfmt.toml diff --git a/lexical-asm/src/lib.rs b/extras/asm/src/lib.rs similarity index 100% rename from lexical-asm/src/lib.rs rename to extras/asm/src/lib.rs diff --git a/lexical-benchmark/Cargo.toml b/extras/benchmark/Cargo.toml similarity index 100% rename from lexical-benchmark/Cargo.toml rename to extras/benchmark/Cargo.toml diff --git a/lexical-benchmark/README.md b/extras/benchmark/README.md similarity index 85% rename from lexical-benchmark/README.md rename to extras/benchmark/README.md index 3fa08179..724bd253 100644 --- a/lexical-benchmark/README.md +++ b/extras/benchmark/README.md @@ -12,5 +12,3 @@ The benchmark requires the following: 4. An installation of [Rust](https://doc.rust-lang.org/1.0.0/book/installing-rust.html). 5. An installation of Google [Benchmark](https://github.com/google/benchmark). 6. An installation of [CMake](https://cmake.org/download/). - -The use of a Rust version >= 1.59.0, with the feature `asm`, is highly recommended for better metrics and/or performance. diff --git a/lexical-benchmark/algorithm/Cargo.toml b/extras/benchmark/algorithm/Cargo.toml similarity index 91% rename from lexical-benchmark/algorithm/Cargo.toml rename to extras/benchmark/algorithm/Cargo.toml index 67f7d84f..b22da46a 100644 --- a/lexical-benchmark/algorithm/Cargo.toml +++ b/extras/benchmark/algorithm/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" publish = false [dependencies.lexical-util] -path = "../../lexical-util" +path = "../../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-float] -path = "../../lexical-parse-float" +path = "../../../lexical-parse-float" default-features = false features = [] diff --git a/lexical-benchmark/algorithm/bigint.rs b/extras/benchmark/algorithm/bigint.rs similarity index 100% rename from lexical-benchmark/algorithm/bigint.rs rename to extras/benchmark/algorithm/bigint.rs diff --git a/lexical-benchmark/algorithm/division.rs b/extras/benchmark/algorithm/division.rs similarity index 100% rename from lexical-benchmark/algorithm/division.rs rename to extras/benchmark/algorithm/division.rs diff --git a/lexical-benchmark/algorithm/input.rs b/extras/benchmark/algorithm/input.rs similarity index 100% rename from lexical-benchmark/algorithm/input.rs rename to extras/benchmark/algorithm/input.rs diff --git a/lexical-benchmark/clippy.toml b/extras/benchmark/clippy.toml similarity index 100% rename from lexical-benchmark/clippy.toml rename to extras/benchmark/clippy.toml diff --git a/lexical-benchmark/compare.py b/extras/benchmark/compare.py similarity index 100% rename from lexical-benchmark/compare.py rename to extras/benchmark/compare.py diff --git a/lexical-benchmark/data b/extras/benchmark/data similarity index 100% rename from lexical-benchmark/data rename to extras/benchmark/data diff --git a/lexical-benchmark/etc/plot.py b/extras/benchmark/etc/plot.py similarity index 100% rename from lexical-benchmark/etc/plot.py rename to extras/benchmark/etc/plot.py diff --git a/lexical-benchmark/etc/run.py b/extras/benchmark/etc/run.py similarity index 100% rename from lexical-benchmark/etc/run.py rename to extras/benchmark/etc/run.py diff --git a/lexical-benchmark/input.rs b/extras/benchmark/input.rs similarity index 100% rename from lexical-benchmark/input.rs rename to extras/benchmark/input.rs diff --git a/lexical-benchmark/parse-float/Cargo.toml b/extras/benchmark/parse-float/Cargo.toml similarity index 95% rename from lexical-benchmark/parse-float/Cargo.toml rename to extras/benchmark/parse-float/Cargo.toml index b2653d96..a6f5e224 100644 --- a/lexical-benchmark/parse-float/Cargo.toml +++ b/extras/benchmark/parse-float/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" publish = false [dependencies.lexical-util] -path = "../../lexical-util" +path = "../../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-float] -path = "../../lexical-parse-float" +path = "../../../lexical-parse-float" default-features = false features = [] diff --git a/lexical-benchmark/parse-float/black_box.rs b/extras/benchmark/parse-float/black_box.rs similarity index 100% rename from lexical-benchmark/parse-float/black_box.rs rename to extras/benchmark/parse-float/black_box.rs diff --git a/lexical-benchmark/parse-float/canada.rs b/extras/benchmark/parse-float/canada.rs similarity index 100% rename from lexical-benchmark/parse-float/canada.rs rename to extras/benchmark/parse-float/canada.rs diff --git a/lexical-benchmark/parse-float/contrived.rs b/extras/benchmark/parse-float/contrived.rs similarity index 100% rename from lexical-benchmark/parse-float/contrived.rs rename to extras/benchmark/parse-float/contrived.rs diff --git a/lexical-benchmark/parse-float/denormal30.rs b/extras/benchmark/parse-float/denormal30.rs similarity index 100% rename from lexical-benchmark/parse-float/denormal30.rs rename to extras/benchmark/parse-float/denormal30.rs diff --git a/lexical-benchmark/parse-float/denormal6400.rs b/extras/benchmark/parse-float/denormal6400.rs similarity index 100% rename from lexical-benchmark/parse-float/denormal6400.rs rename to extras/benchmark/parse-float/denormal6400.rs diff --git a/lexical-benchmark/parse-float/earth.rs b/extras/benchmark/parse-float/earth.rs similarity index 100% rename from lexical-benchmark/parse-float/earth.rs rename to extras/benchmark/parse-float/earth.rs diff --git a/lexical-benchmark/parse-float/input.rs b/extras/benchmark/parse-float/input.rs similarity index 100% rename from lexical-benchmark/parse-float/input.rs rename to extras/benchmark/parse-float/input.rs diff --git a/lexical-benchmark/parse-float/json.rs b/extras/benchmark/parse-float/json.rs similarity index 100% rename from lexical-benchmark/parse-float/json.rs rename to extras/benchmark/parse-float/json.rs diff --git a/lexical-benchmark/parse-float/mesh.rs b/extras/benchmark/parse-float/mesh.rs similarity index 100% rename from lexical-benchmark/parse-float/mesh.rs rename to extras/benchmark/parse-float/mesh.rs diff --git a/lexical-benchmark/parse-float/perf.sh b/extras/benchmark/parse-float/perf.sh old mode 100755 new mode 100644 similarity index 100% rename from lexical-benchmark/parse-float/perf.sh rename to extras/benchmark/parse-float/perf.sh diff --git a/lexical-benchmark/parse-float/random.rs b/extras/benchmark/parse-float/random.rs similarity index 100% rename from lexical-benchmark/parse-float/random.rs rename to extras/benchmark/parse-float/random.rs diff --git a/lexical-benchmark/parse-integer/Cargo.toml b/extras/benchmark/parse-integer/Cargo.toml similarity index 93% rename from lexical-benchmark/parse-integer/Cargo.toml rename to extras/benchmark/parse-integer/Cargo.toml index cf91d7fc..df8b28db 100644 --- a/lexical-benchmark/parse-integer/Cargo.toml +++ b/extras/benchmark/parse-integer/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" publish = false [dependencies.lexical-util] -path = "../../lexical-util" +path = "../../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-integer] -path = "../../lexical-parse-integer" +path = "../../../lexical-parse-integer" default-features = false features = [] diff --git a/lexical-benchmark/parse-integer/input.rs b/extras/benchmark/parse-integer/input.rs similarity index 100% rename from lexical-benchmark/parse-integer/input.rs rename to extras/benchmark/parse-integer/input.rs diff --git a/lexical-benchmark/parse-integer/json.rs b/extras/benchmark/parse-integer/json.rs similarity index 100% rename from lexical-benchmark/parse-integer/json.rs rename to extras/benchmark/parse-integer/json.rs diff --git a/lexical-benchmark/parse-integer/random.rs b/extras/benchmark/parse-integer/random.rs similarity index 100% rename from lexical-benchmark/parse-integer/random.rs rename to extras/benchmark/parse-integer/random.rs diff --git a/lexical-benchmark/profiling.py b/extras/benchmark/profiling.py similarity index 100% rename from lexical-benchmark/profiling.py rename to extras/benchmark/profiling.py diff --git a/lexical-benchmark/results/parse-float.json b/extras/benchmark/results/parse-float.json similarity index 100% rename from lexical-benchmark/results/parse-float.json rename to extras/benchmark/results/parse-float.json diff --git a/lexical-benchmark/results/parse-float_features=compact.json b/extras/benchmark/results/parse-float_features=compact.json similarity index 100% rename from lexical-benchmark/results/parse-float_features=compact.json rename to extras/benchmark/results/parse-float_features=compact.json diff --git a/lexical-benchmark/results/parse-integer.json b/extras/benchmark/results/parse-integer.json similarity index 100% rename from lexical-benchmark/results/parse-integer.json rename to extras/benchmark/results/parse-integer.json diff --git a/lexical-benchmark/results/parse-integer_features=compact.json b/extras/benchmark/results/parse-integer_features=compact.json similarity index 100% rename from lexical-benchmark/results/parse-integer_features=compact.json rename to extras/benchmark/results/parse-integer_features=compact.json diff --git a/lexical-benchmark/results/parse-integer_features=radix.json b/extras/benchmark/results/parse-integer_features=radix.json similarity index 100% rename from lexical-benchmark/results/parse-integer_features=radix.json rename to extras/benchmark/results/parse-integer_features=radix.json diff --git a/lexical-benchmark/results/write-float.json b/extras/benchmark/results/write-float.json similarity index 100% rename from lexical-benchmark/results/write-float.json rename to extras/benchmark/results/write-float.json diff --git a/lexical-benchmark/results/write-float_features=compact.json b/extras/benchmark/results/write-float_features=compact.json similarity index 100% rename from lexical-benchmark/results/write-float_features=compact.json rename to extras/benchmark/results/write-float_features=compact.json diff --git a/lexical-benchmark/results/write-integer.json b/extras/benchmark/results/write-integer.json similarity index 100% rename from lexical-benchmark/results/write-integer.json rename to extras/benchmark/results/write-integer.json diff --git a/lexical-benchmark/results/write-integer_features=compact.json b/extras/benchmark/results/write-integer_features=compact.json similarity index 100% rename from lexical-benchmark/results/write-integer_features=compact.json rename to extras/benchmark/results/write-integer_features=compact.json diff --git a/lexical-benchmark/results/write-integer_features=radix.json b/extras/benchmark/results/write-integer_features=radix.json similarity index 100% rename from lexical-benchmark/results/write-integer_features=radix.json rename to extras/benchmark/results/write-integer_features=radix.json diff --git a/lexical-benchmark/rustfmt.toml b/extras/benchmark/rustfmt.toml similarity index 100% rename from lexical-benchmark/rustfmt.toml rename to extras/benchmark/rustfmt.toml diff --git a/lexical-benchmark/write-float/Cargo.toml b/extras/benchmark/write-float/Cargo.toml similarity index 94% rename from lexical-benchmark/write-float/Cargo.toml rename to extras/benchmark/write-float/Cargo.toml index c3668b3a..b126b735 100644 --- a/lexical-benchmark/write-float/Cargo.toml +++ b/extras/benchmark/write-float/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" publish = false [dependencies.lexical-util] -path = "../../lexical-util" +path = "../../../lexical-util" default-features = false features = [] [dependencies.lexical-write-float] -path = "../../lexical-write-float" +path = "../../../lexical-write-float" default-features = false features = [] diff --git a/lexical-benchmark/write-float/input.rs b/extras/benchmark/write-float/input.rs similarity index 100% rename from lexical-benchmark/write-float/input.rs rename to extras/benchmark/write-float/input.rs diff --git a/lexical-benchmark/write-float/json.rs b/extras/benchmark/write-float/json.rs similarity index 100% rename from lexical-benchmark/write-float/json.rs rename to extras/benchmark/write-float/json.rs diff --git a/lexical-benchmark/write-float/random.rs b/extras/benchmark/write-float/random.rs similarity index 100% rename from lexical-benchmark/write-float/random.rs rename to extras/benchmark/write-float/random.rs diff --git a/lexical-benchmark/write-float/special.rs b/extras/benchmark/write-float/special.rs similarity index 100% rename from lexical-benchmark/write-float/special.rs rename to extras/benchmark/write-float/special.rs diff --git a/lexical-benchmark/write-integer/Cargo.toml b/extras/benchmark/write-integer/Cargo.toml similarity index 93% rename from lexical-benchmark/write-integer/Cargo.toml rename to extras/benchmark/write-integer/Cargo.toml index 51258cea..d5fc9b87 100644 --- a/lexical-benchmark/write-integer/Cargo.toml +++ b/extras/benchmark/write-integer/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" publish = false [dependencies.lexical-util] -path = "../../lexical-util" +path = "../../../lexical-util" default-features = false features = [] [dependencies.lexical-write-integer] -path = "../../lexical-write-integer" +path = "../../../lexical-write-integer" default-features = false features = [] diff --git a/lexical-benchmark/write-integer/input.rs b/extras/benchmark/write-integer/input.rs similarity index 100% rename from lexical-benchmark/write-integer/input.rs rename to extras/benchmark/write-integer/input.rs diff --git a/lexical-benchmark/write-integer/json.rs b/extras/benchmark/write-integer/json.rs similarity index 100% rename from lexical-benchmark/write-integer/json.rs rename to extras/benchmark/write-integer/json.rs diff --git a/lexical-benchmark/write-integer/random.rs b/extras/benchmark/write-integer/random.rs similarity index 100% rename from lexical-benchmark/write-integer/random.rs rename to extras/benchmark/write-integer/random.rs diff --git a/extras/clippy.toml b/extras/clippy.toml new file mode 100644 index 00000000..8bd81b7e --- /dev/null +++ b/extras/clippy.toml @@ -0,0 +1,20 @@ +avoid-breaking-exported-api = false +disallowed-macros = [ + # Can also use an inline table with a `path` key. + { path = "std::print", reason = "no IO allowed" }, + { path = "std::println", reason = "no IO allowed" }, + { path = "std::format", reason = "no string allocation allowed" }, + { path = "std::debug", reason = "debugging macros should not be present in any release" }, + # NOTE: unimplemented is fine because this can be for intentionally disabled methods + { path = "std::todo", reason = "should never have TODO macros in releases" }, +] +disallowed-methods = [ + { path = "std::io::stdout", reason = "no IO allowed" }, + { path = "std::io::stdin", reason = "no IO allowed" }, + { path = "std::io::stderr", reason = "no IO allowed" }, +] +disallowed-types = [ + { path = "std::io::File", reason = "no IO allowed" }, + { path = "std::io::BufReader", reason = "need our own abstractions for reading/writing" }, + { path = "std::io::BufWriter", reason = "need our own abstractions for reading/writing" }, +] diff --git a/extras/core/Cargo.toml b/extras/core/Cargo.toml new file mode 100644 index 00000000..685abcfe --- /dev/null +++ b/extras/core/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-core-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-core] +default-features = false +path = "../../lexical-core" + +[dev-dependencies] +approx = "0.5.0" + +[features] +# Need to enable all for backwards compatibility. +default = ["std", "write-integers", "write-floats", "parse-integers", "parse-floats"] +std = ["lexical-core/std"] +write-integers = ["lexical-core/write-integers"] +write-floats = ["lexical-core/write-floats"] +parse-integers = ["lexical-core/parse-integers"] +parse-floats = ["lexical-core/parse-floats"] +power-of-two = ["lexical-core/power-of-two"] +radix = ["lexical-core/radix", "power-of-two"] +format = ["lexical-core/format"] +compact = ["lexical-core/compact"] +f16 = ["lexical-core/f16"] +lint = ["lexical-core/lint"] diff --git a/extras/core/src/lib.rs b/extras/core/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/core/src/lib.rs @@ -0,0 +1 @@ + diff --git a/lexical-core/tests/float_pow2_tests.rs b/extras/core/tests/float_pow2_tests.rs similarity index 100% rename from lexical-core/tests/float_pow2_tests.rs rename to extras/core/tests/float_pow2_tests.rs diff --git a/lexical-core/tests/float_radix_tests.rs b/extras/core/tests/float_radix_tests.rs similarity index 100% rename from lexical-core/tests/float_radix_tests.rs rename to extras/core/tests/float_radix_tests.rs diff --git a/extras/parse-float/Cargo.toml b/extras/parse-float/Cargo.toml new file mode 100644 index 00000000..07b4ef97 --- /dev/null +++ b/extras/parse-float/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-parse-float-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-util] +path = "../../lexical-util" +default-features = false + +[dependencies.lexical-parse-float] +path = "../../lexical-parse-float" +default-features = false + +[dev-dependencies] +# FIXME: Replace back to "1.0.4" once the PR is merged. +# There's an issue in quickcheck due to an infinitely repeating shrinker. +# Issue: https://github.com/BurntSushi/quickcheck/issues/295 +# Fix: https://github.com/BurntSushi/quickcheck/pull/296 +quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } +proptest = ">=1.5.0" + +[features] +default = ["std"] +std = ["lexical-util/std", "lexical-parse-float/std"] +power-of-two = ["lexical-util/power-of-two", "lexical-parse-float/power-of-two"] +radix = ["lexical-util/radix", "power-of-two", "lexical-parse-float/radix"] +format = ["lexical-util/format", "lexical-parse-float/format"] +compact = ["lexical-util/compact", "lexical-parse-float/compact"] +f16 = ["lexical-util/f16", "lexical-parse-float/f16"] +lint = ["lexical-util/lint", "lexical-parse-float/lint"] diff --git a/extras/parse-float/src/lib.rs b/extras/parse-float/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/parse-float/src/lib.rs @@ -0,0 +1 @@ + diff --git a/extras/parse-float/tests/api_tests.rs b/extras/parse-float/tests/api_tests.rs new file mode 100644 index 00000000..6a98ad3f --- /dev/null +++ b/extras/parse-float/tests/api_tests.rs @@ -0,0 +1,227 @@ +mod util; + +use lexical_parse_float::FromLexical; +#[cfg(feature = "f16")] +use lexical_util::bf16::bf16; +#[cfg(feature = "f16")] +use lexical_util::f16::f16; +use lexical_util::num::Float; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +fn float_equal(x: F, y: F) -> bool { + if x.is_nan() { + y.is_nan() + } else { + y == x + } +} + +default_quickcheck! { + fn f32_roundtrip_quickcheck(x: f32) -> bool { + let string = x.to_string(); + let result = f32::from_lexical(string.as_bytes()); + result.map_or(false, |y| float_equal(x, y)) + } + + fn f32_short_decimal_quickcheck(x: f32) -> bool { + let string = format!("{:.4}", x); + let actual = f32::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f32_long_decimal_quickcheck(x: f32) -> bool { + let string = format!("{:.100}", x); + let actual = f32::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f32_short_exponent_quickcheck(x: f32) -> bool { + let string = format!("{:.4e}", x); + let actual = f32::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f32_long_exponent_quickcheck(x: f32) -> bool { + let string = format!("{:.100e}", x); + let actual = f32::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f64_roundtrip_quickcheck(x: f64) -> bool { + let string = x.to_string(); + let result = f64::from_lexical(string.as_bytes()); + result.map_or(false, |y| float_equal(x, y)) + } + + fn f64_short_decimal_quickcheck(x: f64) -> bool { + let string = format!("{:.4}", x); + let actual = f64::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f64_long_decimal_quickcheck(x: f64) -> bool { + let string = format!("{:.100}", x); + let actual = f64::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f64_short_exponent_quickcheck(x: f64) -> bool { + let string = format!("{:.4e}", x); + let actual = f64::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + fn f64_long_exponent_quickcheck(x: f64) -> bool { + let string = format!("{:.100e}", x); + let actual = f64::from_lexical(string.as_bytes()); + let expected = string.parse::(); + actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) + } + + #[cfg(feature = "f16")] + fn f16_roundtrip_quickcheck(bits: u16) -> bool { + let x = f16::from_bits(bits); + let string = x.as_f32().to_string(); + let result = f16::from_lexical(string.as_bytes()); + result.map_or(false, |y| float_equal(x, y)) + } + + #[cfg(feature = "f16")] + fn bf16_roundtrip_quickcheck(bits: u16) -> bool { + let x = bf16::from_bits(bits); + let string = x.as_f32().to_string(); + let result = bf16::from_lexical(string.as_bytes()); + result.map_or(false, |y| float_equal(x, y)) + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { + let res = f32::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_invalid_digit()); + } + + #[test] + fn f32_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { + let res = f32::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!( + res.err().unwrap().is_invalid_digit() || + res.err().unwrap().is_empty_mantissa() + ); + } + + #[test] + fn f32_sign_or_dot_only_proptest(i in r"[+-]?\.?") { + let res = f32::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!( + res.err().unwrap().is_empty() || + res.err().unwrap().is_empty_mantissa() + ); + } + + #[test] + fn f32_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { + let res = f32::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_empty_exponent()); + } + + #[test] + fn f32_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { + let res = f32::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_empty_exponent()); + } + + #[test] + fn f32_roundtrip_display_proptest(i in f32::MIN..f32::MAX) { + let input: String = format!("{}", i); + prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); + } + + #[test] + fn f32_roundtrip_debug_proptest(i in f32::MIN..f32::MAX) { + let input: String = format!("{:?}", i); + prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); + } + + #[test] + fn f32_roundtrip_scientific_proptest(i in f32::MIN..f32::MAX) { + let input: String = format!("{:e}", i); + prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); + } + + #[test] + fn f64_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { + let res = f64::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_invalid_digit()); + } + + #[test] + fn f64_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { + let res = f64::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!( + res.err().unwrap().is_invalid_digit() || + res.err().unwrap().is_empty_mantissa() + ); + } + + #[test] + fn f64_sign_or_dot_only_proptest(i in r"[+-]?\.?") { + let res = f64::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!( + res.err().unwrap().is_empty() || + res.err().unwrap().is_empty_mantissa() + ); + } + + #[test] + fn f64_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { + let res = f64::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_empty_exponent()); + } + + #[test] + fn f64_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { + let res = f64::from_lexical(i.as_bytes()); + prop_assert!(res.is_err()); + prop_assert!(res.err().unwrap().is_empty_exponent()); + } + + #[test] + fn f64_roundtrip_display_proptest(i in f64::MIN..f64::MAX) { + let input: String = format!("{}", i); + prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); + } + + #[test] + fn f64_roundtrip_debug_proptest(i in f64::MIN..f64::MAX) { + let input: String = format!("{:?}", i); + prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); + } + + #[test] + fn f64_roundtrip_scientific_proptest(i in f64::MIN..f64::MAX) { + let input: String = format!("{:e}", i); + prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); + } +} diff --git a/lexical-parse-float/tests/util.rs b/extras/parse-float/tests/util.rs similarity index 100% rename from lexical-parse-float/tests/util.rs rename to extras/parse-float/tests/util.rs diff --git a/extras/parse-integer/Cargo.toml b/extras/parse-integer/Cargo.toml new file mode 100644 index 00000000..e7fd8bb7 --- /dev/null +++ b/extras/parse-integer/Cargo.toml @@ -0,0 +1,35 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-parse-integer-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-util] +path = "../../lexical-util" +default-features = false + +[dependencies.lexical-parse-integer] +path = "../../lexical-parse-integer" +default-features = false + +[dev-dependencies] +# FIXME: Replace back to "1.0.4" once the PR is merged. +# There's an issue in quickcheck due to an infinitely repeating shrinker. +# Issue: https://github.com/BurntSushi/quickcheck/issues/295 +# Fix: https://github.com/BurntSushi/quickcheck/pull/296 +quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } +proptest = ">=1.5.0" + +[features] +default = ["std"] +std = ["lexical-util/std", "lexical-parse-integer/std"] +power-of-two = ["lexical-util/power-of-two", "lexical-parse-integer/power-of-two"] +radix = ["lexical-util/radix", "lexical-parse-integer/radix", "power-of-two"] +format = ["lexical-util/format", "lexical-parse-integer/format"] +compact = ["lexical-util/compact", "lexical-parse-integer/compact"] +lint = ["lexical-util/lint", "lexical-parse-integer/lint"] diff --git a/extras/parse-integer/src/lib.rs b/extras/parse-integer/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/parse-integer/src/lib.rs @@ -0,0 +1 @@ + diff --git a/extras/parse-integer/tests/algorithm_tests.rs b/extras/parse-integer/tests/algorithm_tests.rs new file mode 100644 index 00000000..ffcd36ac --- /dev/null +++ b/extras/parse-integer/tests/algorithm_tests.rs @@ -0,0 +1,49 @@ +#![cfg(not(feature = "compact"))] + +mod util; + +use lexical_parse_integer::algorithm; +use lexical_util::format::STANDARD; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn parse_4digits_proptest( + a in 0x30u32..0x39, + b in 0x30u32..0x39, + c in 0x30u32..0x39, + d in 0x30u32..0x39, + ) + { + let v = (a << 24) | (b << 16) | (c << 8) | d; + let actual = algorithm::parse_4digits::<{ STANDARD }>(v); + let expected = (a - 0x30) + 10 * (b - 0x30) + 100 * (c - 0x30) + 1000 * (d - 0x30); + prop_assert_eq!(actual, expected); + } + + #[test] + fn parse_8digits_proptest( + a in 0x30u64..0x39, + b in 0x30u64..0x39, + c in 0x30u64..0x39, + d in 0x30u64..0x39, + e in 0x30u64..0x39, + f in 0x30u64..0x39, + g in 0x30u64..0x39, + h in 0x30u64..0x39, + ) + { + let v1 = (a << 24) | (b << 16) | (c << 8) | d; + let v2 = (e << 24) | (f << 16) | (g << 8) | h; + let v = (v1 << 32) | v2; + let actual = algorithm::parse_8digits::<{ STANDARD }>(v); + let e1 = (a - 0x30) + 10 * (b - 0x30) + 100 * (c - 0x30) + 1000 * (d - 0x30); + let e2 = (e - 0x30) + 10 * (f - 0x30) + 100 * (g - 0x30) + 1000 * (h - 0x30); + let expected = e1 + 10000 * e2; + prop_assert_eq!(actual, expected); + } +} diff --git a/extras/parse-integer/tests/api_tests.rs b/extras/parse-integer/tests/api_tests.rs new file mode 100644 index 00000000..e51e571d --- /dev/null +++ b/extras/parse-integer/tests/api_tests.rs @@ -0,0 +1,371 @@ +mod util; + +use lexical_parse_integer::FromLexical; +#[cfg(feature = "power-of-two")] +use lexical_parse_integer::{FromLexicalWithOptions, Options}; +use proptest::prelude::*; +#[cfg(feature = "power-of-two")] +use util::from_radix; + +use crate::util::default_proptest_config; + +macro_rules! is_error { + ($result:expr, $check:ident) => {{ + let result = $result; + prop_assert!(result.is_err()); + let err = result.err().unwrap(); + prop_assert!(err.$check()); + }}; +} + +macro_rules! is_invalid_digit { + ($result:expr) => { + is_error!($result, is_invalid_digit) + }; +} + +macro_rules! is_empty { + ($result:expr) => { + is_error!($result, is_empty) + }; +} + +macro_rules! is_overflow { + ($result:expr) => { + is_error!($result, is_overflow) + }; +} + +macro_rules! is_underflow { + ($result:expr) => { + is_error!($result, is_underflow) + }; +} + +macro_rules! is_invalid_digit_match { + ($result:expr, $p1:pat_param $(| $prest:pat_param)*) => {{ + let result = $result; + prop_assert!(result.is_err()); + let err = result.err().unwrap(); + prop_assert!(err.is_invalid_digit()); + prop_assert!(matches!(*err.index().unwrap(), $p1 $(| $prest)*)); + }}; +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + #[cfg(feature = "power-of-two")] + fn i32_binary_roundtrip_display_proptest(i in i32::MIN..i32::MAX) { + let options = Options::new(); + const FORMAT: u128 = from_radix(2); + let digits = if i < 0 { + format!("-{:b}", (i as i64).wrapping_neg()) + } else { + format!("{:b}", i) + }; + let result = i32::from_lexical_with_options::(digits.as_bytes(), &options); + prop_assert_eq!(i, result.unwrap()); + } + + #[test] + fn u8_invalid_proptest(i in r"[+]?[0-9]{2}\D") { + is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); + } + + #[test] + fn u8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { + is_overflow!(u8::from_lexical(i.as_bytes())); + } + + #[test] + fn u8_negative_proptest(i in r"[-][1-9][0-9]{2}") { + is_invalid_digit!(u8::from_lexical(i.as_bytes())); + } + + #[test] + fn u8_double_sign_proptest(i in r"[+]{2}[0-9]{2}") { + is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn u8_sign_only_proptest(i in r"[+]") { + is_empty!(u8::from_lexical(i.as_bytes())); + } + + #[test] + fn u8_trailing_digits_proptest(i in r"[+]?[0-9]{2}\D[0-9]{2}") { + is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); + } + + #[test] + fn i8_invalid_proptest(i in r"[+-]?[0-9]{2}\D") { + is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); + } + + #[test] + fn i8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { + is_overflow!(i8::from_lexical(i.as_bytes())); + } + + #[test] + fn i8_underflow_proptest(i in r"[-][1-9][0-9]{3}") { + is_underflow!(i8::from_lexical(i.as_bytes())); + } + + #[test] + fn i8_double_sign_proptest(i in r"[+-]{2}[0-9]{2}") { + is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn i8_sign_only_proptest(i in r"[+-]") { + is_empty!(i8::from_lexical(i.as_bytes())); + } + + #[test] + fn i8_trailing_digits_proptest(i in r"[+-]?[0-9]{2}\D[0-9]{2}") { + is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); + } + + #[test] + fn u16_invalid_proptest(i in r"[+]?[0-9]{4}\D") { + is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); + } + + #[test] + fn u16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { + is_overflow!(u16::from_lexical(i.as_bytes())); + } + + #[test] + fn u16_negative_proptest(i in r"[-][1-9][0-9]{4}") { + is_invalid_digit!(u16::from_lexical(i.as_bytes())); + } + + #[test] + fn u16_double_sign_proptest(i in r"[+]{2}[0-9]{4}") { + is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn u16_sign_only_proptest(i in r"[+]") { + is_empty!(u16::from_lexical(i.as_bytes())); + } + + #[test] + fn u16_trailing_digits_proptest(i in r"[+]?[0-9]{4}\D[0-9]{2}") { + is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); + } + + #[test] + fn i16_invalid_proptest(i in r"[+-]?[0-9]{4}\D") { + is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); + } + + #[test] + fn i16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { + is_overflow!(i16::from_lexical(i.as_bytes())); + } + + #[test] + fn i16_underflow_proptest(i in r"[-][1-9][0-9]{5}") { + is_underflow!(i16::from_lexical(i.as_bytes())); + } + + #[test] + fn i16_double_sign_proptest(i in r"[+-]{2}[0-9]{4}") { + is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn i16_sign_only_proptest(i in r"[+-]") { + is_empty!(i16::from_lexical(i.as_bytes())); + } + + #[test] + fn i16_trailing_digits_proptest(i in r"[+-]?[0-9]{4}\D[0-9]{2}") { + is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); + } + + #[test] + fn u32_invalid_proptest(i in r"[+]?[0-9]{9}\D") { + is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); + } + + #[test] + fn u32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { + is_overflow!(u32::from_lexical(i.as_bytes())); + } + + #[test] + fn u32_negative_proptest(i in r"[-][1-9][0-9]{9}") { + is_invalid_digit!(u32::from_lexical(i.as_bytes())); + } + + #[test] + fn u32_double_sign_proptest(i in r"[+]{2}[0-9]{9}") { + is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn u32_sign_only_proptest(i in r"[+]") { + is_empty!(u32::from_lexical(i.as_bytes())); + } + + #[test] + fn u32_trailing_digits_proptest(i in r"[+]?[0-9]{9}\D[0-9]{2}") { + is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); + } + + #[test] + fn i32_invalid_proptest(i in r"[+-]?[0-9]{9}\D") { + is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); + } + + #[test] + fn i32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { + is_overflow!(i32::from_lexical(i.as_bytes())); + } + + #[test] + fn i32_underflow_proptest(i in r"-[1-9][0-9]{10}") { + is_underflow!(i32::from_lexical(i.as_bytes())); + } + + #[test] + fn i32_double_sign_proptest(i in r"[+-]{2}[0-9]{9}") { + is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn i32_sign_only_proptest(i in r"[+-]") { + is_empty!(i32::from_lexical(i.as_bytes())); + } + + #[test] + fn i32_trailing_digits_proptest(i in r"[+-]?[0-9]{9}\D[0-9]{2}") { + is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); + } + + #[test] + fn u64_invalid_proptest(i in r"[+]?[0-9]{19}\D") { + is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); + } + + #[test] + fn u64_overflow_proptest(i in r"[+]?[1-9][0-9]{21}") { + is_overflow!(u64::from_lexical(i.as_bytes())); + } + + #[test] + fn u64_negative_proptest(i in r"[-][1-9][0-9]{21}") { + is_invalid_digit!(u64::from_lexical(i.as_bytes())); + } + + #[test] + fn u64_double_sign_proptest(i in r"[+]{2}[0-9]{19}") { + is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn u64_sign_only_proptest(i in r"[+]") { + is_empty!(u64::from_lexical(i.as_bytes())); + } + + #[test] + fn u64_trailing_digits_proptest(i in r"[+]?[0-9]{19}\D[0-9]{2}") { + is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); + } + + #[test] + fn i64_invalid_proptest(i in r"[+-]?[0-9]{18}\D") { + is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); + } + + #[test] + fn i64_overflow_proptest(i in r"[+]?[1-9][0-9]{19}") { + is_overflow!(i64::from_lexical(i.as_bytes())); + } + + #[test] + fn i64_underflow_proptest(i in r"-[1-9][0-9]{19}") { + is_underflow!(i64::from_lexical(i.as_bytes())); + } + + #[test] + fn i64_double_sign_proptest(i in r"[+-]{2}[0-9]{18}") { + is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn i64_sign_only_proptest(i in r"[+-]") { + is_empty!(i64::from_lexical(i.as_bytes())); + } + + #[test] + fn i64_trailing_digits_proptest(i in r"[+-]?[0-9]{18}\D[0-9]{2}") { + is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); + } + + #[test] + fn u128_invalid_proptest(i in r"[+]?[0-9]{38}\D") { + is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); + } + + #[test] + fn u128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { + is_overflow!(u128::from_lexical(i.as_bytes())); + } + + #[test] + fn u128_negative_proptest(i in r"[-][1-9][0-9]{39}") { + is_invalid_digit!(u128::from_lexical(i.as_bytes())); + } + + #[test] + fn u128_double_sign_proptest(i in r"[+]{2}[0-9]{38}") { + is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn u128_sign_only_proptest(i in r"[+]") { + is_empty!(u128::from_lexical(i.as_bytes())); + } + + #[test] + fn u128_trailing_digits_proptest(i in r"[+]?[0-9]{38}\D[0-9]{2}") { + is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); + } + + #[test] + fn i128_invalid_proptest(i in r"[+-]?[0-9]{38}\D") { + is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); + } + + #[test] + fn i128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { + is_overflow!(i128::from_lexical(i.as_bytes())); + } + + #[test] + fn i128_underflow_proptest(i in r"-[1-9][0-9]{39}") { + is_underflow!(i128::from_lexical(i.as_bytes())); + } + + #[test] + fn i128_double_sign_proptest(i in r"[+-]{2}[0-9]{38}") { + is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 1); + } + + #[test] + fn i128_sign_only_proptest(i in r"[+-]") { + is_empty!(i128::from_lexical(i.as_bytes())); + } + + #[test] + fn i128_trailing_digits_proptest(i in r"[+-]?[0-9]{38}\D[0-9]{2}") { + is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); + } +} diff --git a/extras/parse-integer/tests/util.rs b/extras/parse-integer/tests/util.rs new file mode 100644 index 00000000..d846be3f --- /dev/null +++ b/extras/parse-integer/tests/util.rs @@ -0,0 +1,64 @@ +#![allow(dead_code, unused_imports)] + +#[cfg(feature = "power-of-two")] +use lexical_util::format::NumberFormatBuilder; +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; + +#[cfg(feature = "power-of-two")] +pub const fn from_radix(radix: u8) -> u128 { + NumberFormatBuilder::from_radix(radix) +} + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default +// arguments https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/extras/rustfmt.toml b/extras/rustfmt.toml new file mode 100644 index 00000000..164014ce --- /dev/null +++ b/extras/rustfmt.toml @@ -0,0 +1,19 @@ +# Requires nightly to do proper formatting. +use_small_heuristics = "Off" +use_field_init_shorthand = true +trailing_semicolon = true +newline_style = "Unix" +match_block_trailing_comma = true +empty_item_single_line = false +enum_discrim_align_threshold = 40 +fn_params_layout = "Tall" +fn_single_line = false +format_macro_matchers = true +format_macro_bodies = true +imports_indent = "Block" +imports_layout = "HorizontalVertical" +indent_style = "Block" +match_arm_blocks = true +overflow_delimited_expr = true +group_imports = "StdExternalCrate" +wrap_comments = true diff --git a/lexical-size/Cargo.toml b/extras/size/Cargo.toml similarity index 92% rename from lexical-size/Cargo.toml rename to extras/size/Cargo.toml index d918a562..434c72c4 100644 --- a/lexical-size/Cargo.toml +++ b/extras/size/Cargo.toml @@ -5,28 +5,32 @@ authors = ["Alex Huszagh "] edition = "2021" publish = false +# NOTE: This needs an empty workspace for our profile settings. +[workspace] +members = [] + [dependencies.lexical-util] -path = "../lexical-util" +path = "../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-integer] -path = "../lexical-parse-integer" +path = "../../lexical-parse-integer" default-features = false features = [] [dependencies.lexical-write-integer] -path = "../lexical-write-integer" +path = "../../lexical-write-integer" default-features = false features = [] [dependencies.lexical-parse-float] -path = "../lexical-parse-float" +path = "../../lexical-parse-float" default-features = false features = [] [dependencies.lexical-write-float] -path = "../lexical-write-float" +path = "../../lexical-write-float" default-features = false features = [] @@ -69,8 +73,6 @@ compact = [ "lexical-parse-float/compact" ] -[workspace] - [[bin]] name = "empty" path = "bin/empty.rs" diff --git a/lexical-size/Cargo.toml.in b/extras/size/Cargo.toml.in similarity index 92% rename from lexical-size/Cargo.toml.in rename to extras/size/Cargo.toml.in index 1412e9fd..06c2c4f3 100644 --- a/lexical-size/Cargo.toml.in +++ b/extras/size/Cargo.toml.in @@ -5,28 +5,32 @@ authors = ["Alex Huszagh "] edition = "2021" publish = false +# NOTE: This needs an empty workspace for our profile settings. +[workspace] +members = [] + [dependencies.lexical-util] -path = "../lexical-util" +path = "../../lexical-util" default-features = false features = [] [dependencies.lexical-parse-integer] -path = "../lexical-parse-integer" +path = "../../lexical-parse-integer" default-features = false features = [] [dependencies.lexical-write-integer] -path = "../lexical-write-integer" +path = "../../lexical-write-integer" default-features = false features = [] [dependencies.lexical-parse-float] -path = "../lexical-parse-float" +path = "../../lexical-parse-float" default-features = false features = [] [dependencies.lexical-write-float] -path = "../lexical-write-float" +path = "../../lexical-write-float" default-features = false features = [] @@ -69,8 +73,6 @@ compact = [ "lexical-parse-float/compact" ] -[workspace] - [[bin]] name = "empty" path = "bin/empty.rs" diff --git a/lexical-size/README.md b/extras/size/README.md similarity index 100% rename from lexical-size/README.md rename to extras/size/README.md diff --git a/lexical-size/bin/core_parse.rs b/extras/size/bin/core_parse.rs similarity index 99% rename from lexical-size/bin/core_parse.rs rename to extras/size/bin/core_parse.rs index b0dd44ac..b8ea3e70 100644 --- a/lexical-size/bin/core_parse.rs +++ b/extras/size/bin/core_parse.rs @@ -15,6 +15,7 @@ pub struct ParseIntError { pub kind: IntErrorKind, } +#[allow(dead_code)] pub trait FromStrRadixHelper: PartialOrd + Copy { fn min_value() -> Self; fn max_value() -> Self; diff --git a/lexical-size/bin/empty.rs b/extras/size/bin/empty.rs similarity index 100% rename from lexical-size/bin/empty.rs rename to extras/size/bin/empty.rs diff --git a/lexical-size/bin/parse-float-f32.rs b/extras/size/bin/parse-float-f32.rs similarity index 100% rename from lexical-size/bin/parse-float-f32.rs rename to extras/size/bin/parse-float-f32.rs diff --git a/lexical-size/bin/parse-float-f64.rs b/extras/size/bin/parse-float-f64.rs similarity index 100% rename from lexical-size/bin/parse-float-f64.rs rename to extras/size/bin/parse-float-f64.rs diff --git a/lexical-size/bin/parse-integer-i128.rs b/extras/size/bin/parse-integer-i128.rs similarity index 100% rename from lexical-size/bin/parse-integer-i128.rs rename to extras/size/bin/parse-integer-i128.rs diff --git a/lexical-size/bin/parse-integer-i16.rs b/extras/size/bin/parse-integer-i16.rs similarity index 100% rename from lexical-size/bin/parse-integer-i16.rs rename to extras/size/bin/parse-integer-i16.rs diff --git a/lexical-size/bin/parse-integer-i32.rs b/extras/size/bin/parse-integer-i32.rs similarity index 100% rename from lexical-size/bin/parse-integer-i32.rs rename to extras/size/bin/parse-integer-i32.rs diff --git a/lexical-size/bin/parse-integer-i64.rs b/extras/size/bin/parse-integer-i64.rs similarity index 100% rename from lexical-size/bin/parse-integer-i64.rs rename to extras/size/bin/parse-integer-i64.rs diff --git a/lexical-size/bin/parse-integer-i8.rs b/extras/size/bin/parse-integer-i8.rs similarity index 100% rename from lexical-size/bin/parse-integer-i8.rs rename to extras/size/bin/parse-integer-i8.rs diff --git a/lexical-size/bin/parse-integer-u128.rs b/extras/size/bin/parse-integer-u128.rs similarity index 100% rename from lexical-size/bin/parse-integer-u128.rs rename to extras/size/bin/parse-integer-u128.rs diff --git a/lexical-size/bin/parse-integer-u16.rs b/extras/size/bin/parse-integer-u16.rs similarity index 100% rename from lexical-size/bin/parse-integer-u16.rs rename to extras/size/bin/parse-integer-u16.rs diff --git a/lexical-size/bin/parse-integer-u32.rs b/extras/size/bin/parse-integer-u32.rs similarity index 100% rename from lexical-size/bin/parse-integer-u32.rs rename to extras/size/bin/parse-integer-u32.rs diff --git a/lexical-size/bin/parse-integer-u64.rs b/extras/size/bin/parse-integer-u64.rs similarity index 100% rename from lexical-size/bin/parse-integer-u64.rs rename to extras/size/bin/parse-integer-u64.rs diff --git a/lexical-size/bin/parse-integer-u8.rs b/extras/size/bin/parse-integer-u8.rs similarity index 100% rename from lexical-size/bin/parse-integer-u8.rs rename to extras/size/bin/parse-integer-u8.rs diff --git a/lexical-size/bin/parse.rs b/extras/size/bin/parse.rs similarity index 100% rename from lexical-size/bin/parse.rs rename to extras/size/bin/parse.rs diff --git a/lexical-size/bin/write-float-f32.rs b/extras/size/bin/write-float-f32.rs similarity index 100% rename from lexical-size/bin/write-float-f32.rs rename to extras/size/bin/write-float-f32.rs diff --git a/lexical-size/bin/write-float-f64.rs b/extras/size/bin/write-float-f64.rs similarity index 100% rename from lexical-size/bin/write-float-f64.rs rename to extras/size/bin/write-float-f64.rs diff --git a/lexical-size/bin/write-integer-i128.rs b/extras/size/bin/write-integer-i128.rs similarity index 100% rename from lexical-size/bin/write-integer-i128.rs rename to extras/size/bin/write-integer-i128.rs diff --git a/lexical-size/bin/write-integer-i16.rs b/extras/size/bin/write-integer-i16.rs similarity index 100% rename from lexical-size/bin/write-integer-i16.rs rename to extras/size/bin/write-integer-i16.rs diff --git a/lexical-size/bin/write-integer-i32.rs b/extras/size/bin/write-integer-i32.rs similarity index 100% rename from lexical-size/bin/write-integer-i32.rs rename to extras/size/bin/write-integer-i32.rs diff --git a/lexical-size/bin/write-integer-i64.rs b/extras/size/bin/write-integer-i64.rs similarity index 100% rename from lexical-size/bin/write-integer-i64.rs rename to extras/size/bin/write-integer-i64.rs diff --git a/lexical-size/bin/write-integer-i8.rs b/extras/size/bin/write-integer-i8.rs similarity index 100% rename from lexical-size/bin/write-integer-i8.rs rename to extras/size/bin/write-integer-i8.rs diff --git a/lexical-size/bin/write-integer-u128.rs b/extras/size/bin/write-integer-u128.rs similarity index 100% rename from lexical-size/bin/write-integer-u128.rs rename to extras/size/bin/write-integer-u128.rs diff --git a/lexical-size/bin/write-integer-u16.rs b/extras/size/bin/write-integer-u16.rs similarity index 100% rename from lexical-size/bin/write-integer-u16.rs rename to extras/size/bin/write-integer-u16.rs diff --git a/lexical-size/bin/write-integer-u32.rs b/extras/size/bin/write-integer-u32.rs similarity index 100% rename from lexical-size/bin/write-integer-u32.rs rename to extras/size/bin/write-integer-u32.rs diff --git a/lexical-size/bin/write-integer-u64.rs b/extras/size/bin/write-integer-u64.rs similarity index 100% rename from lexical-size/bin/write-integer-u64.rs rename to extras/size/bin/write-integer-u64.rs diff --git a/lexical-size/bin/write-integer-u8.rs b/extras/size/bin/write-integer-u8.rs similarity index 100% rename from lexical-size/bin/write-integer-u8.rs rename to extras/size/bin/write-integer-u8.rs diff --git a/lexical-size/bin/write.rs b/extras/size/bin/write.rs similarity index 100% rename from lexical-size/bin/write.rs rename to extras/size/bin/write.rs diff --git a/lexical-size/clippy.toml b/extras/size/clippy.toml similarity index 100% rename from lexical-size/clippy.toml rename to extras/size/clippy.toml diff --git a/lexical-size/rustfmt.toml b/extras/size/rustfmt.toml similarity index 100% rename from lexical-size/rustfmt.toml rename to extras/size/rustfmt.toml diff --git a/extras/util/Cargo.toml b/extras/util/Cargo.toml new file mode 100644 index 00000000..67b9e9bf --- /dev/null +++ b/extras/util/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-util-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-util] +path = "../../lexical-util" +default-features = false + +[dev-dependencies] +# FIXME: Replace back to "1.0.4" once the PR is merged. +# There's an issue in quickcheck due to an infinitely repeating shrinker. +# Issue: https://github.com/BurntSushi/quickcheck/issues/295 +# Fix: https://github.com/BurntSushi/quickcheck/pull/296 +quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } +proptest = ">=1.5.0" + +[features] +default = ["std"] +std = ["lexical-util/std"] +power-of-two = ["lexical-util/power-of-two"] +radix = ["lexical-util/radix"] +format = ["lexical-util/format"] +write-integers = ["lexical-util/write-integers", "write", "integers"] +write-floats = ["lexical-util/write-floats", "write", "floats"] +parse-integers = ["lexical-util/parse-integers", "parse", "integers"] +parse-floats = ["lexical-util/parse-floats", "parse", "floats"] +compact = ["lexical-util/compact"] +f16 = ["lexical-util/f16", "parse-floats", "write-floats"] +lint = ["lexical-util/lint"] +write = ["lexical-util/write"] +parse = ["lexical-util/parse"] +integers = ["lexical-util/integers"] +floats = ["lexical-util/floats"] diff --git a/extras/util/src/lib.rs b/extras/util/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/util/src/lib.rs @@ -0,0 +1 @@ + diff --git a/extras/util/tests/bf16_tests.rs b/extras/util/tests/bf16_tests.rs new file mode 100644 index 00000000..7f6cdf64 --- /dev/null +++ b/extras/util/tests/bf16_tests.rs @@ -0,0 +1,33 @@ +#![cfg(feature = "f16")] + +mod util; + +use lexical_util::bf16::bf16; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +default_quickcheck! { + fn f32_roundtrip_quickcheck(x: u16) -> bool { + let f = bf16::from_bits(x).as_f32_const(); + if f.is_nan() { + bf16::from_f32_const(f).is_nan() + } else { + bf16::from_f32_const(f).to_bits() == x + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { + let f = bf16::from_bits(x).to_f32_const(); + if f.is_nan() { + prop_assert!(bf16::from_f32_const(f).is_nan()); + } else { + prop_assert_eq!(bf16::from_f32_const(f).to_bits(), x); + } + } +} diff --git a/lexical-util/tests/div128_tests.rs b/extras/util/tests/div128_tests.rs similarity index 100% rename from lexical-util/tests/div128_tests.rs rename to extras/util/tests/div128_tests.rs diff --git a/extras/util/tests/f16_tests.rs b/extras/util/tests/f16_tests.rs new file mode 100644 index 00000000..1b06a1d5 --- /dev/null +++ b/extras/util/tests/f16_tests.rs @@ -0,0 +1,33 @@ +#![cfg(feature = "f16")] + +mod util; + +use lexical_util::f16::f16; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +default_quickcheck! { + fn f32_roundtrip_quickcheck(x: u16) -> bool { + let f = f16::from_bits(x).as_f32_const(); + if f.is_nan() { + f16::from_f32_const(f).is_nan() + } else { + f16::from_f32_const(f).to_bits() == x + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { + let f = f16::from_bits(x).as_f32_const(); + if f.is_nan() { + prop_assert!(f16::from_f32_const(f).is_nan()); + } else { + prop_assert_eq!(f16::from_f32_const(f).to_bits(), x); + } + } +} diff --git a/lexical-util/tests/mul_tests.rs b/extras/util/tests/mul_tests.rs similarity index 100% rename from lexical-util/tests/mul_tests.rs rename to extras/util/tests/mul_tests.rs diff --git a/lexical-util/tests/util.rs b/extras/util/tests/util.rs similarity index 100% rename from lexical-util/tests/util.rs rename to extras/util/tests/util.rs diff --git a/extras/write-float/Cargo.toml b/extras/write-float/Cargo.toml new file mode 100644 index 00000000..82ec701f --- /dev/null +++ b/extras/write-float/Cargo.toml @@ -0,0 +1,43 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-write-float-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-write-float] +path = "../../lexical-write-float" +default-features = false + +[dependencies.lexical-write-integer] +path = "../../lexical-write-integer" +default-features = false + +[dependencies.lexical-util] +path = "../../lexical-util" +default-features = false + +[dev-dependencies] +approx = "0.5.0" +# FIXME: Replace back to "1.0.4" once the PR is merged. +# There's an issue in quickcheck due to an infinitely repeating shrinker. +# Issue: https://github.com/BurntSushi/quickcheck/issues/295 +# Fix: https://github.com/BurntSushi/quickcheck/pull/296 +quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } +proptest = ">=1.5.0" +fraction = "0.15.0" + +[features] +default = ["std"] +std = [ "lexical-util/std", "lexical-write-integer/std", "lexical-write-float/std"] +power-of-two = ["lexical-util/power-of-two", "lexical-write-integer/power-of-two", "lexical-write-float/power-of-two"] +radix = ["lexical-util/radix", "lexical-write-integer/radix", "lexical-write-float/radix", "power-of-two"] +format = ["lexical-util/format", "lexical-write-float/format"] +compact = ["lexical-util/compact", "lexical-write-integer/compact", "lexical-write-float/compact"] +f16 = ["lexical-util/f16", "lexical-write-float/f16"] +lint = ["lexical-util/lint", "lexical-write-integer/lint", "lexical-write-float/lint"] +f128 = ["lexical-util/f128", "lexical-write-float/f128"] diff --git a/extras/write-float/src/lib.rs b/extras/write-float/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/write-float/src/lib.rs @@ -0,0 +1 @@ + diff --git a/extras/write-float/tests/algorithm_tests.rs b/extras/write-float/tests/algorithm_tests.rs new file mode 100644 index 00000000..f89b151a --- /dev/null +++ b/extras/write-float/tests/algorithm_tests.rs @@ -0,0 +1,73 @@ +#![cfg(not(feature = "compact"))] + +mod util; + +use lexical_util::constants::BUFFER_SIZE; +use lexical_util::format::NumberFormatBuilder; +use lexical_util::num::Float; +use lexical_write_float::{algorithm, Options}; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +const DECIMAL: u128 = NumberFormatBuilder::decimal(); + +default_quickcheck! { + fn f32_quickcheck(f: f32) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if f.is_special() { + true + } else { + let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + roundtrip == Ok(f) + } + } + + fn f64_quickcheck(f: f64) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if f.is_special() { + true + } else { + let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + roundtrip == Ok(f) + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_proptest(f in f32::MIN..f32::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if !f.is_special() { + let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + prop_assert_eq!(roundtrip, Ok(f)) + } + } + + #[test] + fn f64_proptest(f in f64::MIN..f64::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if !f.is_special() { + let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + prop_assert_eq!(roundtrip, Ok(f)) + } + } +} diff --git a/extras/write-float/tests/api_tests.rs b/extras/write-float/tests/api_tests.rs new file mode 100644 index 00000000..f98d2f39 --- /dev/null +++ b/extras/write-float/tests/api_tests.rs @@ -0,0 +1,91 @@ +mod util; + +#[cfg(feature = "f16")] +use lexical_util::bf16::bf16; +use lexical_util::constants::BUFFER_SIZE; +#[cfg(feature = "f16")] +use lexical_util::f16::f16; +use lexical_write_float::ToLexical; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +default_quickcheck! { + fn f32_quickcheck(f: f32) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + roundtrip.is_ok() && roundtrip.unwrap().is_nan() + } else { + roundtrip == Ok(f) + } + } + + fn f64_quickcheck(f: f64) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + roundtrip.is_ok() && roundtrip.unwrap().is_nan() + } else { + roundtrip == Ok(f) + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_proptest(f in f32::MIN..f32::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); + } else { + prop_assert_eq!(roundtrip, Ok(f)); + } + } + + #[test] + fn f64_proptest(f in f64::MIN..f64::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); + } else { + prop_assert_eq!(roundtrip, Ok(f)); + } + } + + #[test] + #[cfg(feature = "f16")] + fn f16_proptest(bits in u16::MIN..u16::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let f = f16::from_bits(bits); + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); + } else { + prop_assert_eq!(roundtrip, Ok(f.as_f32())); + } + } + + #[test] + #[cfg(feature = "f16")] + fn bf16_proptest(bits in u16::MIN..u16::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let f = bf16::from_bits(bits); + let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; + let roundtrip = actual.parse::(); + if f.is_nan() { + prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); + } else { + prop_assert_eq!(roundtrip, Ok(f.as_f32())); + } + } +} diff --git a/extras/write-float/tests/binary_tests.rs b/extras/write-float/tests/binary_tests.rs new file mode 100644 index 00000000..efcf6915 --- /dev/null +++ b/extras/write-float/tests/binary_tests.rs @@ -0,0 +1,122 @@ +#![cfg(feature = "power-of-two")] + +mod parse_radix; +mod util; + +use lexical_util::constants::BUFFER_SIZE; +use lexical_util::format::NumberFormatBuilder; +use lexical_util::num::Float; +use lexical_write_float::{binary, Options}; +use parse_radix::{parse_f32, parse_f64}; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +const BINARY: u128 = NumberFormatBuilder::binary(); +const OCTAL: u128 = NumberFormatBuilder::octal(); + +default_quickcheck! { + fn f32_binary_quickcheck(f: f32) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if f.is_special() { + true + } else { + let f = f.abs(); + let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); + let roundtrip = parse_f32(&buffer[..count], 2, b'e'); + roundtrip == f + } + } + + fn f32_octal_quickcheck(f: f32) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if f.is_special() { + true + } else { + let f = f.abs(); + let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); + let roundtrip = parse_f32(&buffer[..count], 8, b'e'); + roundtrip == f + } + } + + fn f64_binary_quickcheck(f: f64) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if f.is_special() { + true + } else { + let f = f.abs(); + let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); + let roundtrip = parse_f64(&buffer[..count], 2, b'e'); + roundtrip == f + } + } + + fn f64_octal_quickcheck(f: f64) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if f.is_special() { + true + } else { + let f = f.abs(); + let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); + let roundtrip = parse_f64(&buffer[..count], 8, b'e'); + roundtrip == f + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_binary_proptest(f in f32::MIN..f32::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if !f.is_special() { + let f = f.abs(); + let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); + let roundtrip = parse_f32(&buffer[..count], 2, b'e'); + prop_assert_eq!(roundtrip, f) + } + } + + #[test] + fn f32_octal_proptest(f in f32::MIN..f32::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if !f.is_special() { + let f = f.abs(); + let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); + let roundtrip = parse_f32(&buffer[..count], 8, b'e'); + prop_assert_eq!(roundtrip, f) + } + } + + #[test] + fn f64_binary_proptest(f in f64::MIN..f64::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if !f.is_special() { + let f = f.abs(); + let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); + let roundtrip = parse_f64(&buffer[..count], 2, b'e'); + prop_assert_eq!(roundtrip, f) + } + } + + #[test] + fn f64_octal_proptest(f in f64::MIN..f64::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + if !f.is_special() { + let f = f.abs(); + let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); + let roundtrip = parse_f64(&buffer[..count], 8, b'e'); + prop_assert_eq!(roundtrip, f) + } + } +} diff --git a/extras/write-float/tests/compact_tests.rs b/extras/write-float/tests/compact_tests.rs new file mode 100644 index 00000000..aee524e6 --- /dev/null +++ b/extras/write-float/tests/compact_tests.rs @@ -0,0 +1,73 @@ +#![cfg(feature = "compact")] + +mod util; + +use lexical_util::constants::BUFFER_SIZE; +use lexical_util::format::NumberFormatBuilder; +use lexical_util::num::Float; +use lexical_write_float::{compact, Options}; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +const DECIMAL: u128 = NumberFormatBuilder::decimal(); + +default_quickcheck! { + fn f32_quickcheck(f: f32) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if f.is_special() { + true + } else { + let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + roundtrip == Ok(f) + } + } + + fn f64_quickcheck(f: f64) -> bool { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if f.is_special() { + true + } else { + let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + roundtrip == Ok(f) + } + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn f32_proptest(f in f32::MIN..f32::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if !f.is_special() { + let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + prop_assert_eq!(roundtrip, Ok(f)) + } + } + + #[test] + fn f64_proptest(f in f64::MIN..f64::MAX) { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = Options::builder().build().unwrap(); + let f = f.abs(); + if !f.is_special() { + let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); + let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; + let roundtrip = actual.parse::(); + prop_assert_eq!(roundtrip, Ok(f)) + } + } +} diff --git a/lexical-write-float/tests/parse_radix.rs b/extras/write-float/tests/parse_radix.rs similarity index 100% rename from lexical-write-float/tests/parse_radix.rs rename to extras/write-float/tests/parse_radix.rs diff --git a/lexical-write-float/tests/radix_tests.rs b/extras/write-float/tests/radix_tests.rs similarity index 100% rename from lexical-write-float/tests/radix_tests.rs rename to extras/write-float/tests/radix_tests.rs diff --git a/lexical-write-float/tests/util.rs b/extras/write-float/tests/util.rs similarity index 100% rename from lexical-write-float/tests/util.rs rename to extras/write-float/tests/util.rs diff --git a/extras/write-integer/Cargo.toml b/extras/write-integer/Cargo.toml new file mode 100644 index 00000000..94f1817b --- /dev/null +++ b/extras/write-integer/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Alex Huszagh "] +edition = "2021" +keywords = ["no_std"] +license = "MIT/Apache-2.0" +name = "lexical-write-integer-extras" +repository = "https://github.com/Alexhuszagh/rust-lexical" +version = "0.0.1-alpha" +rust-version = "1.65.0" +publish = false + +[dependencies.lexical-write-integer] +path = "../../lexical-write-integer" +default-features = false + +[dependencies.lexical-util] +path = "../../lexical-util" +default-features = false + +[dev-dependencies] +# FIXME: Replace back to "1.0.4" once the PR is merged. +# There's an issue in quickcheck due to an infinitely repeating shrinker. +# Issue: https://github.com/BurntSushi/quickcheck/issues/295 +# Fix: https://github.com/BurntSushi/quickcheck/pull/296 +quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } +proptest = ">=1.5.0" +rustversion = ">=1.0.18" + +[features] +default = ["std"] +std = ["lexical-write-integer/std", "lexical-util/std"] +power-of-two = ["lexical-write-integer/power-of-two", "lexical-util/power-of-two"] +radix = ["lexical-write-integer/radix", "power-of-two", "lexical-util/radix"] +format = ["lexical-write-integer/format", "lexical-util/format"] +compact = ["lexical-write-integer/compact", "lexical-util/compact"] +lint = ["lexical-write-integer/lint", "lexical-util/lint"] diff --git a/extras/write-integer/src/lib.rs b/extras/write-integer/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/extras/write-integer/src/lib.rs @@ -0,0 +1 @@ + diff --git a/extras/write-integer/tests/api_tests.rs b/extras/write-integer/tests/api_tests.rs new file mode 100644 index 00000000..b225975b --- /dev/null +++ b/extras/write-integer/tests/api_tests.rs @@ -0,0 +1,283 @@ +mod util; + +use core::fmt::Debug; +use core::str::{from_utf8_unchecked, FromStr}; + +#[cfg(feature = "radix")] +use lexical_util::constants::BUFFER_SIZE; +use lexical_write_integer::{ToLexical, ToLexicalWithOptions}; +use proptest::prelude::*; +#[cfg(feature = "radix")] +use util::from_radix; + +use crate::util::default_proptest_config; + +trait Roundtrip: ToLexical + ToLexicalWithOptions + FromStr { + #[allow(dead_code)] + fn from_str_radix(src: &str, radix: u32) -> Result; +} + +macro_rules! roundtrip_impl { + ($($t:ty)*) => ($( + impl Roundtrip for $t { + fn from_str_radix(src: &str, radix: u32) -> Result { + <$t>::from_str_radix(src, radix) + } + } + )*); +} + +roundtrip_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize } + +fn roundtrip(x: T) -> T +where + T: Roundtrip, + ::Err: Debug, +{ + let mut buffer = [b'\x00'; 48]; + let bytes = x.to_lexical(&mut buffer); + let string = unsafe { from_utf8_unchecked(bytes) }; + string.parse::().unwrap() +} + +#[cfg(feature = "radix")] +fn roundtrip_radix(x: T, radix: u32) -> T +where + T: Roundtrip, + ::Err: Debug, +{ + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let options = T::Options::default(); + // Trick it into assuming we have a valid radix. + let bytes = match radix { + 2 => x.to_lexical_with_options::<{ from_radix(2) }>(&mut buffer, &options), + 3 => x.to_lexical_with_options::<{ from_radix(3) }>(&mut buffer, &options), + 4 => x.to_lexical_with_options::<{ from_radix(4) }>(&mut buffer, &options), + 5 => x.to_lexical_with_options::<{ from_radix(5) }>(&mut buffer, &options), + 6 => x.to_lexical_with_options::<{ from_radix(6) }>(&mut buffer, &options), + 7 => x.to_lexical_with_options::<{ from_radix(7) }>(&mut buffer, &options), + 8 => x.to_lexical_with_options::<{ from_radix(8) }>(&mut buffer, &options), + 9 => x.to_lexical_with_options::<{ from_radix(9) }>(&mut buffer, &options), + 10 => x.to_lexical_with_options::<{ from_radix(10) }>(&mut buffer, &options), + 11 => x.to_lexical_with_options::<{ from_radix(11) }>(&mut buffer, &options), + 12 => x.to_lexical_with_options::<{ from_radix(12) }>(&mut buffer, &options), + 13 => x.to_lexical_with_options::<{ from_radix(13) }>(&mut buffer, &options), + 14 => x.to_lexical_with_options::<{ from_radix(14) }>(&mut buffer, &options), + 15 => x.to_lexical_with_options::<{ from_radix(15) }>(&mut buffer, &options), + 16 => x.to_lexical_with_options::<{ from_radix(16) }>(&mut buffer, &options), + 17 => x.to_lexical_with_options::<{ from_radix(17) }>(&mut buffer, &options), + 18 => x.to_lexical_with_options::<{ from_radix(18) }>(&mut buffer, &options), + 19 => x.to_lexical_with_options::<{ from_radix(19) }>(&mut buffer, &options), + 20 => x.to_lexical_with_options::<{ from_radix(20) }>(&mut buffer, &options), + 21 => x.to_lexical_with_options::<{ from_radix(21) }>(&mut buffer, &options), + 22 => x.to_lexical_with_options::<{ from_radix(22) }>(&mut buffer, &options), + 23 => x.to_lexical_with_options::<{ from_radix(23) }>(&mut buffer, &options), + 24 => x.to_lexical_with_options::<{ from_radix(24) }>(&mut buffer, &options), + 25 => x.to_lexical_with_options::<{ from_radix(25) }>(&mut buffer, &options), + 26 => x.to_lexical_with_options::<{ from_radix(26) }>(&mut buffer, &options), + 27 => x.to_lexical_with_options::<{ from_radix(27) }>(&mut buffer, &options), + 28 => x.to_lexical_with_options::<{ from_radix(28) }>(&mut buffer, &options), + 29 => x.to_lexical_with_options::<{ from_radix(29) }>(&mut buffer, &options), + 30 => x.to_lexical_with_options::<{ from_radix(30) }>(&mut buffer, &options), + 31 => x.to_lexical_with_options::<{ from_radix(31) }>(&mut buffer, &options), + 32 => x.to_lexical_with_options::<{ from_radix(32) }>(&mut buffer, &options), + 33 => x.to_lexical_with_options::<{ from_radix(33) }>(&mut buffer, &options), + 34 => x.to_lexical_with_options::<{ from_radix(34) }>(&mut buffer, &options), + 35 => x.to_lexical_with_options::<{ from_radix(35) }>(&mut buffer, &options), + 36 => x.to_lexical_with_options::<{ from_radix(36) }>(&mut buffer, &options), + _ => unreachable!(), + }; + let string = unsafe { from_utf8_unchecked(bytes) }; + T::from_str_radix(string, radix).unwrap() +} + +default_quickcheck! { + fn u8_quickcheck(i: u8) -> bool { + i == roundtrip(i) + } + + fn u16_quickcheck(i: u16) -> bool { + i == roundtrip(i) + } + + fn u32_quickcheck(i: u32) -> bool { + i == roundtrip(i) + } + + fn u64_quickcheck(i: u64) -> bool { + i == roundtrip(i) + } + + fn u128_quickcheck(i: u128) -> bool { + i == roundtrip(i) + } + + fn usize_quickcheck(i: usize) -> bool { + i == roundtrip(i) + } + + fn i8_quickcheck(i: i8) -> bool { + i == roundtrip(i) + } + + fn i16_quickcheck(i: i16) -> bool { + i == roundtrip(i) + } + + fn i32_quickcheck(i: i32) -> bool { + i == roundtrip(i) + } + + fn i64_quickcheck(i: i64) -> bool { + i == roundtrip(i) + } + + fn i128_quickcheck(i: i128) -> bool { + i == roundtrip(i) + } + + fn isize_quickcheck(i: isize) -> bool { + i == roundtrip(i) + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn u8_proptest(i in u8::MIN..u8::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn i8_proptest(i in i8::MIN..i8::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn u16_proptest(i in u16::MIN..u16::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn i16_proptest(i in i16::MIN..i16::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn u32_proptest(i in u32::MIN..u32::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn i32_proptest(i in i32::MIN..i32::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn u64_proptest(i in u64::MIN..u64::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn i64_proptest(i in i64::MIN..i64::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn u128_proptest(i in u128::MIN..u128::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn i128_proptest(i in i128::MIN..i128::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn usize_proptest(i in usize::MIN..usize::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn isize_proptest(i in isize::MIN..isize::MAX) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + fn jeaiii_magic_10u64_proptest(i in 10_0000_0000..100_0000_0000u64) { + prop_assert_eq!(i, roundtrip(i)); + } + + #[test] + #[cfg(feature = "radix")] + fn u8_proptest_radix(i in u8::MIN..u8::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn i8_proptest_radix(i in i8::MIN..i8::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn u16_proptest_radix(i in u16::MIN..u16::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn i16_proptest_radix(i in i16::MIN..i16::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn u32_proptest_radix(i in u32::MIN..u32::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn i32_proptest_radix(i in i32::MIN..i32::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn u64_proptest_radix(i in u64::MIN..u64::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn i64_proptest_radix(i in i64::MIN..i64::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn u128_proptest_radix(i in u128::MIN..u128::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn i128_proptest_radix(i in i128::MIN..i128::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn usize_proptest_radix(i in usize::MIN..usize::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } + + #[test] + #[cfg(feature = "radix")] + fn isize_proptest_radix(i in isize::MIN..isize::MAX, radix in 2u32..=36) { + prop_assert_eq!(i, roundtrip_radix(i, radix)); + } +} diff --git a/extras/write-integer/tests/decimal_tests.rs b/extras/write-integer/tests/decimal_tests.rs new file mode 100644 index 00000000..1ce477e4 --- /dev/null +++ b/extras/write-integer/tests/decimal_tests.rs @@ -0,0 +1,45 @@ +#![cfg(not(feature = "compact"))] + +mod util; + +use lexical_util::num::UnsignedInteger; +use lexical_write_integer::decimal::{Decimal, DecimalCount}; + +fn slow_digit_count(x: T) -> usize { + x.to_string().len() +} + +default_quickcheck! { + fn u32_digit_count_quickcheck(x: u32) -> bool { + slow_digit_count(x) == x.decimal_count() + } + + fn u64_digit_count_quickcheck(x: u64) -> bool { + slow_digit_count(x) == x.decimal_count() + } + + fn u128_digit_count_quickcheck(x: u128) -> bool { + slow_digit_count(x) == x.decimal_count() + } + + fn u32toa_quickcheck(x: u32) -> bool { + let actual = x.to_string(); + let mut buffer = [b'\x00'; 16]; + actual.len() == x.decimal(&mut buffer) && + &buffer[..actual.len()] == actual.as_bytes() + } + + fn u64toa_quickcheck(x: u64) -> bool { + let actual = x.to_string(); + let mut buffer = [b'\x00'; 32]; + actual.len() == x.decimal(&mut buffer) && + &buffer[..actual.len()] == actual.as_bytes() + } + + fn u128toa_quickcheck(x: u128) -> bool { + let actual = x.to_string(); + let mut buffer = [b'\x00'; 48]; + actual.len() == x.decimal(&mut buffer) && + &buffer[..actual.len()] == actual.as_bytes() + } +} diff --git a/extras/write-integer/tests/digit_count_tests.rs b/extras/write-integer/tests/digit_count_tests.rs new file mode 100644 index 00000000..75a3791f --- /dev/null +++ b/extras/write-integer/tests/digit_count_tests.rs @@ -0,0 +1,95 @@ +#![cfg(not(feature = "compact"))] + +mod util; + +use lexical_write_integer::decimal::DecimalCount; +use lexical_write_integer::digit_count::{self, DigitCount}; +use proptest::prelude::*; + +use crate::util::default_proptest_config; + +fn slow_log2(x: u32) -> usize { + // Slow approach to calculating a log2, using floats. + if x == 0 { + 0 + } else { + (x as f64).log2().floor() as usize + } +} + +default_quickcheck! { + fn decimal_count_quickcheck(x: u32) -> bool { + x.digit_count(10) == x.decimal_count() + } + + fn fast_log2_quickcheck(x: u32) -> bool { + slow_log2(x) == digit_count::fast_log2(x) + } +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn decimal_slow_u64_test(x: u64) { + prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10)); + } + + #[test] + fn basen_slow_u64_test(x: u64, power in 1u32..=5) { + let radix = 2u32.pow(power); + prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix)); + } + + #[test] + fn decimal_slow_u128_test(x: u128) { + prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10)); + } + + #[test] + #[cfg(feature = "power-of-two")] + fn basen_slow_u128_test(x: u128, power in 1u32..=5) { + let radix = 2u32.pow(power); + prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix)); + } +} + +#[rustversion::since(1.67)] +macro_rules! ilog { + ($x:ident, $radix:expr) => {{ + if $x > 0 { + $x.ilog($radix as _) as usize + } else { + 0usize + } + }}; +} + +#[rustversion::since(1.67)] +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + fn basen_u64_test(x: u64, radix in 2u32..=36) { + prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); + } + + #[test] + #[cfg(feature = "radix")] + fn basen_u128_test(x: u128, radix in 2u32..=36) { + prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); + } + + #[test] + #[cfg(all(feature = "power-of-two", not(feature = "radix")))] + fn basen_u128_test(x: u128, power in 1u32..=5) { + let radix = 2u32.pow(power); + prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); + } + + #[test] + #[cfg(not(feature = "power-of-two"))] + fn basen_u128_test(x: u128) { + prop_assert_eq!(x.digit_count(10), ilog!(x, 10) + 1); + } +} diff --git a/extras/write-integer/tests/radix_tests.rs b/extras/write-integer/tests/radix_tests.rs new file mode 100644 index 00000000..912d54b9 --- /dev/null +++ b/extras/write-integer/tests/radix_tests.rs @@ -0,0 +1,162 @@ +#![cfg(not(feature = "compact"))] +#![cfg(feature = "power-of-two")] + +mod util; + +use core::num::ParseIntError; +use core::str::from_utf8_unchecked; + +use lexical_util::{constants::BUFFER_SIZE, num::UnsignedInteger}; +use lexical_write_integer::write::WriteInteger; +use proptest::prelude::*; + +use crate::util::{default_proptest_config, from_radix}; + +pub trait FromRadix: Sized { + fn from_radix(src: &str, radix: u32) -> Result; +} + +macro_rules! impl_from_radix { + ($($t:ty)*) => ($(impl FromRadix for $t { + #[inline] + fn from_radix(src: &str, radix: u32) -> Result { + <$t>::from_str_radix(src, radix) + } + })*) +} + +impl_from_radix! { u32 u64 u128 } + +// We need to trick the algorithm into thinking we're using a const. +// NOTE: This needs to be broken down into 4 functions since otherwise +// we can overflow the stack, so we just never inline for each test. +#[inline(never)] +fn to_radix_2_9(x: T, radix: u32, buffer: &mut [u8]) -> usize { + match radix { + 2 => x.write_mantissa::<{ from_radix(2) }>(buffer), + 3 => x.write_mantissa::<{ from_radix(3) }>(buffer), + 4 => x.write_mantissa::<{ from_radix(4) }>(buffer), + 5 => x.write_mantissa::<{ from_radix(5) }>(buffer), + 6 => x.write_mantissa::<{ from_radix(6) }>(buffer), + 7 => x.write_mantissa::<{ from_radix(7) }>(buffer), + 8 => x.write_mantissa::<{ from_radix(8) }>(buffer), + 9 => x.write_mantissa::<{ from_radix(9) }>(buffer), + _ => unimplemented!(), + } +} + +#[inline(never)] +fn to_radix_10_18(x: T, radix: u32, buffer: &mut [u8]) -> usize { + match radix { + 10 => x.write_mantissa::<{ from_radix(10) }>(buffer), + 11 => x.write_mantissa::<{ from_radix(11) }>(buffer), + 12 => x.write_mantissa::<{ from_radix(12) }>(buffer), + 13 => x.write_mantissa::<{ from_radix(13) }>(buffer), + 14 => x.write_mantissa::<{ from_radix(14) }>(buffer), + 15 => x.write_mantissa::<{ from_radix(15) }>(buffer), + 16 => x.write_mantissa::<{ from_radix(16) }>(buffer), + 17 => x.write_mantissa::<{ from_radix(17) }>(buffer), + 18 => x.write_mantissa::<{ from_radix(18) }>(buffer), + _ => unimplemented!(), + } +} + +#[inline(never)] +fn to_radix_19_27(x: T, radix: u32, buffer: &mut [u8]) -> usize { + match radix { + 19 => x.write_mantissa::<{ from_radix(19) }>(buffer), + 20 => x.write_mantissa::<{ from_radix(20) }>(buffer), + 21 => x.write_mantissa::<{ from_radix(21) }>(buffer), + 22 => x.write_mantissa::<{ from_radix(22) }>(buffer), + 23 => x.write_mantissa::<{ from_radix(23) }>(buffer), + 24 => x.write_mantissa::<{ from_radix(24) }>(buffer), + 25 => x.write_mantissa::<{ from_radix(25) }>(buffer), + 26 => x.write_mantissa::<{ from_radix(26) }>(buffer), + 27 => x.write_mantissa::<{ from_radix(27) }>(buffer), + _ => unimplemented!(), + } +} + +#[inline(never)] +fn to_radix_28_36(x: T, radix: u32, buffer: &mut [u8]) -> usize { + match radix { + 28 => x.write_mantissa::<{ from_radix(28) }>(buffer), + 29 => x.write_mantissa::<{ from_radix(29) }>(buffer), + 30 => x.write_mantissa::<{ from_radix(30) }>(buffer), + 31 => x.write_mantissa::<{ from_radix(31) }>(buffer), + 32 => x.write_mantissa::<{ from_radix(32) }>(buffer), + 33 => x.write_mantissa::<{ from_radix(33) }>(buffer), + 34 => x.write_mantissa::<{ from_radix(34) }>(buffer), + 35 => x.write_mantissa::<{ from_radix(35) }>(buffer), + 36 => x.write_mantissa::<{ from_radix(36) }>(buffer), + _ => unimplemented!(), + } +} + +#[inline(never)] +fn mockup( + x: T, + radix: u32, +) -> Result<(), TestCaseError> { + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let count = match radix { + 2..=9 => to_radix_2_9(x, radix, &mut buffer), + 10..=18 => to_radix_10_18(x, radix, &mut buffer), + 19..=27 => to_radix_19_27(x, radix, &mut buffer), + 28..=36 => to_radix_28_36(x, radix, &mut buffer), + _ => unimplemented!(), + }; + let y = unsafe { T::from_radix(from_utf8_unchecked(&buffer[..count]), radix) }; + prop_assert_eq!(y, Ok(x)); + + Ok(()) +} + +proptest! { + #![proptest_config(default_proptest_config())] + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(feature = "radix")] + fn u32toa_proptest(x: u32, radix in 2u32..=36) { + mockup(x, radix)?; + } + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(not(feature = "radix"))] + fn u32toa_proptest(x: u32, power in 1u32..=5) { + let radix = 2u32.pow(power); + mockup(x, radix)?; + } + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(feature = "radix")] + fn u64toa_proptest(x: u64, radix in 2u32..=36) { + mockup(x, radix)?; + } + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(not(feature = "radix"))] + fn u64toa_proptest(x: u64, power in 1u32..=5) { + let radix = 2u32.pow(power); + mockup(x, radix)?; + } + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(feature = "radix")] + fn u128toa_proptest(x: u128, radix in 2u32..=36) { + mockup(x, radix)?; + } + + #[test] + #[cfg_attr(miri, ignore)] + #[cfg(not(feature = "radix"))] + fn u128toa_proptest(x: u128, power in 1u32..=5) { + let radix = 2u32.pow(power); + mockup(x, radix)?; + } +} diff --git a/extras/write-integer/tests/util.rs b/extras/write-integer/tests/util.rs new file mode 100644 index 00000000..d846be3f --- /dev/null +++ b/extras/write-integer/tests/util.rs @@ -0,0 +1,64 @@ +#![allow(dead_code, unused_imports)] + +#[cfg(feature = "power-of-two")] +use lexical_util::format::NumberFormatBuilder; +use proptest::prelude::*; +pub(crate) use quickcheck::QuickCheck; + +#[cfg(feature = "power-of-two")] +pub const fn from_radix(radix: u8) -> u128 { + NumberFormatBuilder::from_radix(radix) +} + +pub fn default_proptest_config() -> ProptestConfig { + ProptestConfig { + cases: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().cases + }, + max_shrink_iters: if cfg!(miri) { + 10 + } else { + ProptestConfig::default().max_shrink_iters + }, + failure_persistence: if cfg!(miri) { + None + } else { + ProptestConfig::default().failure_persistence + }, + ..ProptestConfig::default() + } +} + +// This is almost identical to quickcheck's itself, just to add default +// arguments https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 +// The code is unlicensed. +#[macro_export] +macro_rules! default_quickcheck { + (@as_items $($i:item)*) => ($($i)*); + { + $( + $(#[$m:meta])* + fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { + $($code:tt)* + } + )* + } => ( + $crate::default_quickcheck! { + @as_items + $( + #[test] + $(#[$m])* + fn $fn_name() { + fn prop($($arg_name: $arg_ty),*) -> $ret { + $($code)* + } + $crate::util::QuickCheck::new() + .max_tests(if cfg!(miri) { 10 } else { 10_000 }) + .quickcheck(prop as fn($($arg_ty),*) -> $ret); + } + )* + } + ) +} diff --git a/lexical-core/Cargo.toml b/lexical-core/Cargo.toml index 972c71c9..9ea76274 100644 --- a/lexical-core/Cargo.toml +++ b/lexical-core/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-core" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.5" -rust-version = "1.63.0" +rust-version = "1.61.0" exclude = [ "assets/*", "docs/*", @@ -47,9 +47,6 @@ optional = true default-features = false path = "../lexical-write-float" -[dev-dependencies] -approx = "0.5.0" - [features] # Need to enable all for backwards compatibility. default = ["std", "write-integers", "write-floats", "parse-integers", "parse-floats"] diff --git a/lexical-parse-float/Cargo.toml b/lexical-parse-float/Cargo.toml index f9857bd5..4d05512d 100644 --- a/lexical-parse-float/Cargo.toml +++ b/lexical-parse-float/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-parse-float" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.5" -rust-version = "1.63.0" +rust-version = "1.61.0" exclude = [ "assets/*", "docs/*", @@ -33,14 +33,6 @@ features = [] [dependencies] static_assertions = "1" -[dev-dependencies] -# FIXME: Replace back to "1.0.4" once the PR is merged. -# There's an issue in quickcheck due to an infinitely repeating shrinker. -# Issue: https://github.com/BurntSushi/quickcheck/issues/295 -# Fix: https://github.com/BurntSushi/quickcheck/pull/296 -quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } -proptest = ">=1.5.0" - [features] default = ["std"] # Use the standard library. diff --git a/lexical-parse-float/docs/Benchmarks.md b/lexical-parse-float/docs/Benchmarks.md index e63036fe..969f879e 100644 --- a/lexical-parse-float/docs/Benchmarks.md +++ b/lexical-parse-float/docs/Benchmarks.md @@ -1,6 +1,6 @@ # Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [a6dbf6d](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/a6dbf6d6639758989f24d6750ee9711d29c9f6bd). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/parse-float). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [a6dbf6d](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/a6dbf6d6639758989f24d6750ee9711d29c9f6bd). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/parse-float). **Random** diff --git a/lexical-parse-float/docs/CompactBenchmarks.md b/lexical-parse-float/docs/CompactBenchmarks.md index 18ab1837..cee15402 100644 --- a/lexical-parse-float/docs/CompactBenchmarks.md +++ b/lexical-parse-float/docs/CompactBenchmarks.md @@ -1,6 +1,6 @@ # Compact Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/parse-float). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/parse-float). **Random** diff --git a/lexical-parse-float/tests/api_tests.rs b/lexical-parse-float/tests/api_tests.rs index 6af6ce2c..0e11935e 100644 --- a/lexical-parse-float/tests/api_tests.rs +++ b/lexical-parse-float/tests/api_tests.rs @@ -1,23 +1,13 @@ -mod util; - #[cfg(feature = "format")] use core::num; use lexical_parse_float::{FromLexical, FromLexicalWithOptions, Options}; -#[cfg(feature = "f16")] -use lexical_util::bf16::bf16; use lexical_util::error::Error; -#[cfg(feature = "f16")] -use lexical_util::f16::f16; #[cfg(feature = "format")] use lexical_util::format; #[cfg(any(feature = "format", feature = "power-of-two"))] use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; -use lexical_util::num::Float; -use proptest::prelude::*; - -use crate::util::default_proptest_config; #[test] fn special_bytes_test() { @@ -1243,219 +1233,3 @@ fn issue68_test() { assert_eq!(f32::INFINITY, f32::from_lexical_with_options::(hex, &options).unwrap()); assert_eq!(f64::INFINITY, f64::from_lexical_with_options::(hex, &options).unwrap()); } - -fn float_equal(x: F, y: F) -> bool { - if x.is_nan() { - y.is_nan() - } else { - y == x - } -} - -default_quickcheck! { - fn f32_roundtrip_quickcheck(x: f32) -> bool { - let string = x.to_string(); - let result = f32::from_lexical(string.as_bytes()); - result.map_or(false, |y| float_equal(x, y)) - } - - fn f32_short_decimal_quickcheck(x: f32) -> bool { - let string = format!("{:.4}", x); - let actual = f32::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f32_long_decimal_quickcheck(x: f32) -> bool { - let string = format!("{:.100}", x); - let actual = f32::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f32_short_exponent_quickcheck(x: f32) -> bool { - let string = format!("{:.4e}", x); - let actual = f32::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f32_long_exponent_quickcheck(x: f32) -> bool { - let string = format!("{:.100e}", x); - let actual = f32::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f64_roundtrip_quickcheck(x: f64) -> bool { - let string = x.to_string(); - let result = f64::from_lexical(string.as_bytes()); - result.map_or(false, |y| float_equal(x, y)) - } - - fn f64_short_decimal_quickcheck(x: f64) -> bool { - let string = format!("{:.4}", x); - let actual = f64::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f64_long_decimal_quickcheck(x: f64) -> bool { - let string = format!("{:.100}", x); - let actual = f64::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f64_short_exponent_quickcheck(x: f64) -> bool { - let string = format!("{:.4e}", x); - let actual = f64::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - fn f64_long_exponent_quickcheck(x: f64) -> bool { - let string = format!("{:.100e}", x); - let actual = f64::from_lexical(string.as_bytes()); - let expected = string.parse::(); - actual.map_or(false, |y| expected.map_or(false, |x| float_equal(x, y))) - } - - #[cfg(feature = "f16")] - fn f16_roundtrip_quickcheck(bits: u16) -> bool { - let x = f16::from_bits(bits); - let string = x.as_f32().to_string(); - let result = f16::from_lexical(string.as_bytes()); - result.map_or(false, |y| float_equal(x, y)) - } - - #[cfg(feature = "f16")] - fn bf16_roundtrip_quickcheck(bits: u16) -> bool { - let x = bf16::from_bits(bits); - let string = x.as_f32().to_string(); - let result = bf16::from_lexical(string.as_bytes()); - result.map_or(false, |y| float_equal(x, y)) - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { - let res = f32::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_invalid_digit()); - } - - #[test] - fn f32_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { - let res = f32::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!( - res.err().unwrap().is_invalid_digit() || - res.err().unwrap().is_empty_mantissa() - ); - } - - #[test] - fn f32_sign_or_dot_only_proptest(i in r"[+-]?\.?") { - let res = f32::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!( - res.err().unwrap().is_empty() || - res.err().unwrap().is_empty_mantissa() - ); - } - - #[test] - fn f32_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { - let res = f32::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_empty_exponent()); - } - - #[test] - fn f32_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { - let res = f32::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_empty_exponent()); - } - - #[test] - fn f32_roundtrip_display_proptest(i in f32::MIN..f32::MAX) { - let input: String = format!("{}", i); - prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); - } - - #[test] - fn f32_roundtrip_debug_proptest(i in f32::MIN..f32::MAX) { - let input: String = format!("{:?}", i); - prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); - } - - #[test] - fn f32_roundtrip_scientific_proptest(i in f32::MIN..f32::MAX) { - let input: String = format!("{:e}", i); - prop_assert_eq!(i, f32::from_lexical(input.as_bytes()).unwrap()); - } - - #[test] - fn f64_invalid_proptest(i in r"[+-]?[0-9]{2}[^\deE]?\.[^\deE]?[0-9]{2}[^\deE]?e[+-]?[0-9]+[^\deE]") { - let res = f64::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_invalid_digit()); - } - - #[test] - fn f64_double_sign_proptest(i in r"[+-]{2}[0-9]{2}\.[0-9]{2}e[+-]?[0-9]+") { - let res = f64::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!( - res.err().unwrap().is_invalid_digit() || - res.err().unwrap().is_empty_mantissa() - ); - } - - #[test] - fn f64_sign_or_dot_only_proptest(i in r"[+-]?\.?") { - let res = f64::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!( - res.err().unwrap().is_empty() || - res.err().unwrap().is_empty_mantissa() - ); - } - - #[test] - fn f64_double_exponent_sign_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]{2}[0-9]+") { - let res = f64::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_empty_exponent()); - } - - #[test] - fn f64_missing_exponent_proptest(i in r"[+-]?[0-9]{2}\.[0-9]{2}e[+-]?") { - let res = f64::from_lexical(i.as_bytes()); - prop_assert!(res.is_err()); - prop_assert!(res.err().unwrap().is_empty_exponent()); - } - - #[test] - fn f64_roundtrip_display_proptest(i in f64::MIN..f64::MAX) { - let input: String = format!("{}", i); - prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); - } - - #[test] - fn f64_roundtrip_debug_proptest(i in f64::MIN..f64::MAX) { - let input: String = format!("{:?}", i); - prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); - } - - #[test] - fn f64_roundtrip_scientific_proptest(i in f64::MIN..f64::MAX) { - let input: String = format!("{:e}", i); - prop_assert_eq!(i, f64::from_lexical(input.as_bytes()).unwrap()); - } -} diff --git a/lexical-parse-integer/Cargo.toml b/lexical-parse-integer/Cargo.toml index 5566b3b5..d285fbeb 100644 --- a/lexical-parse-integer/Cargo.toml +++ b/lexical-parse-integer/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-parse-integer" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.5" -rust-version = "1.63.0" +rust-version = "1.61.0" exclude = [ "assets/*", "docs/*", @@ -27,14 +27,6 @@ path = "../lexical-util" default-features = false features = ["parse-integers"] -[dev-dependencies] -# FIXME: Replace back to "1.0.4" once the PR is merged. -# There's an issue in quickcheck due to an infinitely repeating shrinker. -# Issue: https://github.com/BurntSushi/quickcheck/issues/295 -# Fix: https://github.com/BurntSushi/quickcheck/pull/296 -quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } -proptest = ">=1.5.0" - [features] default = ["std"] # Use the standard library. diff --git a/lexical-parse-integer/docs/Benchmarks.md b/lexical-parse-integer/docs/Benchmarks.md index 801af5ee..c182ad3a 100644 --- a/lexical-parse-integer/docs/Benchmarks.md +++ b/lexical-parse-integer/docs/Benchmarks.md @@ -1,6 +1,6 @@ # Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [54adf3b](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/54adf3bcb09806fc009e1f54ab6324c11c84c6d2). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/parse-integer). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [54adf3b](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/54adf3bcb09806fc009e1f54ab6324c11c84c6d2). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/parse-integer). # Random diff --git a/lexical-parse-integer/docs/CompactBenchmarks.md b/lexical-parse-integer/docs/CompactBenchmarks.md index 67e84511..3d09fd13 100644 --- a/lexical-parse-integer/docs/CompactBenchmarks.md +++ b/lexical-parse-integer/docs/CompactBenchmarks.md @@ -1,6 +1,6 @@ # Compact Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/parse-integer). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/parse-integer). # Random diff --git a/lexical-parse-integer/src/lib.rs b/lexical-parse-integer/src/lib.rs index a89b59e4..95b804b2 100644 --- a/lexical-parse-integer/src/lib.rs +++ b/lexical-parse-integer/src/lib.rs @@ -46,7 +46,7 @@ //! //! # Version Support //! -//! The minimum, standard, required version is 1.63.0, for const generic +//! The minimum, standard, required version is 1.61.0, for const generic //! support. Older versions of lexical support older Rust versions. //! //! # Design diff --git a/lexical-parse-integer/tests/algorithm_tests.rs b/lexical-parse-integer/tests/algorithm_tests.rs index 4c9706df..6ce0d1ba 100644 --- a/lexical-parse-integer/tests/algorithm_tests.rs +++ b/lexical-parse-integer/tests/algorithm_tests.rs @@ -6,12 +6,9 @@ use lexical_parse_integer::algorithm; use lexical_parse_integer::options::SMALL_NUMBERS; use lexical_util::format::STANDARD; use lexical_util::iterator::AsBytes; -use proptest::prelude::*; #[cfg(feature = "power-of-two")] use util::from_radix; -use crate::util::default_proptest_config; - #[test] fn test_is_4digits() { let value: u32 = 0x31_32_33_34; @@ -179,43 +176,3 @@ fn algorithm_128_test() { assert_eq!(parse_i128(b"+12345"), Ok((12345, 6))); assert_eq!(parse_i128(b"+123.45"), Ok((123, 4))); } - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn parse_4digits_proptest( - a in 0x30u32..0x39, - b in 0x30u32..0x39, - c in 0x30u32..0x39, - d in 0x30u32..0x39, - ) - { - let v = (a << 24) | (b << 16) | (c << 8) | d; - let actual = algorithm::parse_4digits::<{ STANDARD }>(v); - let expected = (a - 0x30) + 10 * (b - 0x30) + 100 * (c - 0x30) + 1000 * (d - 0x30); - prop_assert_eq!(actual, expected); - } - - #[test] - fn parse_8digits_proptest( - a in 0x30u64..0x39, - b in 0x30u64..0x39, - c in 0x30u64..0x39, - d in 0x30u64..0x39, - e in 0x30u64..0x39, - f in 0x30u64..0x39, - g in 0x30u64..0x39, - h in 0x30u64..0x39, - ) - { - let v1 = (a << 24) | (b << 16) | (c << 8) | d; - let v2 = (e << 24) | (f << 16) | (g << 8) | h; - let v = (v1 << 32) | v2; - let actual = algorithm::parse_8digits::<{ STANDARD }>(v); - let e1 = (a - 0x30) + 10 * (b - 0x30) + 100 * (c - 0x30) + 1000 * (d - 0x30); - let e2 = (e - 0x30) + 10 * (f - 0x30) + 100 * (g - 0x30) + 1000 * (h - 0x30); - let expected = e1 + 10000 * e2; - prop_assert_eq!(actual, expected); - } -} diff --git a/lexical-parse-integer/tests/api_tests.rs b/lexical-parse-integer/tests/api_tests.rs index 520b5a1a..f87187cf 100644 --- a/lexical-parse-integer/tests/api_tests.rs +++ b/lexical-parse-integer/tests/api_tests.rs @@ -5,12 +5,9 @@ use lexical_util::error::Error; #[cfg(feature = "format")] use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; -use proptest::prelude::*; #[cfg(feature = "power-of-two")] use util::from_radix; -use crate::util::default_proptest_config; - #[test] fn u8_decimal_test() { assert_eq!(Ok(0), u8::from_lexical(b"0")); @@ -358,364 +355,3 @@ fn base_prefix_and_suffix_test() { assert!(i32::from_lexical_with_options::(b"+h", &options).is_err()); assert!(i32::from_lexical_with_options::(b"+0x", &options).is_err()); } - -macro_rules! is_error { - ($result:expr, $check:ident) => {{ - let result = $result; - prop_assert!(result.is_err()); - let err = result.err().unwrap(); - prop_assert!(err.$check()); - }}; -} - -macro_rules! is_invalid_digit { - ($result:expr) => { - is_error!($result, is_invalid_digit) - }; -} - -macro_rules! is_empty { - ($result:expr) => { - is_error!($result, is_empty) - }; -} - -macro_rules! is_overflow { - ($result:expr) => { - is_error!($result, is_overflow) - }; -} - -macro_rules! is_underflow { - ($result:expr) => { - is_error!($result, is_underflow) - }; -} - -macro_rules! is_invalid_digit_match { - ($result:expr, $p1:pat_param $(| $prest:pat_param)*) => {{ - let result = $result; - prop_assert!(result.is_err()); - let err = result.err().unwrap(); - prop_assert!(err.is_invalid_digit()); - prop_assert!(matches!(*err.index().unwrap(), $p1 $(| $prest)*)); - }}; -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - #[cfg(feature = "power-of-two")] - fn i32_binary_roundtrip_display_proptest(i in i32::MIN..i32::MAX) { - let options = Options::new(); - const FORMAT: u128 = from_radix(2); - let digits = if i < 0 { - format!("-{:b}", (i as i64).wrapping_neg()) - } else { - format!("{:b}", i) - }; - let result = i32::from_lexical_with_options::(digits.as_bytes(), &options); - prop_assert_eq!(i, result.unwrap()); - } - - #[test] - fn u8_invalid_proptest(i in r"[+]?[0-9]{2}\D") { - is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); - } - - #[test] - fn u8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { - is_overflow!(u8::from_lexical(i.as_bytes())); - } - - #[test] - fn u8_negative_proptest(i in r"[-][1-9][0-9]{2}") { - is_invalid_digit!(u8::from_lexical(i.as_bytes())); - } - - #[test] - fn u8_double_sign_proptest(i in r"[+]{2}[0-9]{2}") { - is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn u8_sign_only_proptest(i in r"[+]") { - is_empty!(u8::from_lexical(i.as_bytes())); - } - - #[test] - fn u8_trailing_digits_proptest(i in r"[+]?[0-9]{2}\D[0-9]{2}") { - is_invalid_digit_match!(u8::from_lexical(i.as_bytes()), 2 | 3); - } - - #[test] - fn i8_invalid_proptest(i in r"[+-]?[0-9]{2}\D") { - is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); - } - - #[test] - fn i8_overflow_proptest(i in r"[+]?[1-9][0-9]{3}") { - is_overflow!(i8::from_lexical(i.as_bytes())); - } - - #[test] - fn i8_underflow_proptest(i in r"[-][1-9][0-9]{3}") { - is_underflow!(i8::from_lexical(i.as_bytes())); - } - - #[test] - fn i8_double_sign_proptest(i in r"[+-]{2}[0-9]{2}") { - is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn i8_sign_only_proptest(i in r"[+-]") { - is_empty!(i8::from_lexical(i.as_bytes())); - } - - #[test] - fn i8_trailing_digits_proptest(i in r"[+-]?[0-9]{2}\D[0-9]{2}") { - is_invalid_digit_match!(i8::from_lexical(i.as_bytes()), 2 | 3); - } - - #[test] - fn u16_invalid_proptest(i in r"[+]?[0-9]{4}\D") { - is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); - } - - #[test] - fn u16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { - is_overflow!(u16::from_lexical(i.as_bytes())); - } - - #[test] - fn u16_negative_proptest(i in r"[-][1-9][0-9]{4}") { - is_invalid_digit!(u16::from_lexical(i.as_bytes())); - } - - #[test] - fn u16_double_sign_proptest(i in r"[+]{2}[0-9]{4}") { - is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn u16_sign_only_proptest(i in r"[+]") { - is_empty!(u16::from_lexical(i.as_bytes())); - } - - #[test] - fn u16_trailing_digits_proptest(i in r"[+]?[0-9]{4}\D[0-9]{2}") { - is_invalid_digit_match!(u16::from_lexical(i.as_bytes()), 4 | 5); - } - - #[test] - fn i16_invalid_proptest(i in r"[+-]?[0-9]{4}\D") { - is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); - } - - #[test] - fn i16_overflow_proptest(i in r"[+]?[1-9][0-9]{5}") { - is_overflow!(i16::from_lexical(i.as_bytes())); - } - - #[test] - fn i16_underflow_proptest(i in r"[-][1-9][0-9]{5}") { - is_underflow!(i16::from_lexical(i.as_bytes())); - } - - #[test] - fn i16_double_sign_proptest(i in r"[+-]{2}[0-9]{4}") { - is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn i16_sign_only_proptest(i in r"[+-]") { - is_empty!(i16::from_lexical(i.as_bytes())); - } - - #[test] - fn i16_trailing_digits_proptest(i in r"[+-]?[0-9]{4}\D[0-9]{2}") { - is_invalid_digit_match!(i16::from_lexical(i.as_bytes()), 4 | 5); - } - - #[test] - fn u32_invalid_proptest(i in r"[+]?[0-9]{9}\D") { - is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); - } - - #[test] - fn u32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { - is_overflow!(u32::from_lexical(i.as_bytes())); - } - - #[test] - fn u32_negative_proptest(i in r"[-][1-9][0-9]{9}") { - is_invalid_digit!(u32::from_lexical(i.as_bytes())); - } - - #[test] - fn u32_double_sign_proptest(i in r"[+]{2}[0-9]{9}") { - is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn u32_sign_only_proptest(i in r"[+]") { - is_empty!(u32::from_lexical(i.as_bytes())); - } - - #[test] - fn u32_trailing_digits_proptest(i in r"[+]?[0-9]{9}\D[0-9]{2}") { - is_invalid_digit_match!(u32::from_lexical(i.as_bytes()), 9 | 10); - } - - #[test] - fn i32_invalid_proptest(i in r"[+-]?[0-9]{9}\D") { - is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); - } - - #[test] - fn i32_overflow_proptest(i in r"[+]?[1-9][0-9]{10}") { - is_overflow!(i32::from_lexical(i.as_bytes())); - } - - #[test] - fn i32_underflow_proptest(i in r"-[1-9][0-9]{10}") { - is_underflow!(i32::from_lexical(i.as_bytes())); - } - - #[test] - fn i32_double_sign_proptest(i in r"[+-]{2}[0-9]{9}") { - is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn i32_sign_only_proptest(i in r"[+-]") { - is_empty!(i32::from_lexical(i.as_bytes())); - } - - #[test] - fn i32_trailing_digits_proptest(i in r"[+-]?[0-9]{9}\D[0-9]{2}") { - is_invalid_digit_match!(i32::from_lexical(i.as_bytes()), 9 | 10); - } - - #[test] - fn u64_invalid_proptest(i in r"[+]?[0-9]{19}\D") { - is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); - } - - #[test] - fn u64_overflow_proptest(i in r"[+]?[1-9][0-9]{21}") { - is_overflow!(u64::from_lexical(i.as_bytes())); - } - - #[test] - fn u64_negative_proptest(i in r"[-][1-9][0-9]{21}") { - is_invalid_digit!(u64::from_lexical(i.as_bytes())); - } - - #[test] - fn u64_double_sign_proptest(i in r"[+]{2}[0-9]{19}") { - is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn u64_sign_only_proptest(i in r"[+]") { - is_empty!(u64::from_lexical(i.as_bytes())); - } - - #[test] - fn u64_trailing_digits_proptest(i in r"[+]?[0-9]{19}\D[0-9]{2}") { - is_invalid_digit_match!(u64::from_lexical(i.as_bytes()), 19 | 20); - } - - #[test] - fn i64_invalid_proptest(i in r"[+-]?[0-9]{18}\D") { - is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); - } - - #[test] - fn i64_overflow_proptest(i in r"[+]?[1-9][0-9]{19}") { - is_overflow!(i64::from_lexical(i.as_bytes())); - } - - #[test] - fn i64_underflow_proptest(i in r"-[1-9][0-9]{19}") { - is_underflow!(i64::from_lexical(i.as_bytes())); - } - - #[test] - fn i64_double_sign_proptest(i in r"[+-]{2}[0-9]{18}") { - is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn i64_sign_only_proptest(i in r"[+-]") { - is_empty!(i64::from_lexical(i.as_bytes())); - } - - #[test] - fn i64_trailing_digits_proptest(i in r"[+-]?[0-9]{18}\D[0-9]{2}") { - is_invalid_digit_match!(i64::from_lexical(i.as_bytes()), 18 | 19); - } - - #[test] - fn u128_invalid_proptest(i in r"[+]?[0-9]{38}\D") { - is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); - } - - #[test] - fn u128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { - is_overflow!(u128::from_lexical(i.as_bytes())); - } - - #[test] - fn u128_negative_proptest(i in r"[-][1-9][0-9]{39}") { - is_invalid_digit!(u128::from_lexical(i.as_bytes())); - } - - #[test] - fn u128_double_sign_proptest(i in r"[+]{2}[0-9]{38}") { - is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn u128_sign_only_proptest(i in r"[+]") { - is_empty!(u128::from_lexical(i.as_bytes())); - } - - #[test] - fn u128_trailing_digits_proptest(i in r"[+]?[0-9]{38}\D[0-9]{2}") { - is_invalid_digit_match!(u128::from_lexical(i.as_bytes()), 38 | 39); - } - - #[test] - fn i128_invalid_proptest(i in r"[+-]?[0-9]{38}\D") { - is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); - } - - #[test] - fn i128_overflow_proptest(i in r"[+]?[1-9][0-9]{39}") { - is_overflow!(i128::from_lexical(i.as_bytes())); - } - - #[test] - fn i128_underflow_proptest(i in r"-[1-9][0-9]{39}") { - is_underflow!(i128::from_lexical(i.as_bytes())); - } - - #[test] - fn i128_double_sign_proptest(i in r"[+-]{2}[0-9]{38}") { - is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 1); - } - - #[test] - fn i128_sign_only_proptest(i in r"[+-]") { - is_empty!(i128::from_lexical(i.as_bytes())); - } - - #[test] - fn i128_trailing_digits_proptest(i in r"[+-]?[0-9]{38}\D[0-9]{2}") { - is_invalid_digit_match!(i128::from_lexical(i.as_bytes()), 38 | 39); - } -} diff --git a/lexical-parse-integer/tests/util.rs b/lexical-parse-integer/tests/util.rs index d846be3f..cb128333 100644 --- a/lexical-parse-integer/tests/util.rs +++ b/lexical-parse-integer/tests/util.rs @@ -2,63 +2,8 @@ #[cfg(feature = "power-of-two")] use lexical_util::format::NumberFormatBuilder; -use proptest::prelude::*; -pub(crate) use quickcheck::QuickCheck; #[cfg(feature = "power-of-two")] pub const fn from_radix(radix: u8) -> u128 { NumberFormatBuilder::from_radix(radix) } - -pub fn default_proptest_config() -> ProptestConfig { - ProptestConfig { - cases: if cfg!(miri) { - 10 - } else { - ProptestConfig::default().cases - }, - max_shrink_iters: if cfg!(miri) { - 10 - } else { - ProptestConfig::default().max_shrink_iters - }, - failure_persistence: if cfg!(miri) { - None - } else { - ProptestConfig::default().failure_persistence - }, - ..ProptestConfig::default() - } -} - -// This is almost identical to quickcheck's itself, just to add default -// arguments https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 -// The code is unlicensed. -#[macro_export] -macro_rules! default_quickcheck { - (@as_items $($i:item)*) => ($($i)*); - { - $( - $(#[$m:meta])* - fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { - $($code:tt)* - } - )* - } => ( - $crate::default_quickcheck! { - @as_items - $( - #[test] - $(#[$m])* - fn $fn_name() { - fn prop($($arg_name: $arg_ty),*) -> $ret { - $($code)* - } - $crate::util::QuickCheck::new() - .max_tests(if cfg!(miri) { 10 } else { 10_000 }) - .quickcheck(prop as fn($($arg_ty),*) -> $ret); - } - )* - } - ) -} diff --git a/lexical-util/Cargo.toml b/lexical-util/Cargo.toml index 02ff05ed..4553fcf0 100644 --- a/lexical-util/Cargo.toml +++ b/lexical-util/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-util" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.6" -rust-version = "1.63.0" +rust-version = "1.60.0" exclude = [ "assets/*", "docs/*", @@ -22,14 +22,6 @@ exclude = [ static_assertions = "1" float16 = { version = "0.1.0", optional = true } -[dev-dependencies] -# FIXME: Replace back to "1.0.4" once the PR is merged. -# There's an issue in quickcheck due to an infinitely repeating shrinker. -# Issue: https://github.com/BurntSushi/quickcheck/issues/295 -# Fix: https://github.com/BurntSushi/quickcheck/pull/296 -quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } -proptest = ">=1.5.0" - # FEATURES # -------- # In order to improve compile times, we have separate support diff --git a/lexical-util/tests/bf16_tests.rs b/lexical-util/tests/bf16_tests.rs index 8c11c4bc..e193a265 100644 --- a/lexical-util/tests/bf16_tests.rs +++ b/lexical-util/tests/bf16_tests.rs @@ -1,12 +1,7 @@ #![cfg(feature = "f16")] -mod util; - use lexical_util::bf16::bf16; use lexical_util::num::Float; -use proptest::prelude::*; - -use crate::util::default_proptest_config; #[test] fn as_f32_test() { @@ -47,28 +42,3 @@ fn math_tests() { assert_eq!(bf16::ONE - bf16::ONE, bf16::ZERO); assert_eq!(bf16::ONE % bf16::ONE, bf16::ZERO); } - -default_quickcheck! { - fn f32_roundtrip_quickcheck(x: u16) -> bool { - let f = bf16::from_bits(x).as_f32_const(); - if f.is_nan() { - bf16::from_f32_const(f).is_nan() - } else { - bf16::from_f32_const(f).to_bits() == x - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { - let f = bf16::from_bits(x).to_f32_const(); - if f.is_nan() { - prop_assert!(bf16::from_f32_const(f).is_nan()); - } else { - prop_assert_eq!(bf16::from_f32_const(f).to_bits(), x); - } - } -} diff --git a/lexical-util/tests/f16_tests.rs b/lexical-util/tests/f16_tests.rs index 5fd54ab7..39798124 100644 --- a/lexical-util/tests/f16_tests.rs +++ b/lexical-util/tests/f16_tests.rs @@ -1,12 +1,7 @@ #![cfg(feature = "f16")] -mod util; - use lexical_util::f16::f16; use lexical_util::num::Float; -use proptest::prelude::*; - -use crate::util::default_proptest_config; #[test] fn as_f32_test() { @@ -44,28 +39,3 @@ fn math_tests() { assert_eq!(f16::ONE - f16::ONE, f16::ZERO); assert_eq!(f16::ONE % f16::ONE, f16::ZERO); } - -default_quickcheck! { - fn f32_roundtrip_quickcheck(x: u16) -> bool { - let f = f16::from_bits(x).as_f32_const(); - if f.is_nan() { - f16::from_f32_const(f).is_nan() - } else { - f16::from_f32_const(f).to_bits() == x - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_roundtrip_proptest(x in u16::MIN..u16::MAX) { - let f = f16::from_bits(x).as_f32_const(); - if f.is_nan() { - prop_assert!(f16::from_f32_const(f).is_nan()); - } else { - prop_assert_eq!(f16::from_f32_const(f).to_bits(), x); - } - } -} diff --git a/lexical-write-float/Cargo.toml b/lexical-write-float/Cargo.toml index 58ae1b0a..219d9d37 100644 --- a/lexical-write-float/Cargo.toml +++ b/lexical-write-float/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-write-float" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.5" -rust-version = "1.63.0" +rust-version = "1.60.0" exclude = [ "assets/*", "docs/*", @@ -33,16 +33,6 @@ features = [] [dependencies] static_assertions = "1" -[dev-dependencies] -approx = "0.5.0" -# FIXME: Replace back to "1.0.4" once the PR is merged. -# There's an issue in quickcheck due to an infinitely repeating shrinker. -# Issue: https://github.com/BurntSushi/quickcheck/issues/295 -# Fix: https://github.com/BurntSushi/quickcheck/pull/296 -quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } -proptest = ">=1.5.0" -fraction = "0.15.0" - [features] default = ["std"] # Use the standard library. diff --git a/lexical-write-float/docs/Benchmarks.md b/lexical-write-float/docs/Benchmarks.md index 53d48c2b..f38630f4 100644 --- a/lexical-write-float/docs/Benchmarks.md +++ b/lexical-write-float/docs/Benchmarks.md @@ -1,6 +1,6 @@ # Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.13.12/Fedora 34, and run against commit [5955fe3](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/5955fe34ead65d94b57ff3caff14122bcdd48b02). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/write-float). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.13.12/Fedora 34, and run against commit [5955fe3](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/5955fe34ead65d94b57ff3caff14122bcdd48b02). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/write-float). **JSON** diff --git a/lexical-write-float/docs/CompactBenchmarks.md b/lexical-write-float/docs/CompactBenchmarks.md index 9a10b280..ba7e1d69 100644 --- a/lexical-write-float/docs/CompactBenchmarks.md +++ b/lexical-write-float/docs/CompactBenchmarks.md @@ -1,6 +1,6 @@ # Compact Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.13.12/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/write-float). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.13.12/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/write-float). **JSON** diff --git a/lexical-write-float/tests/algorithm_tests.rs b/lexical-write-float/tests/algorithm_tests.rs index c957f2e9..5a6b35db 100644 --- a/lexical-write-float/tests/algorithm_tests.rs +++ b/lexical-write-float/tests/algorithm_tests.rs @@ -1,7 +1,5 @@ #![cfg(not(feature = "compact"))] -mod util; - use core::num; use lexical_util::constants::BUFFER_SIZE; @@ -10,9 +8,6 @@ use lexical_util::num::Float; use lexical_write_float::algorithm::DragonboxFloat; use lexical_write_float::float::{ExtendedFloat80, RawFloat}; use lexical_write_float::{algorithm, Options, RoundMode}; -use proptest::prelude::*; - -use crate::util::default_proptest_config; const DECIMAL: u128 = NumberFormatBuilder::decimal(); @@ -756,63 +751,3 @@ fn is_left_endpoint_test() { assert_eq!(algorithm::is_left_endpoint::(3), true); assert_eq!(algorithm::is_left_endpoint::(4), false); } - -default_quickcheck! { - fn f32_quickcheck(f: f32) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if f.is_special() { - true - } else { - let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - roundtrip == Ok(f) - } - } - - fn f64_quickcheck(f: f64) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if f.is_special() { - true - } else { - let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - roundtrip == Ok(f) - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_proptest(f in f32::MIN..f32::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if !f.is_special() { - let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - prop_assert_eq!(roundtrip, Ok(f)) - } - } - - #[test] - fn f64_proptest(f in f64::MIN..f64::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if !f.is_special() { - let count = algorithm::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - prop_assert_eq!(roundtrip, Ok(f)) - } - } -} diff --git a/lexical-write-float/tests/api_tests.rs b/lexical-write-float/tests/api_tests.rs index 81309f3d..e5e22140 100644 --- a/lexical-write-float/tests/api_tests.rs +++ b/lexical-write-float/tests/api_tests.rs @@ -1,15 +1,6 @@ -mod util; - -#[cfg(feature = "f16")] -use lexical_util::bf16::bf16; use lexical_util::constants::BUFFER_SIZE; -#[cfg(feature = "f16")] -use lexical_util::f16::f16; use lexical_util::format::STANDARD; use lexical_write_float::{Options, ToLexical, ToLexicalWithOptions}; -use proptest::prelude::*; - -use crate::util::default_proptest_config; #[test] fn error_tests() { @@ -87,83 +78,3 @@ fn hex_test() { let result = float.to_lexical_with_options::(&mut buffer, &HEX_OPTIONS); assert_eq!(result, b"3.039^12"); } - -default_quickcheck! { - fn f32_quickcheck(f: f32) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - roundtrip.is_ok() && roundtrip.unwrap().is_nan() - } else { - roundtrip == Ok(f) - } - } - - fn f64_quickcheck(f: f64) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - roundtrip.is_ok() && roundtrip.unwrap().is_nan() - } else { - roundtrip == Ok(f) - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_proptest(f in f32::MIN..f32::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); - } else { - prop_assert_eq!(roundtrip, Ok(f)); - } - } - - #[test] - fn f64_proptest(f in f64::MIN..f64::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); - } else { - prop_assert_eq!(roundtrip, Ok(f)); - } - } - - #[test] - #[cfg(feature = "f16")] - fn f16_proptest(bits in u16::MIN..u16::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let f = f16::from_bits(bits); - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); - } else { - prop_assert_eq!(roundtrip, Ok(f.as_f32())); - } - } - - #[test] - #[cfg(feature = "f16")] - fn bf16_proptest(bits in u16::MIN..u16::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let f = bf16::from_bits(bits); - let actual = unsafe { std::str::from_utf8_unchecked(f.to_lexical(&mut buffer)) }; - let roundtrip = actual.parse::(); - if f.is_nan() { - prop_assert!(roundtrip.is_ok() && roundtrip.unwrap().is_nan()); - } else { - prop_assert_eq!(roundtrip, Ok(f.as_f32())); - } - } -} diff --git a/lexical-write-float/tests/binary_tests.rs b/lexical-write-float/tests/binary_tests.rs index c5f0b506..29c1004b 100644 --- a/lexical-write-float/tests/binary_tests.rs +++ b/lexical-write-float/tests/binary_tests.rs @@ -1,8 +1,5 @@ #![cfg(feature = "power-of-two")] -mod parse_radix; -mod util; - use core::num; use lexical_util::constants::{FormattedSize, BUFFER_SIZE}; @@ -11,10 +8,6 @@ use lexical_util::num::{Float, Integer}; use lexical_write_float::options::RoundMode; use lexical_write_float::{binary, Options}; use lexical_write_integer::write::WriteInteger; -use parse_radix::{parse_f32, parse_f64}; -use proptest::prelude::*; - -use crate::util::default_proptest_config; const BINARY: u128 = NumberFormatBuilder::binary(); const BASE4: u128 = NumberFormatBuilder::from_radix(4); @@ -1086,109 +1079,3 @@ fn write_float_test() { write_float::<_, BINARY>(1.2345678901234567890e2f64, &truncate, "1111011"); write_float::<_, BINARY>(1.2345678901234567890e2f64, &round, "1111011.1"); } - -default_quickcheck! { - fn f32_binary_quickcheck(f: f32) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if f.is_special() { - true - } else { - let f = f.abs(); - let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); - let roundtrip = parse_f32(&buffer[..count], 2, b'e'); - roundtrip == f - } - } - - fn f32_octal_quickcheck(f: f32) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if f.is_special() { - true - } else { - let f = f.abs(); - let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); - let roundtrip = parse_f32(&buffer[..count], 8, b'e'); - roundtrip == f - } - } - - fn f64_binary_quickcheck(f: f64) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if f.is_special() { - true - } else { - let f = f.abs(); - let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); - let roundtrip = parse_f64(&buffer[..count], 2, b'e'); - roundtrip == f - } - } - - fn f64_octal_quickcheck(f: f64) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if f.is_special() { - true - } else { - let f = f.abs(); - let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); - let roundtrip = parse_f64(&buffer[..count], 8, b'e'); - roundtrip == f - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_binary_proptest(f in f32::MIN..f32::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if !f.is_special() { - let f = f.abs(); - let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); - let roundtrip = parse_f32(&buffer[..count], 2, b'e'); - prop_assert_eq!(roundtrip, f) - } - } - - #[test] - fn f32_octal_proptest(f in f32::MIN..f32::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if !f.is_special() { - let f = f.abs(); - let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); - let roundtrip = parse_f32(&buffer[..count], 8, b'e'); - prop_assert_eq!(roundtrip, f) - } - } - - #[test] - fn f64_binary_proptest(f in f64::MIN..f64::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if !f.is_special() { - let f = f.abs(); - let count = binary::write_float::<_, BINARY>(f, &mut buffer, &options); - let roundtrip = parse_f64(&buffer[..count], 2, b'e'); - prop_assert_eq!(roundtrip, f) - } - } - - #[test] - fn f64_octal_proptest(f in f64::MIN..f64::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - if !f.is_special() { - let f = f.abs(); - let count = binary::write_float::<_, OCTAL>(f, &mut buffer, &options); - let roundtrip = parse_f64(&buffer[..count], 8, b'e'); - prop_assert_eq!(roundtrip, f) - } - } -} diff --git a/lexical-write-float/tests/compact_tests.rs b/lexical-write-float/tests/compact_tests.rs index 8fa4fb1b..37c0c11e 100644 --- a/lexical-write-float/tests/compact_tests.rs +++ b/lexical-write-float/tests/compact_tests.rs @@ -1,17 +1,11 @@ #![cfg(feature = "compact")] -mod util; - use core::num; use lexical_util::constants::BUFFER_SIZE; use lexical_util::format::NumberFormatBuilder; -use lexical_util::num::Float; use lexical_write_float::float::{ExtendedFloat80, RawFloat}; use lexical_write_float::{compact, Options, RoundMode}; -use proptest::prelude::*; - -use crate::util::default_proptest_config; const DECIMAL: u128 = NumberFormatBuilder::decimal(); @@ -673,63 +667,3 @@ fn f64_roundtrip_test() { assert_eq!(roundtrip, Ok(float)); } } - -default_quickcheck! { - fn f32_quickcheck(f: f32) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if f.is_special() { - true - } else { - let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - roundtrip == Ok(f) - } - } - - fn f64_quickcheck(f: f64) -> bool { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if f.is_special() { - true - } else { - let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - roundtrip == Ok(f) - } - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn f32_proptest(f in f32::MIN..f32::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if !f.is_special() { - let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - prop_assert_eq!(roundtrip, Ok(f)) - } - } - - #[test] - fn f64_proptest(f in f64::MIN..f64::MAX) { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = Options::builder().build().unwrap(); - let f = f.abs(); - if !f.is_special() { - let count = compact::write_float::<_, DECIMAL>(f, &mut buffer, &options); - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - let roundtrip = actual.parse::(); - prop_assert_eq!(roundtrip, Ok(f)) - } - } -} diff --git a/lexical-write-integer/Cargo.toml b/lexical-write-integer/Cargo.toml index d80b05eb..9c4302ab 100644 --- a/lexical-write-integer/Cargo.toml +++ b/lexical-write-integer/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical-write-integer" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "1.0.5" -rust-version = "1.63.0" +rust-version = "1.60.0" exclude = [ "assets/*", "docs/*", @@ -27,15 +27,6 @@ path = "../lexical-util" default-features = false features = ["write-integers"] -[dev-dependencies] -# FIXME: Replace back to "1.0.4" once the PR is merged. -# There's an issue in quickcheck due to an infinitely repeating shrinker. -# Issue: https://github.com/BurntSushi/quickcheck/issues/295 -# Fix: https://github.com/BurntSushi/quickcheck/pull/296 -quickcheck = { git = "https://github.com/Alexhuszagh/quickcheck/", branch = "i32min-shrink-bound-legacy" } -proptest = ">=1.5.0" -rustversion = ">=1.0.18" - [features] default = ["std"] # Use the standard library. diff --git a/lexical-write-integer/docs/Benchmarks.md b/lexical-write-integer/docs/Benchmarks.md index 6ee5c0fc..45a42298 100644 --- a/lexical-write-integer/docs/Benchmarks.md +++ b/lexical-write-integer/docs/Benchmarks.md @@ -1,6 +1,6 @@ # Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [3e4e1c4](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/3e4e1c446b19d30cc56f2f8e7078242f4f72c7a3). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/blob/main/lexical-benchmark/write-integer). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [3e4e1c4](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/3e4e1c446b19d30cc56f2f8e7078242f4f72c7a3). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/blob/main/extras/benchmark/write-integer). # Random diff --git a/lexical-write-integer/docs/CompactBenchmarks.md b/lexical-write-integer/docs/CompactBenchmarks.md index f0cfdfb2..fd5e5bac 100644 --- a/lexical-write-integer/docs/CompactBenchmarks.md +++ b/lexical-write-integer/docs/CompactBenchmarks.md @@ -1,6 +1,6 @@ # Compact Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/blob/main/lexical-benchmark/write-integer). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [961aefc](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/961aefc5d7c1f4eb8b10c043a585644bc891c832). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/blob/main/extras/benchmark/write-integer). # Random diff --git a/lexical-write-integer/docs/RadixBenchmarks.md b/lexical-write-integer/docs/RadixBenchmarks.md index d11819a6..0d2357c5 100644 --- a/lexical-write-integer/docs/RadixBenchmarks.md +++ b/lexical-write-integer/docs/RadixBenchmarks.md @@ -1,6 +1,6 @@ # Benchmarks -These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [469e805](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/469e8053a5d1c8b3592840cf97a5a28511e2651d). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/lexical-benchmark/write-integer). +These benchmarks were run on an `Intel(R) Core(TM) i7-6560U CPU @ 2.20GHz` processor, on Linux 5.12.5/Fedora 34, and run against commit [469e805](https://github.com/Alexhuszagh/rust-lexical-experimental/commit/469e8053a5d1c8b3592840cf97a5a28511e2651d). The Rust compiler version was `rustc 1.55.0-nightly (b41936b92 2021-07-20)`. The exact code and data used to run the benchmark can be seen [here](https://github.com/Alexhuszagh/rust-lexical/tree/main/extras/benchmark/write-integer). Since there is no analogous feature in Rust's fmt, or any 3rd party library, this merely benchmarks decimal strings while the `radix` feature is enabled. This is because the `radix` feature can add numerous code paths, which could affect code generation and therefore performance, and therefore exists to show no major regressions occur. diff --git a/lexical-write-integer/tests/api_tests.rs b/lexical-write-integer/tests/api_tests.rs index 635fe713..df4e4d6a 100644 --- a/lexical-write-integer/tests/api_tests.rs +++ b/lexical-write-integer/tests/api_tests.rs @@ -9,12 +9,9 @@ use lexical_util::constants::BUFFER_SIZE; use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; use lexical_write_integer::{Options, ToLexical, ToLexicalWithOptions}; -use proptest::prelude::*; #[cfg(feature = "radix")] use util::from_radix; -use crate::util::default_proptest_config; - trait Roundtrip: ToLexical + ToLexicalWithOptions + FromStr { #[allow(dead_code)] fn from_str_radix(src: &str, radix: u32) -> Result; @@ -229,57 +226,6 @@ where string.parse::().unwrap() } -#[cfg(feature = "radix")] -fn roundtrip_radix(x: T, radix: u32) -> T -where - T: Roundtrip, - ::Err: Debug, -{ - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let options = T::Options::default(); - // Trick it into assuming we have a valid radix. - let bytes = match radix { - 2 => x.to_lexical_with_options::<{ from_radix(2) }>(&mut buffer, &options), - 3 => x.to_lexical_with_options::<{ from_radix(3) }>(&mut buffer, &options), - 4 => x.to_lexical_with_options::<{ from_radix(4) }>(&mut buffer, &options), - 5 => x.to_lexical_with_options::<{ from_radix(5) }>(&mut buffer, &options), - 6 => x.to_lexical_with_options::<{ from_radix(6) }>(&mut buffer, &options), - 7 => x.to_lexical_with_options::<{ from_radix(7) }>(&mut buffer, &options), - 8 => x.to_lexical_with_options::<{ from_radix(8) }>(&mut buffer, &options), - 9 => x.to_lexical_with_options::<{ from_radix(9) }>(&mut buffer, &options), - 10 => x.to_lexical_with_options::<{ from_radix(10) }>(&mut buffer, &options), - 11 => x.to_lexical_with_options::<{ from_radix(11) }>(&mut buffer, &options), - 12 => x.to_lexical_with_options::<{ from_radix(12) }>(&mut buffer, &options), - 13 => x.to_lexical_with_options::<{ from_radix(13) }>(&mut buffer, &options), - 14 => x.to_lexical_with_options::<{ from_radix(14) }>(&mut buffer, &options), - 15 => x.to_lexical_with_options::<{ from_radix(15) }>(&mut buffer, &options), - 16 => x.to_lexical_with_options::<{ from_radix(16) }>(&mut buffer, &options), - 17 => x.to_lexical_with_options::<{ from_radix(17) }>(&mut buffer, &options), - 18 => x.to_lexical_with_options::<{ from_radix(18) }>(&mut buffer, &options), - 19 => x.to_lexical_with_options::<{ from_radix(19) }>(&mut buffer, &options), - 20 => x.to_lexical_with_options::<{ from_radix(20) }>(&mut buffer, &options), - 21 => x.to_lexical_with_options::<{ from_radix(21) }>(&mut buffer, &options), - 22 => x.to_lexical_with_options::<{ from_radix(22) }>(&mut buffer, &options), - 23 => x.to_lexical_with_options::<{ from_radix(23) }>(&mut buffer, &options), - 24 => x.to_lexical_with_options::<{ from_radix(24) }>(&mut buffer, &options), - 25 => x.to_lexical_with_options::<{ from_radix(25) }>(&mut buffer, &options), - 26 => x.to_lexical_with_options::<{ from_radix(26) }>(&mut buffer, &options), - 27 => x.to_lexical_with_options::<{ from_radix(27) }>(&mut buffer, &options), - 28 => x.to_lexical_with_options::<{ from_radix(28) }>(&mut buffer, &options), - 29 => x.to_lexical_with_options::<{ from_radix(29) }>(&mut buffer, &options), - 30 => x.to_lexical_with_options::<{ from_radix(30) }>(&mut buffer, &options), - 31 => x.to_lexical_with_options::<{ from_radix(31) }>(&mut buffer, &options), - 32 => x.to_lexical_with_options::<{ from_radix(32) }>(&mut buffer, &options), - 33 => x.to_lexical_with_options::<{ from_radix(33) }>(&mut buffer, &options), - 34 => x.to_lexical_with_options::<{ from_radix(34) }>(&mut buffer, &options), - 35 => x.to_lexical_with_options::<{ from_radix(35) }>(&mut buffer, &options), - 36 => x.to_lexical_with_options::<{ from_radix(36) }>(&mut buffer, &options), - _ => unreachable!(), - }; - let string = unsafe { from_utf8_unchecked(bytes) }; - T::from_str_radix(string, radix).unwrap() -} - #[test] fn u8_pow2_test() { let values: &[u8] = @@ -1187,197 +1133,6 @@ fn u128_pow10_test() { } } -default_quickcheck! { - fn u8_quickcheck(i: u8) -> bool { - i == roundtrip(i) - } - - fn u16_quickcheck(i: u16) -> bool { - i == roundtrip(i) - } - - fn u32_quickcheck(i: u32) -> bool { - i == roundtrip(i) - } - - fn u64_quickcheck(i: u64) -> bool { - i == roundtrip(i) - } - - fn u128_quickcheck(i: u128) -> bool { - i == roundtrip(i) - } - - fn usize_quickcheck(i: usize) -> bool { - i == roundtrip(i) - } - - fn i8_quickcheck(i: i8) -> bool { - i == roundtrip(i) - } - - fn i16_quickcheck(i: i16) -> bool { - i == roundtrip(i) - } - - fn i32_quickcheck(i: i32) -> bool { - i == roundtrip(i) - } - - fn i64_quickcheck(i: i64) -> bool { - i == roundtrip(i) - } - - fn i128_quickcheck(i: i128) -> bool { - i == roundtrip(i) - } - - fn isize_quickcheck(i: isize) -> bool { - i == roundtrip(i) - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn u8_proptest(i in u8::MIN..u8::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn i8_proptest(i in i8::MIN..i8::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn u16_proptest(i in u16::MIN..u16::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn i16_proptest(i in i16::MIN..i16::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn u32_proptest(i in u32::MIN..u32::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn i32_proptest(i in i32::MIN..i32::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn u64_proptest(i in u64::MIN..u64::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn i64_proptest(i in i64::MIN..i64::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn u128_proptest(i in u128::MIN..u128::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn i128_proptest(i in i128::MIN..i128::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn usize_proptest(i in usize::MIN..usize::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn isize_proptest(i in isize::MIN..isize::MAX) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - fn jeaiii_magic_10u64_proptest(i in 10_0000_0000..100_0000_0000u64) { - prop_assert_eq!(i, roundtrip(i)); - } - - #[test] - #[cfg(feature = "radix")] - fn u8_proptest_radix(i in u8::MIN..u8::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn i8_proptest_radix(i in i8::MIN..i8::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn u16_proptest_radix(i in u16::MIN..u16::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn i16_proptest_radix(i in i16::MIN..i16::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn u32_proptest_radix(i in u32::MIN..u32::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn i32_proptest_radix(i in i32::MIN..i32::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn u64_proptest_radix(i in u64::MIN..u64::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn i64_proptest_radix(i in i64::MIN..i64::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn u128_proptest_radix(i in u128::MIN..u128::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn i128_proptest_radix(i in i128::MIN..i128::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn usize_proptest_radix(i in usize::MIN..usize::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } - - #[test] - #[cfg(feature = "radix")] - fn isize_proptest_radix(i in isize::MIN..isize::MAX, radix in 2u32..=36) { - prop_assert_eq!(i, roundtrip_radix(i, radix)); - } -} - #[test] #[should_panic] fn i8_buffer_test() { diff --git a/lexical-write-integer/tests/decimal_tests.rs b/lexical-write-integer/tests/decimal_tests.rs index 698bbb40..bf55d35d 100644 --- a/lexical-write-integer/tests/decimal_tests.rs +++ b/lexical-write-integer/tests/decimal_tests.rs @@ -1,8 +1,5 @@ #![cfg(not(feature = "compact"))] -mod util; - -use lexical_util::num::UnsignedInteger; use lexical_write_integer::decimal::{self, Decimal, DecimalCount}; #[test] @@ -399,42 +396,3 @@ fn u128toa_test() { assert_eq!(340282366920938463463374607431768211455u128.decimal(&mut buffer), 39); assert_eq!(&buffer[..39], b"340282366920938463463374607431768211455"); } - -fn slow_digit_count(x: T) -> usize { - x.to_string().len() -} - -default_quickcheck! { - fn u32_digit_count_quickcheck(x: u32) -> bool { - slow_digit_count(x) == x.decimal_count() - } - - fn u64_digit_count_quickcheck(x: u64) -> bool { - slow_digit_count(x) == x.decimal_count() - } - - fn u128_digit_count_quickcheck(x: u128) -> bool { - slow_digit_count(x) == x.decimal_count() - } - - fn u32toa_quickcheck(x: u32) -> bool { - let actual = x.to_string(); - let mut buffer = [b'\x00'; 16]; - actual.len() == x.decimal(&mut buffer) && - &buffer[..actual.len()] == actual.as_bytes() - } - - fn u64toa_quickcheck(x: u64) -> bool { - let actual = x.to_string(); - let mut buffer = [b'\x00'; 32]; - actual.len() == x.decimal(&mut buffer) && - &buffer[..actual.len()] == actual.as_bytes() - } - - fn u128toa_quickcheck(x: u128) -> bool { - let actual = x.to_string(); - let mut buffer = [b'\x00'; 48]; - actual.len() == x.decimal(&mut buffer) && - &buffer[..actual.len()] == actual.as_bytes() - } -} diff --git a/lexical-write-integer/tests/digit_count_tests.rs b/lexical-write-integer/tests/digit_count_tests.rs index 84c9f5b7..85d05e39 100644 --- a/lexical-write-integer/tests/digit_count_tests.rs +++ b/lexical-write-integer/tests/digit_count_tests.rs @@ -2,11 +2,7 @@ mod util; -use lexical_write_integer::decimal::DecimalCount; use lexical_write_integer::digit_count::{self, DigitCount}; -use proptest::prelude::*; - -use crate::util::default_proptest_config; #[test] fn fast_log2_test() { @@ -23,15 +19,6 @@ fn fast_log2_test() { assert_eq!(digit_count::fast_log2(u32::MAX), 31); } -fn slow_log2(x: u32) -> usize { - // Slow approach to calculating a log2, using floats. - if x == 0 { - 0 - } else { - (x as f64).log2().floor() as usize - } -} - #[test] fn base10_count_test() { assert_eq!(1, 0u32.digit_count(10)); @@ -164,80 +151,3 @@ fn base32_count_test() { } } } - -default_quickcheck! { - fn decimal_count_quickcheck(x: u32) -> bool { - x.digit_count(10) == x.decimal_count() - } - - fn fast_log2_quickcheck(x: u32) -> bool { - slow_log2(x) == digit_count::fast_log2(x) - } -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn decimal_slow_u64_test(x: u64) { - prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10)); - } - - #[test] - fn basen_slow_u64_test(x: u64, power in 1u32..=5) { - let radix = 2u32.pow(power); - prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix)); - } - - #[test] - fn decimal_slow_u128_test(x: u128) { - prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10)); - } - - #[test] - #[cfg(feature = "power-of-two")] - fn basen_slow_u128_test(x: u128, power in 1u32..=5) { - let radix = 2u32.pow(power); - prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix)); - } -} - -#[rustversion::since(1.67)] -macro_rules! ilog { - ($x:ident, $radix:expr) => {{ - if $x > 0 { - $x.ilog($radix as _) as usize - } else { - 0usize - } - }}; -} - -#[rustversion::since(1.67)] -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - fn basen_u64_test(x: u64, radix in 2u32..=36) { - prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); - } - - #[test] - #[cfg(feature = "radix")] - fn basen_u128_test(x: u128, radix in 2u32..=36) { - prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); - } - - #[test] - #[cfg(all(feature = "power-of-two", not(feature = "radix")))] - fn basen_u128_test(x: u128, power in 1u32..=5) { - let radix = 2u32.pow(power); - prop_assert_eq!(x.digit_count(radix), ilog!(x, radix) + 1); - } - - #[test] - #[cfg(not(feature = "power-of-two"))] - fn basen_u128_test(x: u128) { - prop_assert_eq!(x.digit_count(10), ilog!(x, 10) + 1); - } -} diff --git a/lexical-write-integer/tests/radix_tests.rs b/lexical-write-integer/tests/radix_tests.rs index 15b9d751..afa51da5 100644 --- a/lexical-write-integer/tests/radix_tests.rs +++ b/lexical-write-integer/tests/radix_tests.rs @@ -4,13 +4,13 @@ mod util; use core::num::ParseIntError; +#[cfg(feature = "radix")] use core::str::from_utf8_unchecked; -use lexical_util::{constants::BUFFER_SIZE, num::UnsignedInteger}; +use lexical_util::constants::BUFFER_SIZE; use lexical_write_integer::write::WriteInteger; -use proptest::prelude::*; -use crate::util::{default_proptest_config, from_radix}; +use crate::util::from_radix; pub trait FromRadix: Sized { fn from_radix(src: &str, radix: u32) -> Result; @@ -115,137 +115,3 @@ fn issue_169_tests() { b"3411233210434101044040414300210231141130323220441010334", ); } - -// We need to trick the algorithm into thinking we're using a const. -// NOTE: This needs to be broken down into 4 functions since otherwise -// we can overflow the stack, so we just never inline for each test. -#[inline(never)] -fn to_radix_2_9(x: T, radix: u32, buffer: &mut [u8]) -> usize { - match radix { - 2 => x.write_mantissa::<{ from_radix(2) }>(buffer), - 3 => x.write_mantissa::<{ from_radix(3) }>(buffer), - 4 => x.write_mantissa::<{ from_radix(4) }>(buffer), - 5 => x.write_mantissa::<{ from_radix(5) }>(buffer), - 6 => x.write_mantissa::<{ from_radix(6) }>(buffer), - 7 => x.write_mantissa::<{ from_radix(7) }>(buffer), - 8 => x.write_mantissa::<{ from_radix(8) }>(buffer), - 9 => x.write_mantissa::<{ from_radix(9) }>(buffer), - _ => unimplemented!(), - } -} - -#[inline(never)] -fn to_radix_10_18(x: T, radix: u32, buffer: &mut [u8]) -> usize { - match radix { - 10 => x.write_mantissa::<{ from_radix(10) }>(buffer), - 11 => x.write_mantissa::<{ from_radix(11) }>(buffer), - 12 => x.write_mantissa::<{ from_radix(12) }>(buffer), - 13 => x.write_mantissa::<{ from_radix(13) }>(buffer), - 14 => x.write_mantissa::<{ from_radix(14) }>(buffer), - 15 => x.write_mantissa::<{ from_radix(15) }>(buffer), - 16 => x.write_mantissa::<{ from_radix(16) }>(buffer), - 17 => x.write_mantissa::<{ from_radix(17) }>(buffer), - 18 => x.write_mantissa::<{ from_radix(18) }>(buffer), - _ => unimplemented!(), - } -} - -#[inline(never)] -fn to_radix_19_27(x: T, radix: u32, buffer: &mut [u8]) -> usize { - match radix { - 19 => x.write_mantissa::<{ from_radix(19) }>(buffer), - 20 => x.write_mantissa::<{ from_radix(20) }>(buffer), - 21 => x.write_mantissa::<{ from_radix(21) }>(buffer), - 22 => x.write_mantissa::<{ from_radix(22) }>(buffer), - 23 => x.write_mantissa::<{ from_radix(23) }>(buffer), - 24 => x.write_mantissa::<{ from_radix(24) }>(buffer), - 25 => x.write_mantissa::<{ from_radix(25) }>(buffer), - 26 => x.write_mantissa::<{ from_radix(26) }>(buffer), - 27 => x.write_mantissa::<{ from_radix(27) }>(buffer), - _ => unimplemented!(), - } -} - -#[inline(never)] -fn to_radix_28_36(x: T, radix: u32, buffer: &mut [u8]) -> usize { - match radix { - 28 => x.write_mantissa::<{ from_radix(28) }>(buffer), - 29 => x.write_mantissa::<{ from_radix(29) }>(buffer), - 30 => x.write_mantissa::<{ from_radix(30) }>(buffer), - 31 => x.write_mantissa::<{ from_radix(31) }>(buffer), - 32 => x.write_mantissa::<{ from_radix(32) }>(buffer), - 33 => x.write_mantissa::<{ from_radix(33) }>(buffer), - 34 => x.write_mantissa::<{ from_radix(34) }>(buffer), - 35 => x.write_mantissa::<{ from_radix(35) }>(buffer), - 36 => x.write_mantissa::<{ from_radix(36) }>(buffer), - _ => unimplemented!(), - } -} - -#[inline(never)] -fn mockup( - x: T, - radix: u32, -) -> Result<(), TestCaseError> { - let mut buffer = [b'\x00'; BUFFER_SIZE]; - let count = match radix { - 2..=9 => to_radix_2_9(x, radix, &mut buffer), - 10..=18 => to_radix_10_18(x, radix, &mut buffer), - 19..=27 => to_radix_19_27(x, radix, &mut buffer), - 28..=36 => to_radix_28_36(x, radix, &mut buffer), - _ => unimplemented!(), - }; - let y = unsafe { T::from_radix(from_utf8_unchecked(&buffer[..count]), radix) }; - prop_assert_eq!(y, Ok(x)); - - Ok(()) -} - -proptest! { - #![proptest_config(default_proptest_config())] - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(feature = "radix")] - fn u32toa_proptest(x: u32, radix in 2u32..=36) { - mockup(x, radix)?; - } - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(not(feature = "radix"))] - fn u32toa_proptest(x: u32, power in 1u32..=5) { - let radix = 2u32.pow(power); - mockup(x, radix)?; - } - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(feature = "radix")] - fn u64toa_proptest(x: u64, radix in 2u32..=36) { - mockup(x, radix)?; - } - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(not(feature = "radix"))] - fn u64toa_proptest(x: u64, power in 1u32..=5) { - let radix = 2u32.pow(power); - mockup(x, radix)?; - } - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(feature = "radix")] - fn u128toa_proptest(x: u128, radix in 2u32..=36) { - mockup(x, radix)?; - } - - #[test] - #[cfg_attr(miri, ignore)] - #[cfg(not(feature = "radix"))] - fn u128toa_proptest(x: u128, power in 1u32..=5) { - let radix = 2u32.pow(power); - mockup(x, radix)?; - } -} diff --git a/lexical-write-integer/tests/util.rs b/lexical-write-integer/tests/util.rs index d846be3f..cb128333 100644 --- a/lexical-write-integer/tests/util.rs +++ b/lexical-write-integer/tests/util.rs @@ -2,63 +2,8 @@ #[cfg(feature = "power-of-two")] use lexical_util::format::NumberFormatBuilder; -use proptest::prelude::*; -pub(crate) use quickcheck::QuickCheck; #[cfg(feature = "power-of-two")] pub const fn from_radix(radix: u8) -> u128 { NumberFormatBuilder::from_radix(radix) } - -pub fn default_proptest_config() -> ProptestConfig { - ProptestConfig { - cases: if cfg!(miri) { - 10 - } else { - ProptestConfig::default().cases - }, - max_shrink_iters: if cfg!(miri) { - 10 - } else { - ProptestConfig::default().max_shrink_iters - }, - failure_persistence: if cfg!(miri) { - None - } else { - ProptestConfig::default().failure_persistence - }, - ..ProptestConfig::default() - } -} - -// This is almost identical to quickcheck's itself, just to add default -// arguments https://docs.rs/quickcheck/1.0.3/src/quickcheck/lib.rs.html#43-67 -// The code is unlicensed. -#[macro_export] -macro_rules! default_quickcheck { - (@as_items $($i:item)*) => ($($i)*); - { - $( - $(#[$m:meta])* - fn $fn_name:ident($($arg_name:ident : $arg_ty:ty),*) -> $ret:ty { - $($code:tt)* - } - )* - } => ( - $crate::default_quickcheck! { - @as_items - $( - #[test] - $(#[$m])* - fn $fn_name() { - fn prop($($arg_name: $arg_ty),*) -> $ret { - $($code)* - } - $crate::util::QuickCheck::new() - .max_tests(if cfg!(miri) { 10 } else { 10_000 }) - .quickcheck(prop as fn($($arg_ty),*) -> $ret); - } - )* - } - ) -} diff --git a/lexical/Cargo.toml b/lexical/Cargo.toml index ee1a58f0..e6759a37 100644 --- a/lexical/Cargo.toml +++ b/lexical/Cargo.toml @@ -10,7 +10,7 @@ name = "lexical" readme = "README.md" repository = "https://github.com/Alexhuszagh/rust-lexical" version = "7.0.4" -rust-version = "1.63.0" +rust-version = "1.61.0" exclude = [ "assets/*", "docs/*", diff --git a/scripts/asm.sh b/scripts/asm.sh index 1f04f059..79bbee82 100755 --- a/scripts/asm.sh +++ b/scripts/asm.sh @@ -5,7 +5,9 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") -cd "${script_dir}"/../lexical-asm +script_home=$(realpath "${script_dir}") +home=$(dirname "${script_home}") +cd "${home}"/extras/asm export RUSTFLAGS="--emit asm -C llvm-args=-x86-asm-syntax=intel" cargo +nightly build --release "$@" diff --git a/scripts/bench.sh b/scripts/bench.sh index 7c9ac2e5..073e76fd 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -5,6 +5,8 @@ set -ex # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") -cd "${script_dir}"/../lexical-benchmark +script_home=$(realpath "${script_dir}") +home=$(dirname "${script_home}") +cd "${home}/extras/benchmark" cargo test --bench '*' diff --git a/scripts/check.sh b/scripts/check.sh index 4a7211fd..5bc16904 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -6,7 +6,8 @@ set -ex # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" # Make sure we error on warnings, and don't format in-place. @@ -18,20 +19,25 @@ cargo +nightly clippy --features=format,radix -- --deny warnings cargo +nightly clippy --features=f16 -- --deny warnings cargo +nightly clippy --all-features -- --deny warnings +# all our additional unittests +cd "${home}/extras" +cargo +nightly fmt -- --check +cargo +nightly clippy --all-features -- --deny warnings + # ASM, size, and benchmarks use separate workspaces, do those separately. -cd lexical-asm +cd "${home}/extras/asm" cargo +nightly fmt -- --check cargo +nightly clippy --all-features -- --deny warnings -cd ../lexical-size +cd "${home}/extras/size" cargo +nightly fmt -- --check cargo +nightly clippy --all-features -- --deny warnings -cd ../lexical-benchmark +cd "${home}/extras/benchmark" cargo +nightly fmt -- --check cargo +nightly clippy --all-features --benches -- --deny warnings # Check our docs will be valid -cd .. +cd "${home}" RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --no-default-features RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --features=radix,format,f16 diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 40d8a5d9..d1bee034 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -13,6 +13,7 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" cargo +nightly tarpaulin diff --git a/scripts/fmt.sh b/scripts/fmt.sh index 22c883f7..641f6302 100755 --- a/scripts/fmt.sh +++ b/scripts/fmt.sh @@ -6,15 +6,19 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" cargo +nightly fmt -cd lexical-asm +cd "${home}/extras" cargo +nightly fmt -cd ../lexical-size +cd "${home}/extras/asm" cargo +nightly fmt -cd ../lexical-benchmark +cd "${home}/extras/size" +cargo +nightly fmt + +cd "${home}/extras/benchmark" cargo +nightly fmt diff --git a/scripts/fuzz.sh b/scripts/fuzz.sh index b342f4c5..f13ac846 100755 --- a/scripts/fuzz.sh +++ b/scripts/fuzz.sh @@ -6,6 +6,7 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" cargo +nightly fuzz run "$@" diff --git a/scripts/hooks.sh b/scripts/hooks.sh index 04710ad8..eb466c0e 100755 --- a/scripts/hooks.sh +++ b/scripts/hooks.sh @@ -6,7 +6,8 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" # Install formatting hook. echo 'echo "Running rustfmt and clippy checks."' > .git/hooks/pre-commit diff --git a/scripts/link.sh b/scripts/link.sh index f72434a6..9d17506b 100755 --- a/scripts/link.sh +++ b/scripts/link.sh @@ -6,7 +6,8 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" WORKSPACES=( "lexical" @@ -31,9 +32,9 @@ for workspace in "${WORKSPACES[@]}"; do done PROJECTS=( - "lexical-asm" - "lexical-benchmark" - "lexical-size" + "asm" + "benchmark" + "size" ) PROJECT_FILES=( "clippy.toml" @@ -42,7 +43,8 @@ PROJECT_FILES=( for project in "${PROJECTS[@]}"; do for file in "${PROJECT_FILES[@]}"; do - unlink "$project/$file" - ln -s ../"$file" "$project/$file" + project_file="extras/${project}/${file}" + unlink "${project_file}" + ln -s ../"${file}" "${project_file}" done done diff --git a/scripts/size.py b/scripts/size.py index 180c7f07..d8896984 100755 --- a/scripts/size.py +++ b/scripts/size.py @@ -35,6 +35,7 @@ scripts = os.path.dirname(os.path.realpath(__file__)) home = os.path.dirname(scripts) +size_home = f'{home}/extras/size' size_command = os.environ.get('SIZE', 'size') strip_command = os.environ.get('STRIP', 'strip') @@ -152,7 +153,7 @@ def plot_ax(ax, xticks): def clean(): '''Clean the project''' - os.chdir(f'{home}/lexical-size') + os.chdir(size_home) subprocess.check_call( 'cargo +nightly clean', shell=True, @@ -164,7 +165,7 @@ def clean(): def write_manifest(level): '''Write the manifest for the given optimization level.''' - manifest = f'{home}/lexical-size/Cargo.toml' + manifest = f'{size_home}/Cargo.toml' with open(f'{manifest}.in') as file: contents = file.read() @@ -183,7 +184,7 @@ def write_manifest(level): def build(args, level, is_lexical): '''Build the project.''' - os.chdir(f'{home}/lexical-size') + os.chdir(size_home) command = 'cargo +nightly build' if args.no_default_features: command = f'{command} --no-default-features' @@ -260,7 +261,7 @@ def get_sizes(level): data = {} build_type = LEVELS[level] - target = f'{home}/lexical-size/target/{build_type}' + target = f'{size_home}/target/{build_type}' for filename in os.listdir(target): path = os.path.join(target, filename) if os.path.isfile(path) and is_executable(path): @@ -282,7 +283,7 @@ def strip(level): return build_type = LEVELS[level] - target = f'{home}/lexical-size/target/{build_type}' + target = f'{size_home}/target/{build_type}' for filename in os.listdir(target): path = os.path.join(target, filename) if os.path.isfile(path) and is_executable(path): diff --git a/scripts/test.sh b/scripts/test.sh index bca981a2..75e91085 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,19 +9,27 @@ set -e # Change to our project home. script_dir=$(dirname "${BASH_SOURCE[0]}") script_home=$(realpath "${script_dir}") -cd "${script_home}"/.. +home=$(dirname "${script_home}") +cd "${home}" export RUSTFLAGS="--deny warnings" export MIRIFLAGS="-Zmiri-tag-raw-pointers" -cargo +nightly test -cargo +nightly test --all-features -if [ "$SKIP_VALGRIND" == "" ]; then - cargo +nightly valgrind test --features=radix --release -fi -if [ "$SKIP_MIRI" == "" ]; then - cargo +nightly miri test --features=radix --tests --release -fi +run_test() { + cargo +nightly test + cargo +nightly test --all-features + if [ "$SKIP_VALGRIND" == "" ]; then + cargo +nightly valgrind test --features=radix --release + fi + if [ "$SKIP_MIRI" == "" ]; then + cargo +nightly miri test --features=radix --tests --release + fi +} + +run_test +cd "${home}/extras" +run_test +cd "${home}" # Test various feature combinations. FEATURES=( @@ -35,23 +43,30 @@ FEATURES=( "format,radix" "f16" ) -if [ "$SKIP_FEATURES" == "" ]; then - for features in "${FEATURES[@]}"; do - echo "cargo test --features=$features" - cargo +nightly test --features="$features" - done -fi - -# This is very slow, but uses Valgrind to test all features. -if [ "$SKIP_VALGRIND" == "" ] && [ "$LEXICAL_VALGRIND_TEST_ALL" != "" ]; then - for features in "${FEATURES[@]}"; do - cargo +nightly valgrind test --features="$features" --release - done -fi - -# This is very slow, but uses Miri to test all features. -if [ "$SKIP_MIRI" == "" ] && [ "$LEXICAL_MIRI_TEST_ALL" != "" ]; then - for features in "${FEATURES[@]}"; do - cargo +nightly miri test --features="$features" --tests --release - done -fi +run_features() { + if [ "$SKIP_FEATURES" == "" ]; then + for features in "${FEATURES[@]}"; do + echo "cargo test --features=$features" + cargo +nightly test --features="$features" + done + fi + + # This is very slow, but uses Valgrind to test all features. + if [ "$SKIP_VALGRIND" == "" ] && [ "$LEXICAL_VALGRIND_TEST_ALL" != "" ]; then + for features in "${FEATURES[@]}"; do + cargo +nightly valgrind test --features="$features" --release + done + fi + + # This is very slow, but uses Miri to test all features. + if [ "$SKIP_MIRI" == "" ] && [ "$LEXICAL_MIRI_TEST_ALL" != "" ]; then + for features in "${FEATURES[@]}"; do + cargo +nightly miri test --features="$features" --tests --release + done + fi +} + +run_features +cd "${home}/extras" +run_features +cd "${home}" diff --git a/scripts/timings.py b/scripts/timings.py index 0a9ffe1a..7205c316 100755 --- a/scripts/timings.py +++ b/scripts/timings.py @@ -58,7 +58,7 @@ def build(args, directory=home): '''Build the project and get the timings output.''' os.chdir(directory) - command = 'cargo +nightly build -Z timings=json' + command = 'cargo +nightly build -Z unstable-options --timings=json' if args.no_default_features: command = f'{command} --no-default-features' if args.features: