From 7993aa414721a61c04519ebc7e7a7ed60e7818ed Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Thu, 12 Dec 2024 23:19:55 -0600 Subject: [PATCH] Add validation for our docs in our CI pipelines. --- lexical-parse-float/src/options.rs | 2 +- lexical-util/src/iterator.rs | 6 +-- lexical-write-float/src/options.rs | 2 +- lexical-write-integer/src/lib.rs | 60 +++++++++++++++++++++++------- scripts/check.sh | 5 +++ 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/lexical-parse-float/src/options.rs b/lexical-parse-float/src/options.rs index ea99291a..509bba16 100644 --- a/lexical-parse-float/src/options.rs +++ b/lexical-parse-float/src/options.rs @@ -9,7 +9,7 @@ use lexical_util::result::Result; use static_assertions::const_assert; /// Maximum length for a special string. -const MAX_SPECIAL_STRING_LENGTH: usize = 50; +pub const MAX_SPECIAL_STRING_LENGTH: usize = 50; /// Builder for `Options`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] diff --git a/lexical-util/src/iterator.rs b/lexical-util/src/iterator.rs index 4703bb9d..6a628811 100644 --- a/lexical-util/src/iterator.rs +++ b/lexical-util/src/iterator.rs @@ -71,7 +71,7 @@ pub unsafe trait Iter<'a> { /// This operators on the underlying buffer: that is, /// it returns if [`as_slice`] would return an empty slice. /// - /// [as_slice]: Iter::as_slice + /// [`as_slice`]: Iter::as_slice #[inline(always)] fn is_buffer_empty(&self) -> bool { self.cursor() >= self.get_buffer().len() @@ -290,8 +290,8 @@ pub trait DigitsIter<'a>: Iterator + Iter<'a> { /// If you would like to see if the cursor is at the end of the buffer, /// see [`is_buffer_empty`] instead. /// - /// [is_buffer_empty]: Iter::is_buffer_empty - /// [peek]: DigitsIter::peek + /// [`is_buffer_empty`]: Iter::is_buffer_empty + /// [`peek`]: DigitsIter::peek #[inline(always)] #[allow(clippy::wrong_self_convention)] // reason="required for peeking next item" fn is_consumed(&mut self) -> bool { diff --git a/lexical-write-float/src/options.rs b/lexical-write-float/src/options.rs index 2f0ead6a..afda918c 100644 --- a/lexical-write-float/src/options.rs +++ b/lexical-write-float/src/options.rs @@ -31,7 +31,7 @@ pub enum RoundMode { } /// Maximum length for a special string. -const MAX_SPECIAL_STRING_LENGTH: usize = 50; +pub const MAX_SPECIAL_STRING_LENGTH: usize = 50; const_assert!(MAX_SPECIAL_STRING_LENGTH < f32::FORMATTED_SIZE_DECIMAL); /// Builder for `Options`. diff --git a/lexical-write-integer/src/lib.rs b/lexical-write-integer/src/lib.rs index a0071e05..e4128ceb 100644 --- a/lexical-write-integer/src/lib.rs +++ b/lexical-write-integer/src/lib.rs @@ -62,8 +62,45 @@ //! fairly easy to demonstrate the safety as long as the caller ensures at least //! the required number of digits are provided. //! -//! Our algorithms work like this, carving off the lower digits and writing them -//! to the back of the buffer. +//! ## Decimal +//! +//! Our decimal-based digit writers are based on the [`Jeaiii Algorithm`], which +//! branches based on the number of digits and writes digits to minimize the +//! number of additions and multiplications. This avoids the need to calculate +//! the number of digits ahead of time, by just branching on the value. +//! +//! James Anhalt's itoa algorithm along with Junekey Jeon's performance tweaks +//! have excellent performance, however, this can be further optimized. Both +//! James Anhalt's and Junekey Jeon's use a binary search for determining the +//! correct number of digits to print (for 32-bit integers). +//! +//! ```text +//! /\____________ +//! / \______ \______ +//! /\ \ \ \ \ +//! 0 1 /\ /\ /\ /\ +//! 2 3 4 5 6 7 8 9 +//! ``` +//! +//! This leads to a max tree depth of 4, and the major performance bottleneck +//! with larger type sizes is the branching. A minor modification can optimize +//! this, leadingg to a max tree depth of 3 while only required 1 extra +//! comparison at the top level. Also, we invert the comparisons: oddly enough, +//! our benchmarks show doing larger comparisons then smaller improves +//! performance for numbers with both large and small numbers of digits. +//! +//! ```text +//! ____________________ +//! ___/_ __|__ \ +//! / | \ / \ /\ +//! /\ 1 0 /\ /\ 8 9 +//! 3 2 6 7 4 5 +//! ``` +//! +//! ## Radix +//! +//! Our radix-based algorithms work like this, carving off the lower digits and +//! writing them to the back of the buffer. //! //! ```rust,ignore //! let mut value = 12345u32; @@ -94,27 +131,22 @@ //! bytes[index] = table[r1]; //! } //! -//! // oontinue with radix^2 and then a single digit. +//! // continue with radix^2 and then a single digit. //! ``` //! //! We can efficiently determine at compile time if the pre-computed //! tables are large enough so there are no non-local safety considerations //! there. The current logic call stack is: //! 1. [`to_lexical`] -//! 2. [decimal][dec], compact, or radix (gets the correct tables and calls +//! 2. [`decimal`][`dec`], compact, or radix (gets the correct tables and calls //! algorithm) -//! 3. [algorithm] -//! -//! [decimal][dec], compact, and radix therefore **MUST** be safe and do type -//! check of the bounds to avoid too much exposure to unsafety. Only -//! [`algorithm`] should have any unsafety associated with it. That is, as long -//! as the direct caller has ensure the proper buffer is allocated, there are -//! non-local safety invariants. +//! 3. [`jeaiii`] //! -//! [`digit_count`]: crate::decimal::DigitCount +//! [`digit_count`]: crate::digit_count::DigitCount //! [`to_lexical`]: crate::ToLexical::to_lexical -//! [dec]: crate::decimal::Decimal::decimal -//! [`algorithm`]: crate::algorithm::algorithm +//! [`dec`]: crate::decimal::Decimal::decimal +//! [`jeaiii`]: crate::jeaiii +//! [`Jeaiii Algorithm`]: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ // We want to have the same safety guarantees as Rust core, // so we allow unused unsafe to clearly document safety guarantees. diff --git a/scripts/check.sh b/scripts/check.sh index 36b2b21d..4a7211fd 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -30,3 +30,8 @@ cargo +nightly clippy --all-features -- --deny warnings cd ../lexical-benchmark cargo +nightly fmt -- --check cargo +nightly clippy --all-features --benches -- --deny warnings + +# Check our docs will be valid +cd .. +RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --no-default-features +RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --features=radix,format,f16