From 3bd77893d5a42a6c21fa1a02c6bcbfd72709de77 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Thu, 29 Feb 2024 22:30:54 +0100 Subject: [PATCH] Sync to upstream/master --- .github/workflows/ci.yml | 19 ++++++++++ Cargo.toml | 5 ++- README.md | 4 +- fuzz/.gitignore | 7 ++-- fuzz/Cargo.toml | 3 ++ src/buffer/mod.rs | 10 ++++- src/d2s.rs | 10 +++-- src/d2s_intrinsics.rs | 10 ++--- src/d2s_small_table.rs | 2 +- src/f2s.rs | 6 ++- src/lib.rs | 1 + src/pretty/exponent.rs | 2 +- src/pretty/mantissa.rs | 2 +- src/pretty/mod.rs | 10 +++-- src/s2d.rs | 4 +- src/s2f.rs | 6 ++- tests/common_test.rs | 2 +- tests/d2s_intrinsics_test.rs | 72 ++++++++++++++++++++++++++++++++++++ tests/d2s_table_test.rs | 4 +- tests/d2s_test.rs | 12 ++++++ tests/f2s_test.rs | 3 +- 21 files changed, 161 insertions(+), 33 deletions(-) create mode 100644 tests/d2s_intrinsics_test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1f7c13..97dbc7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,12 @@ permissions: env: RUSTFLAGS: -Dwarnings +permissions: + contents: read + +env: + RUSTFLAGS: -Dwarnings + jobs: test: name: Rust ${{matrix.rust}} @@ -50,6 +56,18 @@ jobs: - run: cargo build - run: cargo build --features small + doc: + name: Documentation + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/install@cargo-docs-rs + - run: cargo docs-rs + miri: name: Miri runs-on: ubuntu-latest @@ -57,6 +75,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@miri + - run: cargo miri setup - run: cargo miri test env: MIRIFLAGS: -Zmiri-strict-provenance diff --git a/Cargo.toml b/Cargo.toml index 0deaf11..afa412f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ryu-js" version = "1.0.0" authors = ["David Tolnay ", "boa-dev"] -categories = ["value-formatting", "no-std"] +categories = ["value-formatting", "no-std", "no-std::no-alloc"] description = "Fast floating point to string conversion, ECMAScript compliant." documentation = "https://docs.rs/ryu-js" edition = "2018" @@ -35,6 +35,9 @@ doc-scrape-examples = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] +# See: https://github.com/rust-lang/rust/pull/84176 +rustdoc-args = ["--generate-link-to-definition"] + [[bench]] name = "bench" harness = false diff --git a/README.md b/README.md index 3ff74d4..89b6802 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ under the creative commons CC-BY-SA license. This Rust implementation is a line-by-line port of Ulf Adams' implementation in C, [https://github.com/ulfjack/ryu][upstream]. -*Requirements: this crate supports any compiler version back to rustc 1.64; it +*Requirements: This crate supports any compiler version back to rustc 1.64; it uses nothing from the Rust standard library so is usable from no_std crates.* [paper]: https://dl.acm.org/citation.cfm?id=3192369 @@ -54,7 +54,7 @@ You can run upstream's benchmarks with: ```console $ git clone https://github.com/ulfjack/ryu c-ryu $ cd c-ryu -$ bazel run -c opt //ryu/benchmark:ryu_benchmark -- +$ bazel run -c opt //ryu/benchmark:ryu_benchmark ``` And the same benchmark against our implementation with: diff --git a/fuzz/.gitignore b/fuzz/.gitignore index a092511..f83457a 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -1,3 +1,4 @@ -target -corpus -artifacts +artifacts/ +corpus/ +coverage/ +target/ diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 6f4fb79..a5df692 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,6 +13,9 @@ arbitrary = { version = "1", features = ["derive"] } libfuzzer-sys = "0.4" ryu-js = { path = ".." } +[features] +small = ["ryu-js/small"] + [[bin]] name = "fuzz_ryu_js" path = "fuzz_targets/fuzz_ryu_js.rs" diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 0a0fdb3..5c724f3 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -21,7 +21,7 @@ const BUFFER_SIZE: usize = MAX_BUFFER_SIZE; /// let printed = buffer.format_finite(1.234); /// assert_eq!(printed, "1.234"); /// ``` -#[derive(Copy, Clone)] +#[derive(Copy)] pub struct Buffer { bytes: [MaybeUninit; BUFFER_SIZE], } @@ -119,6 +119,14 @@ impl Buffer { } } +impl Clone for Buffer { + #[inline] + #[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072 + fn clone(&self) -> Self { + Buffer::new() + } +} + impl Default for Buffer { #[inline] #[cfg_attr(feature = "no-panic", no_panic)] diff --git a/src/d2s.rs b/src/d2s.rs index 392577a..76a8164 100644 --- a/src/d2s.rs +++ b/src/d2s.rs @@ -18,12 +18,14 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. -use crate::common::*; +use crate::common::{log10_pow2, log10_pow5, pow5bits}; #[cfg(not(feature = "small"))] -pub use crate::d2s_full_table::*; -use crate::d2s_intrinsics::*; +pub use crate::d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT}; +use crate::d2s_intrinsics::{ + div10, div100, div5, mul_shift_all_64, multiple_of_power_of_2, multiple_of_power_of_5, +}; #[cfg(feature = "small")] -pub use crate::d2s_small_table::*; +pub use crate::d2s_small_table::{compute_inv_pow5, compute_pow5}; use core::mem::MaybeUninit; pub const DOUBLE_MANTISSA_BITS: u32 = 52; diff --git a/src/d2s_intrinsics.rs b/src/d2s_intrinsics.rs index f244a4d..a4e1fb1 100644 --- a/src/d2s_intrinsics.rs +++ b/src/d2s_intrinsics.rs @@ -36,16 +36,16 @@ pub fn div100(x: u64) -> u64 { } #[cfg_attr(feature = "no-panic", inline)] -fn pow5_factor(mut value: u64) -> u32 { +pub(crate) fn pow5_factor(mut value: u64) -> u32 { + const M_INV_5: u64 = 14757395258967641293; // 5 * m_inv_5 = 1 (mod 2^64) + const N_DIV_5: u64 = 3689348814741910323; // #{ n | n = 0 (mod 2^64) } = 2^64 / 5 let mut count = 0u32; loop { debug_assert!(value != 0); - let q = div5(value); - let r = (value as u32).wrapping_sub(5u32.wrapping_mul(q as u32)); - if r != 0 { + value = value.wrapping_mul(M_INV_5); + if value > N_DIV_5 { break; } - value = q; count += 1; } count diff --git a/src/d2s_small_table.rs b/src/d2s_small_table.rs index 262fc04..b6e3223 100644 --- a/src/d2s_small_table.rs +++ b/src/d2s_small_table.rs @@ -18,7 +18,7 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. -use crate::common::*; +use crate::common::pow5bits; pub static DOUBLE_POW5_INV_SPLIT2: [(u64, u64); 15] = [ (1, 2305843009213693952), diff --git a/src/f2s.rs b/src/f2s.rs index eeb457a..987fefb 100644 --- a/src/f2s.rs +++ b/src/f2s.rs @@ -18,8 +18,10 @@ // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. -use crate::common::*; -use crate::f2s_intrinsics::*; +use crate::common::{log10_pow2, log10_pow5, pow5bits}; +use crate::f2s_intrinsics::{ + mul_pow5_div_pow2, mul_pow5_inv_div_pow2, multiple_of_power_of_2_32, multiple_of_power_of_5_32, +}; pub const FLOAT_MANTISSA_BITS: u32 = 23; pub const FLOAT_EXPONENT_BITS: u32 = 8; diff --git a/src/lib.rs b/src/lib.rs index 261195e..305731c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,7 @@ clippy::missing_panics_doc, clippy::module_name_repetitions, clippy::must_use_candidate, + clippy::needless_doctest_main, clippy::similar_names, clippy::too_many_lines, clippy::unreadable_literal, diff --git a/src/pretty/exponent.rs b/src/pretty/exponent.rs index 1654fdc..a77d0f9 100644 --- a/src/pretty/exponent.rs +++ b/src/pretty/exponent.rs @@ -1,4 +1,4 @@ -use crate::digit_table::*; +use crate::digit_table::DIGIT_TABLE; use core::ptr; #[cfg_attr(feature = "no-panic", inline)] diff --git a/src/pretty/mantissa.rs b/src/pretty/mantissa.rs index 0149f5c..552dfe3 100644 --- a/src/pretty/mantissa.rs +++ b/src/pretty/mantissa.rs @@ -1,4 +1,4 @@ -use crate::digit_table::*; +use crate::digit_table::DIGIT_TABLE; use core::ptr; #[cfg_attr(feature = "no-panic", inline)] diff --git a/src/pretty/mod.rs b/src/pretty/mod.rs index 6a53620..f69b932 100644 --- a/src/pretty/mod.rs +++ b/src/pretty/mod.rs @@ -1,11 +1,11 @@ mod exponent; mod mantissa; -use self::exponent::*; -use self::mantissa::*; +use self::exponent::{write_exponent2, write_exponent3}; +use self::mantissa::{write_mantissa, write_mantissa_long}; use crate::common; -use crate::d2s::{self, *}; -use crate::f2s::*; +use crate::d2s::{self, d2d, DOUBLE_EXPONENT_BITS, DOUBLE_MANTISSA_BITS}; +use crate::f2s::{f2d, FLOAT_EXPONENT_BITS, FLOAT_MANTISSA_BITS}; use core::ptr; #[cfg(feature = "no-panic")] use no_panic::no_panic; @@ -54,6 +54,7 @@ pub use to_fixed::format64_to_fixed; #[cfg_attr(feature = "no-panic", no_panic)] pub unsafe fn format64(f: f64, result: *mut u8) -> usize { debug_assert!(!result.is_null()); + let bits = f.to_bits(); let sign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; let ieee_mantissa = bits & ((1u64 << DOUBLE_MANTISSA_BITS) - 1); @@ -160,6 +161,7 @@ pub unsafe fn format64(f: f64, result: *mut u8) -> usize { #[cfg_attr(feature = "no-panic", no_panic)] pub unsafe fn format32(f: f32, result: *mut u8) -> usize { debug_assert!(!result.is_null()); + let bits = f.to_bits(); let sign = ((bits >> (FLOAT_MANTISSA_BITS + FLOAT_EXPONENT_BITS)) & 1) != 0; let ieee_mantissa = bits & ((1u32 << FLOAT_MANTISSA_BITS) - 1); diff --git a/src/s2d.rs b/src/s2d.rs index eb8691a..9acc4f3 100644 --- a/src/s2d.rs +++ b/src/s2d.rs @@ -1,6 +1,6 @@ -use crate::common::*; +use crate::common::{ceil_log2_pow5, log2_pow5}; use crate::d2s; -use crate::d2s_intrinsics::*; +use crate::d2s_intrinsics::{mul_shift_64, multiple_of_power_of_2, multiple_of_power_of_5}; use crate::parse::Error; #[cfg(feature = "no-panic")] use no_panic::no_panic; diff --git a/src/s2f.rs b/src/s2f.rs index cfb87f1..32e4e5c 100644 --- a/src/s2f.rs +++ b/src/s2f.rs @@ -1,6 +1,8 @@ -use crate::common::*; +use crate::common::{ceil_log2_pow5, log2_pow5}; use crate::f2s; -use crate::f2s_intrinsics::*; +use crate::f2s_intrinsics::{ + mul_pow5_div_pow2, mul_pow5_inv_div_pow2, multiple_of_power_of_2_32, multiple_of_power_of_5_32, +}; use crate::parse::Error; #[cfg(feature = "no-panic")] use no_panic::no_panic; diff --git a/tests/common_test.rs b/tests/common_test.rs index 89d82e6..29e6806 100644 --- a/tests/common_test.rs +++ b/tests/common_test.rs @@ -31,7 +31,7 @@ #[path = "../src/common.rs"] mod common; -use common::*; +use common::{ceil_log2_pow5, decimal_length9, log10_pow2, log10_pow5}; #[test] fn test_decimal_length9() { diff --git a/tests/d2s_intrinsics_test.rs b/tests/d2s_intrinsics_test.rs new file mode 100644 index 0000000..0ac80c9 --- /dev/null +++ b/tests/d2s_intrinsics_test.rs @@ -0,0 +1,72 @@ +// Translated from C to Rust. The original C code can be found at +// https://github.com/ulfjack/ryu and carries the following license: +// +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. + +#![allow(dead_code)] +#![allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::unreadable_literal +)] + +#[path = "../src/d2s_intrinsics.rs"] +mod d2s_intrinsics; + +use d2s_intrinsics::pow5_factor; + +#[test] +fn test_pow5_factor() { + assert_eq!(0, pow5_factor(1)); + assert_eq!(0, pow5_factor(2)); + assert_eq!(0, pow5_factor(3)); + assert_eq!(0, pow5_factor(4)); + assert_eq!(1, pow5_factor(5)); + assert_eq!(0, pow5_factor(6)); + assert_eq!(0, pow5_factor(7)); + assert_eq!(0, pow5_factor(8)); + assert_eq!(0, pow5_factor(9)); + assert_eq!(1, pow5_factor(10)); + + assert_eq!(0, pow5_factor(12)); + assert_eq!(0, pow5_factor(14)); + assert_eq!(0, pow5_factor(16)); + assert_eq!(0, pow5_factor(18)); + assert_eq!(1, pow5_factor(20)); + + assert_eq!(2, pow5_factor(5 * 5)); + assert_eq!(3, pow5_factor(5 * 5 * 5)); + assert_eq!(4, pow5_factor(5 * 5 * 5 * 5)); + assert_eq!(5, pow5_factor(5 * 5 * 5 * 5 * 5)); + assert_eq!(6, pow5_factor(5 * 5 * 5 * 5 * 5 * 5)); + assert_eq!(7, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5)); + assert_eq!(8, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); + assert_eq!(9, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); + assert_eq!(10, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); + + assert_eq!(0, pow5_factor(42)); + assert_eq!(1, pow5_factor(42 * 5)); + assert_eq!(2, pow5_factor(42 * 5 * 5)); + assert_eq!(3, pow5_factor(42 * 5 * 5 * 5)); + assert_eq!(4, pow5_factor(42 * 5 * 5 * 5 * 5)); + assert_eq!(5, pow5_factor(42 * 5 * 5 * 5 * 5 * 5)); + + assert_eq!(27, pow5_factor(7450580596923828125)); // 5^27, largest power of 5 < 2^64. + assert_eq!(1, pow5_factor(18446744073709551615)); // 2^64 - 1, largest multiple of 5 < 2^64. + assert_eq!(0, pow5_factor(18446744073709551614)); // 2^64 - 2, largest non-multiple of 5 < 2^64. +} diff --git a/tests/d2s_table_test.rs b/tests/d2s_table_test.rs index c3e4a7c..b592b14 100644 --- a/tests/d2s_table_test.rs +++ b/tests/d2s_table_test.rs @@ -41,8 +41,8 @@ mod d2s_intrinsics; #[path = "../src/d2s_small_table.rs"] mod d2s_small_table; -use d2s_full_table::*; -use d2s_small_table::*; +use d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT}; +use d2s_small_table::{compute_inv_pow5, compute_pow5}; #[test] fn test_compute_pow5() { diff --git a/tests/d2s_test.rs b/tests/d2s_test.rs index 4636ebb..1f28976 100644 --- a/tests/d2s_test.rs +++ b/tests/d2s_test.rs @@ -78,6 +78,18 @@ fn test_non_finite() { } } +#[test] +fn test_basic() { + assert_eq!(pretty(0.0), "0"); + assert_eq!(pretty(-0.0), "0"); + assert_eq!(pretty(1.0), "1"); + assert_eq!(pretty(-1.0), "-1"); + assert_eq!(pretty(f64::NAN.copysign(1.0)), "NaN"); + assert_eq!(pretty(f64::NAN.copysign(-1.0)), "NaN"); + assert_eq!(pretty(f64::INFINITY), "Infinity"); + assert_eq!(pretty(f64::NEG_INFINITY), "-Infinity"); +} + #[test] fn test_switch_to_subnormal() { check!(2.2250738585072014e-308); diff --git a/tests/f2s_test.rs b/tests/f2s_test.rs index 4b1ea15..7262fa7 100644 --- a/tests/f2s_test.rs +++ b/tests/f2s_test.rs @@ -74,7 +74,8 @@ fn test_basic() { assert_eq!(pretty(-0.0), "0"); assert_eq!(pretty(1.0), "1"); assert_eq!(pretty(-1.0), "-1"); - assert_eq!(pretty(f32::NAN), "NaN"); + assert_eq!(pretty(f32::NAN.copysign(1.0)), "NaN"); + assert_eq!(pretty(f32::NAN.copysign(-1.0)), "NaN"); assert_eq!(pretty(f32::INFINITY), "Infinity"); assert_eq!(pretty(f32::NEG_INFINITY), "-Infinity"); }