diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index fffa5813a..5973b5157 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -23,6 +23,7 @@ num-traits = { workspace = true } thiserror = { workspace = true } uucore = { workspace = true, features = [ "extendedbigdecimal", + "fast-inc", "format", "parser", "quoting-style", diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 5501485b6..181309ba6 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -15,7 +15,7 @@ use uucore::error::{FromIo, UResult}; use uucore::extendedbigdecimal::ExtendedBigDecimal; use uucore::format::num_format::FloatVariant; use uucore::format::{Format, num_format}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::{fast_inc::fast_inc, format_usage, help_about, help_usage}; mod error; @@ -259,71 +259,6 @@ pub fn uu_app() -> Command { ) } -/// Fast code path increment function. -/// -/// Add inc to the string val[start..end]. This operates on ASCII digits, assuming -/// val and inc are well formed. -/// -/// Returns the new value for start (can be less that the original value if we -/// have a carry or if inc > start). -/// -/// We also assume that there is enough space in val to expand if start needs -/// to be updated. -#[inline] -fn fast_inc(val: &mut [u8], start: usize, end: usize, inc: &[u8]) -> usize { - // To avoid a lot of casts to signed integers, we make sure to decrement pos - // as late as possible, so that it does not ever go negative. - let mut pos = end; - let mut carry = 0u8; - - // First loop, add all digits of inc into val. - for inc_pos in (0..inc.len()).rev() { - pos -= 1; - - let mut new_val = inc[inc_pos] + carry; - // Be careful here, only add existing digit of val. - if pos >= start { - new_val += val[pos] - b'0'; - } - if new_val > b'9' { - carry = 1; - new_val -= 10; - } else { - carry = 0; - } - val[pos] = new_val; - } - - // Done, now, if we have a carry, add that to the upper digits of val. - if carry == 0 { - return start.min(pos); - } - - return fast_inc_one(val, start, pos); -} - -#[inline] -fn fast_inc_one(val: &mut [u8], start: usize, end: usize) -> usize { - let mut pos = end; - - while pos > start { - pos -= 1; - - if val[pos] == b'9' { - // 9+1 = 10. Carry propagating, keep going. - val[pos] = b'0'; - } else { - // Carry stopped propagating, return unchanged start. - val[pos] += 1; - return start; - } - } - - // The carry propagated so far that a new digit was added. - val[start - 1] = b'1'; - start - 1 -} - /// Integer print, default format, positive increment: fast code path /// that avoids reformating digit at all iterations. fn fast_print_seq( @@ -452,58 +387,3 @@ fn print_seq( stdout.flush()?; Ok(()) } - -#[cfg(test)] -mod tests { - #[test] - fn test_fast_inc_simple() { - use crate::fast_inc; - - let mut val = [b'.', b'.', b'.', b'0', b'_']; - let inc = [b'4'].as_ref(); - assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 3); - assert_eq!(val, "...4_".as_bytes()); - assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 3); - assert_eq!(val, "...8_".as_bytes()); - assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 2); // carried 1 more digit - assert_eq!(val, "..12_".as_bytes()); - - let mut val = [b'0', b'_']; - let inc = [b'2'].as_ref(); - assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); - assert_eq!(val, "2_".as_bytes()); - assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); - assert_eq!(val, "4_".as_bytes()); - assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); - assert_eq!(val, "6_".as_bytes()); - } - - // Check that we handle increment > val correctly. - #[test] - fn test_fast_inc_large_inc() { - use crate::fast_inc; - - let mut val = [b'.', b'.', b'.', b'7', b'_']; - let inc = "543".as_bytes(); - assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 1); // carried 2 more digits - assert_eq!(val, ".550_".as_bytes()); - assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); // carried 1 more digit - assert_eq!(val, "1093_".as_bytes()); - } - - // Check that we handle longer carries - #[test] - fn test_fast_inc_carry() { - use crate::fast_inc; - - let mut val = [b'.', b'9', b'9', b'9', b'_']; - let inc = "1".as_bytes(); - assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); - assert_eq!(val, "1000_".as_bytes()); - - let mut val = [b'.', b'9', b'9', b'9', b'_']; - let inc = "11".as_bytes(); - assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); - assert_eq!(val, "1010_".as_bytes()); - } -} diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 6f70843db..acbba4c73 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -91,6 +91,7 @@ checksum = ["data-encoding", "thiserror", "sum"] encoding = ["data-encoding", "data-encoding-macro", "z85"] entries = ["libc"] extendedbigdecimal = ["bigdecimal", "num-traits"] +fast-inc = [] fs = ["dunce", "libc", "winapi-util", "windows-sys"] fsext = ["libc", "windows-sys"] fsxattr = ["xattr"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 3f0649c0c..257043e00 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -20,6 +20,8 @@ pub mod custom_tz_fmt; pub mod encoding; #[cfg(feature = "extendedbigdecimal")] pub mod extendedbigdecimal; +#[cfg(feature = "fast-inc")] +pub mod fast_inc; #[cfg(feature = "format")] pub mod format; #[cfg(feature = "fs")] diff --git a/src/uucore/src/lib/features/fast_inc.rs b/src/uucore/src/lib/features/fast_inc.rs new file mode 100644 index 000000000..5d3ae689c --- /dev/null +++ b/src/uucore/src/lib/features/fast_inc.rs @@ -0,0 +1,177 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Fast increment function, operating on ASCII strings. +/// +/// Add inc to the string val[start..end]. This operates on ASCII digits, assuming +/// val and inc are well formed. +/// +/// Returns the new value for start (can be less that the original value if we +/// have a carry or if inc > start). +/// +/// We also assume that there is enough space in val to expand if start needs +/// to be updated. +/// ``` +/// use uucore::fast_inc::fast_inc; +/// +/// // Start with a buffer containing "0", with one byte of head space +/// let mut val = Vec::from(".0".as_bytes()); +/// let mut start = val.len()-1; +/// let end = val.len(); +/// let inc = "6".as_bytes(); +/// assert_eq!(&val[start..end], "0".as_bytes()); +/// start = fast_inc(val.as_mut(), start, end, inc); +/// assert_eq!(&val[start..end], "6".as_bytes()); +/// start = fast_inc(val.as_mut(), start, end, inc); +/// assert_eq!(&val[start..end], "12".as_bytes()); +/// ``` +#[inline] +pub fn fast_inc(val: &mut [u8], start: usize, end: usize, inc: &[u8]) -> usize { + // To avoid a lot of casts to signed integers, we make sure to decrement pos + // as late as possible, so that it does not ever go negative. + let mut pos = end; + let mut carry = 0u8; + + // First loop, add all digits of inc into val. + for inc_pos in (0..inc.len()).rev() { + pos -= 1; + + let mut new_val = inc[inc_pos] + carry; + // Be careful here, only add existing digit of val. + if pos >= start { + new_val += val[pos] - b'0'; + } + if new_val > b'9' { + carry = 1; + new_val -= 10; + } else { + carry = 0; + } + val[pos] = new_val; + } + + // Done, now, if we have a carry, add that to the upper digits of val. + if carry == 0 { + return start.min(pos); + } + + fast_inc_one(val, start, pos) +} + +/// Fast increment by one function, operating on ASCII strings. +/// +/// Add 1 to the string val[start..end]. This operates on ASCII digits, assuming +/// val is well formed. +/// +/// Returns the new value for start (can be less that the original value if we +/// have a carry). +/// +/// We also assume that there is enough space in val to expand if start needs +/// to be updated. +/// ``` +/// use uucore::fast_inc::fast_inc_one; +/// +/// // Start with a buffer containing "8", with one byte of head space +/// let mut val = Vec::from(".8".as_bytes()); +/// let mut start = val.len()-1; +/// let end = val.len(); +/// assert_eq!(&val[start..end], "8".as_bytes()); +/// start = fast_inc_one(val.as_mut(), start, end); +/// assert_eq!(&val[start..end], "9".as_bytes()); +/// start = fast_inc_one(val.as_mut(), start, end); +/// assert_eq!(&val[start..end], "10".as_bytes()); +/// ``` +#[inline] +pub fn fast_inc_one(val: &mut [u8], start: usize, end: usize) -> usize { + let mut pos = end; + + while pos > start { + pos -= 1; + + if val[pos] == b'9' { + // 9+1 = 10. Carry propagating, keep going. + val[pos] = b'0'; + } else { + // Carry stopped propagating, return unchanged start. + val[pos] += 1; + return start; + } + } + + // The carry propagated so far that a new digit was added. + val[start - 1] = b'1'; + start - 1 +} + +#[cfg(test)] +mod tests { + use crate::fast_inc::fast_inc; + use crate::fast_inc::fast_inc_one; + + #[test] + fn test_fast_inc_simple() { + let mut val = Vec::from("...0_".as_bytes()); + let inc = "4".as_bytes(); + assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 3); + assert_eq!(val, "...4_".as_bytes()); + assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 3); + assert_eq!(val, "...8_".as_bytes()); + assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 2); // carried 1 more digit + assert_eq!(val, "..12_".as_bytes()); + + let mut val = Vec::from("0_".as_bytes()); + let inc = "2".as_bytes(); + assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); + assert_eq!(val, "2_".as_bytes()); + assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); + assert_eq!(val, "4_".as_bytes()); + assert_eq!(fast_inc(val.as_mut(), 0, 1, inc), 0); + assert_eq!(val, "6_".as_bytes()); + } + + // Check that we handle increment > val correctly. + #[test] + fn test_fast_inc_large_inc() { + let mut val = Vec::from("...7_".as_bytes()); + let inc = "543".as_bytes(); + assert_eq!(fast_inc(val.as_mut(), 3, 4, inc), 1); // carried 2 more digits + assert_eq!(val, ".550_".as_bytes()); + assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); // carried 1 more digit + assert_eq!(val, "1093_".as_bytes()); + } + + // Check that we handle longer carries + #[test] + fn test_fast_inc_carry() { + let mut val = Vec::from(".999_".as_bytes()); + let inc = "1".as_bytes(); + assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); + assert_eq!(val, "1000_".as_bytes()); + + let mut val = Vec::from(".999_".as_bytes()); + let inc = "11".as_bytes(); + assert_eq!(fast_inc(val.as_mut(), 1, 4, inc), 0); + assert_eq!(val, "1010_".as_bytes()); + } + + #[test] + fn test_fast_inc_one_simple() { + let mut val = Vec::from("...8_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 3, 4), 3); + assert_eq!(val, "...9_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 3, 4), 2); // carried 1 more digit + assert_eq!(val, "..10_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 2, 4), 2); + assert_eq!(val, "..11_".as_bytes()); + + let mut val = Vec::from("0_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 0, 1), 0); + assert_eq!(val, "1_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 0, 1), 0); + assert_eq!(val, "2_".as_bytes()); + assert_eq!(fast_inc_one(val.as_mut(), 0, 1), 0); + assert_eq!(val, "3_".as_bytes()); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 0762240ed..ee0fd8525 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -45,6 +45,8 @@ pub use crate::features::custom_tz_fmt; pub use crate::features::encoding; #[cfg(feature = "extendedbigdecimal")] pub use crate::features::extendedbigdecimal; +#[cfg(feature = "fast-inc")] +pub use crate::features::fast_inc; #[cfg(feature = "format")] pub use crate::features::format; #[cfg(feature = "fs")]