From 6d4dfd9e50d96db529e99589b510e79a635322ab Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:33:20 +0200 Subject: [PATCH] feat: alloc feature --- CHANGELOG.md | 4 +- Cargo.toml | 17 ++++--- src/add.rs | 2 - src/algorithms/mod.rs | 4 +- src/base_convert.rs | 49 ++++++++++---------- src/bit_arr.rs | 9 +++- src/bits.rs | 2 - src/bytes.rs | 62 ++++++++++++++++++------- src/lib.rs | 2 +- src/modular.rs | 12 +++-- src/string.rs | 102 ++++++++++++++++++++++-------------------- src/utils.rs | 18 -------- 12 files changed, 153 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4795938..c67d23b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Support for `no_std` environments (#274) +- Support for `no_std` environments ([#274]) +- `alloc` feature ([#277]) [#274]: https://github.com/recmo/uint/pulls/274 +[#277]: https://github.com/recmo/uint/pulls/277 ## [1.9.0] - 2023-07-25 diff --git a/Cargo.toml b/Cargo.toml index bc509dd..976b3ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ required-features = ["std"] [dependencies] ruint-macro.workspace = true +itoa = "1.0" thiserror = { version = "1.0", optional = true } @@ -51,7 +52,7 @@ arbitrary = { version = "1", optional = true, default-features = false } ark-ff-03 = { version = "0.3.0", package = "ark-ff", optional = true, default-features = false } ark-ff-04 = { version = "0.4.0", package = "ark-ff", optional = true, default-features = false } bn-rs = { version = "0.2", optional = true, default-features = true } -fastrlp = { version = "0.3", optional = true, default-features = false } +fastrlp = { version = "0.3", optional = true, default-features = false, features = ["alloc"] } num-bigint = { version = "0.4", optional = true, default-features = false } parity-scale-codec = { version = "3", optional = true, features = [ "derive", @@ -93,8 +94,9 @@ proptest = "1.2" serde_json = "1.0" [features] -default = ["std"] +# default = ["std"] std = [ + "alloc", "alloy-rlp?/std", "ark-ff-03?/std", "ark-ff-04?/std", @@ -110,22 +112,23 @@ std = [ "valuable?/std", "zeroize?/std", ] +alloc = ["proptest?/alloc", "rand?/alloc", "serde?/alloc", "valuable?/alloc", "zeroize?/alloc"] # nightly-only features nightly = [] generic_const_exprs = ["nightly"] # support -alloy-rlp = ["dep:alloy-rlp"] +alloy-rlp = ["dep:alloy-rlp", "alloc"] arbitrary = ["dep:arbitrary", "std"] ark-ff = ["dep:ark-ff-03"] ark-ff-04 = ["dep:ark-ff-04"] bn-rs = ["dep:bn-rs", "std"] -fastrlp = ["dep:fastrlp"] -num-bigint = ["dep:num-bigint"] -parity-scale-codec = ["dep:parity-scale-codec"] +fastrlp = ["dep:fastrlp", "alloc"] +num-bigint = ["dep:num-bigint", "alloc"] +parity-scale-codec = ["dep:parity-scale-codec", "alloc"] primitive-types = ["dep:primitive-types"] -proptest = ["dep:proptest"] +proptest = ["dep:proptest", "alloc"] pyo3 = ["dep:pyo3", "std"] quickcheck = ["dep:quickcheck", "std"] rand = ["dep:rand"] diff --git a/src/add.rs b/src/add.rs index d316db1..22ba503 100644 --- a/src/add.rs +++ b/src/add.rs @@ -52,7 +52,6 @@ impl Uint { } } - #[allow(clippy::doc_markdown)] /// Calculates $\mod{\mathtt{self} + \mathtt{rhs}}_{2^{BITS}}$. /// /// Returns a tuple of the addition along with a boolean indicating whether @@ -88,7 +87,6 @@ impl Uint { Self::ZERO.overflowing_sub(self) } - #[allow(clippy::doc_markdown)] /// Calculates $\mod{\mathtt{self} - \mathtt{rhs}}_{2^{BITS}}$. /// /// Returns a tuple of the subtraction along with a boolean indicating diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs index fa47e80..a96ec9a 100644 --- a/src/algorithms/mod.rs +++ b/src/algorithms/mod.rs @@ -7,6 +7,7 @@ mod add; pub mod div; mod gcd; mod mul; +#[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free mod mul_redc; mod ops; mod shift; @@ -16,10 +17,11 @@ pub use self::{ div::div, gcd::{gcd, gcd_extended, inv_mod, LehmerMatrix}, mul::{add_nx1, addmul, addmul_n, addmul_nx1, addmul_ref, submul_nx1}, - mul_redc::mul_redc, ops::{adc, sbb}, shift::{shift_left_small, shift_right_small}, }; +#[cfg(feature = "alloc")] +pub use mul_redc::mul_redc; trait DoubleWord: Sized + Copy { fn join(high: T, low: T) -> Self; diff --git a/src/base_convert.rs b/src/base_convert.rs index 4519d6b..dd57596 100644 --- a/src/base_convert.rs +++ b/src/base_convert.rs @@ -1,5 +1,4 @@ use crate::Uint; -use alloc::vec::Vec; use core::fmt; /// Error for [`from_base_le`][Uint::from_base_le] and @@ -45,6 +44,7 @@ impl Uint { /// # Panics /// /// Panics if the base is less than 2. + #[inline] pub fn to_base_le(&self, base: u64) -> impl Iterator { assert!(base > 1); SpigotLittle { @@ -63,9 +63,23 @@ impl Uint { /// # Panics /// /// Panics if the base is less than 2. + #[inline] + #[cfg(feature = "alloc")] // OPT: Find an allocation free method. Maybe extract from the top? pub fn to_base_be(&self, base: u64) -> impl Iterator { + struct OwnedVecIterator { + vec: alloc::vec::Vec, + } + + impl Iterator for OwnedVecIterator { + type Item = u64; + + #[inline] + fn next(&mut self) -> Option { + self.vec.pop() + } + } + assert!(base > 1); - // OPT: Find an allocation free method. Maybe extract from the top? OwnedVecIterator { vec: self.to_base_le(base).collect(), } @@ -79,14 +93,13 @@ impl Uint { /// * [`BaseConvertError::InvalidDigit`] if a digit is out of range. /// * [`BaseConvertError::Overflow`] if the number is too large to /// fit. - pub fn from_base_le>( - base: u64, - digits: I, - ) -> Result { - // TODO: Do not allocate. - let mut digits: Vec<_> = digits.into_iter().collect(); - digits.reverse(); - Self::from_base_be(base, digits) + #[inline] + pub fn from_base_le(base: u64, digits: I) -> Result + where + I: IntoIterator, + I::IntoIter: DoubleEndedIterator, + { + Self::from_base_be(base, digits.into_iter().rev()) } /// Constructs the [`Uint`] from digits in the base `base` in big-endian. @@ -139,6 +152,7 @@ struct SpigotLittle { impl Iterator for SpigotLittle { type Item = u64; + #[inline] #[allow(clippy::cast_possible_truncation)] // Doesn't truncate fn next(&mut self) -> Option { // Knuth Algorithm S. @@ -147,8 +161,7 @@ impl Iterator for SpigotLittle { // OPT: If we keep track of leading zero limbs we can half iterations. for limb in self.limbs.iter_mut().rev() { zero |= *limb; - remainder <<= 64; - remainder |= u128::from(*limb); + remainder = (remainder << 64) | u128::from(*limb); *limb = (remainder / u128::from(self.base)) as u64; remainder %= u128::from(self.base); } @@ -160,18 +173,6 @@ impl Iterator for SpigotLittle { } } -struct OwnedVecIterator { - vec: Vec, -} - -impl Iterator for OwnedVecIterator { - type Item = u64; - - fn next(&mut self) -> Option { - self.vec.pop() - } -} - #[cfg(test)] #[allow(clippy::unreadable_literal)] #[allow(clippy::zero_prefixed_literal)] diff --git a/src/bit_arr.rs b/src/bit_arr.rs index 2c8e441..50dd3a4 100644 --- a/src/bit_arr.rs +++ b/src/bit_arr.rs @@ -1,5 +1,4 @@ use crate::{ParseError, Uint}; -use alloc::{borrow::Cow, vec::Vec}; use core::{ ops::{ BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index, Not, Shl, ShlAssign, @@ -8,6 +7,9 @@ use core::{ str::FromStr, }; +#[cfg(feature = "alloc")] +use alloc::{borrow::Cow, vec::Vec}; + /// A newtype wrapper around [`Uint`] that restricts operations to those /// relevant for bit arrays. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] @@ -206,11 +208,14 @@ impl Bits { forward! { fn reverse_bits(self) -> Self; } + #[cfg(feature = "alloc")] forward! { fn as_le_bytes(&self) -> Cow<'_, [u8]>; + fn to_be_bytes_vec(&self) -> Vec; + } + forward! { fn to_le_bytes(&self) -> [u8; BYTES]; fn to_be_bytes(&self) -> [u8; BYTES]; - fn to_be_bytes_vec(&self) -> Vec; fn leading_zeros(&self) -> usize; fn leading_ones(&self) -> usize; fn trailing_zeros(&self) -> usize; diff --git a/src/bits.rs b/src/bits.rs index 02e47a5..7b16731 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -240,7 +240,6 @@ impl Uint { } } - #[allow(clippy::doc_markdown)] /// Left shift by `rhs` bits with overflow detection. /// /// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$. @@ -301,7 +300,6 @@ impl Uint { (self, overflow) } - #[allow(clippy::doc_markdown)] /// Left shift by `rhs` bits. /// /// Returns $\mod{\mathtt{value} ⋅ 2^{\mathtt{rhs}}}_{2^{\mathtt{BITS}}}$. diff --git a/src/bytes.rs b/src/bytes.rs index 3d2103e..171d651 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,16 +1,15 @@ // OPT: Use u64::from_{be/le}_bytes() to work 8 bytes at a time. // FEATURE: (BLOCKED) Make `const fn`s when `const_for` is stable. -use crate::{ - utils::{trim_end_slice, trim_end_vec}, - Uint, -}; -use alloc::{borrow::Cow, vec::Vec}; +use crate::Uint; use core::{ ptr::{addr_of, addr_of_mut}, slice, }; +#[cfg(feature = "alloc")] +use alloc::{borrow::Cow, vec::Vec}; + // OPT: *_to_smallvec to avoid allocation. impl Uint { /// The size of this integer type in bytes. Note that some bits may be @@ -51,28 +50,39 @@ impl Uint { /// Access the underlying store as a little-endian bytes. /// /// Uses an optimized implementation on little-endian targets. + #[cfg(feature = "alloc")] #[must_use] - #[inline(always)] + #[inline] pub const fn as_le_bytes(&self) -> Cow<'_, [u8]> { // On little endian platforms this is a no-op. #[cfg(target_endian = "little")] return Cow::Borrowed(self.as_le_slice()); - // In others it's a bit more complicated. - #[cfg(not(target_endian = "little"))] - return Cow::Owned(self.to_le_bytes_vec()); + // In others, reverse each limb and return a copy. + #[cfg(target_endian = "big")] + return Cow::Owned({ + let mut cpy = *self; + cpy.limbs.iter_mut().for_each(|limb| limb.reverse_bits()); + slice::from_raw_parts(cpy.limbs.as_ptr().cast(), Self::BYTES).to_vec() + }); } /// Access the underlying store as a little-endian bytes with trailing zeros /// removed. /// /// Uses an optimized implementation on little-endian targets. + #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn as_le_bytes_trimmed(&self) -> Cow<'_, [u8]> { + fn lastnz(x: &[u8]) -> Option { + x.iter().rposition(|b| *b != 0) + } + match self.as_le_bytes() { - Cow::Borrowed(slice) => Cow::Borrowed(trim_end_slice(slice, &0)), + Cow::Borrowed(slice) => Cow::Borrowed(&slice[..lastnz(slice).unwrap_or(0)]), Cow::Owned(mut vec) => { - trim_end_vec(&mut vec, &0); + vec.truncate(lastnz(&vec).map_or(0, |idx| idx + 1)); Cow::Owned(vec) } } @@ -90,15 +100,17 @@ impl Uint { /// [#60551]: https://github.com/rust-lang/rust/issues/60551 #[must_use] pub fn to_le_bytes(&self) -> [u8; BYTES] { - assert_eq!(BYTES, Self::BYTES); + // TODO: Use a `const {}` block for this assertion + assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES"); + let mut bytes = [0; BYTES]; #[cfg(target_endian = "little")] bytes.copy_from_slice(self.as_le_slice()); - #[cfg(not(target_endian = "little"))] + #[cfg(target_endian = "big")] for (chunk, limb) in bytes.chunks_mut(8).zip(self.as_limbs()) { - chunk.copy_from_slice(&limb.to_le_bytes()[..chunk.len()]); + chunk.copy_from_slice(&limb.to_le_bytes()); } bytes @@ -109,14 +121,18 @@ impl Uint { /// /// This method is useful when [`Self::to_le_bytes`] can not be used because /// byte size is not known compile time. + #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn to_le_bytes_vec(&self) -> Vec { self.as_le_bytes().into_owned() } /// Converts the [`Uint`] to a little-endian byte vector with trailing zeros /// bytes removed. + #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn to_le_bytes_trimmed_vec(&self) -> Vec { self.as_le_bytes_trimmed().into_owned() } @@ -132,6 +148,7 @@ impl Uint { /// /// [#60551]: https://github.com/rust-lang/rust/issues/60551 #[must_use] + #[inline] pub fn to_be_bytes(&self) -> [u8; BYTES] { let mut bytes = self.to_le_bytes(); bytes.reverse(); @@ -143,7 +160,9 @@ impl Uint { /// /// This method is useful when [`Self::to_be_bytes`] can not be used because /// byte size is not known compile time. + #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn to_be_bytes_vec(&self) -> Vec { let mut bytes = self.to_le_bytes_vec(); bytes.reverse(); @@ -152,7 +171,9 @@ impl Uint { /// Converts the [`Uint`] to a big-endian byte vector with leading zeros /// bytes removed. + #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn to_be_bytes_trimmed_vec(&self) -> Vec { let mut bytes = self.to_le_bytes_trimmed_vec(); bytes.reverse(); @@ -162,6 +183,7 @@ impl Uint { /// Creates a new integer from a little endian stream of bytes. #[must_use] #[allow(clippy::cast_lossless)] + #[inline] fn try_from_le_byte_iter(iter: I) -> Option where I: Iterator, @@ -191,6 +213,7 @@ impl Uint { /// /// Returns [`None`] if the value is larger than fits the [`Uint`]. #[must_use] + #[inline] pub fn try_from_be_slice(bytes: &[u8]) -> Option { Self::try_from_le_byte_iter(bytes.iter().copied().rev()) } @@ -202,6 +225,7 @@ impl Uint { /// /// Returns [`None`] if the value is larger than fits the [`Uint`]. #[must_use] + #[inline] pub fn try_from_le_slice(bytes: &[u8]) -> Option { Self::try_from_le_byte_iter(bytes.iter().copied()) } @@ -220,8 +244,11 @@ impl Uint { /// Panics if the value is too large for the bit-size of the Uint. #[must_use] #[track_caller] + #[inline] pub fn from_be_bytes(bytes: [u8; BYTES]) -> Self { - assert_eq!(BYTES, Self::BYTES); + // TODO: Use a `const {}` block for this assertion + assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES"); + if BYTES % 8 == 0 { // Optimized implementation for full-limb types. let mut limbs = [0_u64; LIMBS]; @@ -248,8 +275,11 @@ impl Uint { /// Panics if the value is too large for the bit-size of the Uint. #[must_use] #[track_caller] + #[inline] pub fn from_le_bytes(bytes: [u8; BYTES]) -> Self { - assert_eq!(BYTES, Self::BYTES); + // TODO: Use a `const {}` block for this assertion + assert_eq!(BYTES, Self::BYTES, "BYTES must be equal to Self::BYTES"); + Self::try_from_le_slice(&bytes).expect("Value too large for Uint") } } diff --git a/src/lib.rs b/src/lib.rs index 7364c2e..2841aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ // See extern crate self as ruint; -// TODO: alloc feature flag +#[cfg(feature = "alloc")] #[macro_use] extern crate alloc; diff --git a/src/modular.rs b/src/modular.rs index eea5260..c44af18 100644 --- a/src/modular.rs +++ b/src/modular.rs @@ -1,4 +1,4 @@ -use crate::{algorithms, nlimbs, Uint}; +use crate::{algorithms, Uint}; // FEATURE: sub_mod, neg_mod, inv_mod, div_mod, root_mod // See @@ -8,7 +8,6 @@ use crate::{algorithms, nlimbs, Uint}; // FEATURE: Modular wrapper class, like Wrapping. impl Uint { - #[allow(clippy::doc_markdown)] // False positive /// ⚠️ Compute $\mod{\mathtt{self}}_{\mathtt{modulus}}$. /// /// **Warning.** This function is not part of the stable API. @@ -26,7 +25,6 @@ impl Uint { self } - #[allow(clippy::doc_markdown)] // False positive /// Compute $\mod{\mathtt{self} + \mathtt{rhs}}_{\mathtt{modulus}}$. /// /// Returns zero if the modulus is zero. @@ -44,7 +42,6 @@ impl Uint { result } - #[allow(clippy::doc_markdown)] // False positive /// Compute $\mod{\mathtt{self} ⋅ \mathtt{rhs}}_{\mathtt{modulus}}$. /// /// Returns zero if the modulus is zero. @@ -52,6 +49,7 @@ impl Uint { /// See [`mul_redc`](Self::mul_redc) for a faster variant at the cost of /// some pre-computation. #[must_use] + #[cfg(feature = "alloc")] // see comments below pub fn mul_mod(self, rhs: Self, mut modulus: Self) -> Self { if modulus == Self::ZERO { return Self::ZERO; @@ -62,7 +60,7 @@ impl Uint { // we could just use a `widening_mul`. So instead we allocate from heap. // Alternatively we could use `alloca`, but that is blocked on // See - let mut product = vec![0; nlimbs(2 * BITS)]; + let mut product = vec![0; crate::nlimbs(2 * BITS)]; let overflow = algorithms::addmul(&mut product, &self.limbs, &rhs.limbs); debug_assert!(!overflow); @@ -73,11 +71,11 @@ impl Uint { modulus } - #[allow(clippy::doc_markdown)] // False positive /// Compute $\mod{\mathtt{self}^{\mathtt{rhs}}}_{\mathtt{modulus}}$. /// /// Returns zero if the modulus is zero. #[must_use] + #[cfg(feature = "alloc")] // see comments in mul_mod pub fn pow_mod(mut self, mut exp: Self, modulus: Self) -> Self { if modulus == Self::ZERO || modulus <= Self::from(1) { // Also covers Self::BITS == 0 @@ -107,7 +105,6 @@ impl Uint { algorithms::inv_mod(self, modulus) } - #[allow(clippy::doc_markdown)] // False positive /// Montgomery multiplication. /// /// Computes @@ -147,6 +144,7 @@ impl Uint { /// /// Panics if `inv` is not correct. #[must_use] + #[cfg(feature = "alloc")] // TODO: Make mul_redc alloc-free pub fn mul_redc(self, other: Self, modulus: Self, inv: u64) -> Self { if BITS == 0 { return Self::ZERO; diff --git a/src/string.rs b/src/string.rs index f946782..73f858e 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,15 +1,13 @@ use crate::{base_convert::BaseConvertError, utils::rem_up, Uint}; -use core::{ - fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, - str::FromStr, -}; +use core::{fmt, str::FromStr}; // FEATURE: Respect width parameter in formatters. // TODO: Do we want to write `0` for `BITS == 0`. -impl Display for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +#[cfg(feature = "alloc")] +impl fmt::Display for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Base convert 19 digits at a time const BASE: u64 = 10_000_000_000_000_000_000_u64; let mut spigot = self.to_base_be(BASE); @@ -21,65 +19,44 @@ impl Display for Uint { } } -impl Debug for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +impl fmt::Debug for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:#x}_U{BITS}") } } -impl LowerHex for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - if f.alternate() { - write!(f, "0x")?; - } - let mut limbs = self.as_limbs().iter().rev(); - if let Some(first) = limbs.next() { - let width = 2 * rem_up(Self::BYTES, 8); - write!(f, "{first:0width$x}")?; - } - for limb in limbs { - write!(f, "{limb:016x}")?; - } - Ok(()) +impl fmt::LowerHex for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_hex::(f) } } -impl UpperHex for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - if f.alternate() { - write!(f, "0x")?; - } - let mut limbs = self.as_limbs().iter().rev(); - if let Some(first) = limbs.next() { - let width = 2 * rem_up(Self::BYTES, 8); - write!(f, "{first:0width$X}")?; - } - for limb in limbs { - write!(f, "{limb:016X}")?; - } - Ok(()) +impl fmt::UpperHex for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_hex::(f) } } -impl Binary for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +impl fmt::Binary for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if f.alternate() { write!(f, "0b")?; } - let mut limbs = self.as_limbs().iter().rev(); - if let Some(first) = limbs.next() { - let width = rem_up(Self::BITS, 64); - write!(f, "{first:0width$b}")?; + if LIMBS == 0 || *self == Self::ZERO { + return f.write_str("0"); } - for limb in limbs { - write!(f, "{limb:064b}")?; + + for (i, &limb) in self.limbs.iter().rev().enumerate() { + let width = if i == 0 { rem_up(Self::BITS, 64) } else { 64 }; + write!(f, "{limb:0width$b}")?; } Ok(()) } } -impl Octal for Uint { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +#[cfg(feature = "alloc")] +impl fmt::Octal for Uint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Base convert 21 digits at a time const BASE: u64 = 0x8000_0000_0000_0000_u64; let mut spigot = self.to_base_be(BASE); @@ -91,6 +68,31 @@ impl Octal for Uint { } } +impl Uint { + fn fmt_hex(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + write!(f, "0x")?; + } + if LIMBS == 0 || *self == Self::ZERO { + return f.write_str("0"); + } + + for (i, &limb) in self.limbs.iter().rev().enumerate() { + let width = if i == 0 { + 2 * rem_up(Self::BITS, 8) + } else { + 16 + }; + if UPPER { + write!(f, "{limb:0width$X}")?; + } else { + write!(f, "{limb:0width$x}")?; + } + } + Ok(()) + } +} + /// Error for [`from_str_radix`](Uint::from_str_radix). #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ParseError { @@ -106,6 +108,7 @@ pub enum ParseError { #[cfg(feature = "std")] impl std::error::Error for ParseError { + #[inline] fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::BaseConvertError(e) => Some(e), @@ -115,15 +118,16 @@ impl std::error::Error for ParseError { } impl From for ParseError { + #[inline] fn from(value: BaseConvertError) -> Self { Self::BaseConvertError(value) } } -impl Display for ParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BaseConvertError(e) => Display::fmt(e, f), + Self::BaseConvertError(e) => fmt::Display::fmt(e, f), Self::InvalidDigit(c) => write!(f, "Invalid digit: {c}"), Self::InvalidRadix(r) => write!(f, "Invalid radix {r}, up to 64 is supported"), } diff --git a/src/utils.rs b/src/utils.rs index a07653b..8636346 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,3 @@ -use alloc::vec::Vec; - /// Like `a % b` but returns `b` instead of `0`. #[must_use] pub(crate) const fn rem_up(a: usize, b: usize) -> usize { @@ -11,22 +9,6 @@ pub(crate) const fn rem_up(a: usize, b: usize) -> usize { } } -#[must_use] -pub(crate) fn trim_end_slice<'a, T: PartialEq>(slice: &'a [T], value: &T) -> &'a [T] { - slice - .iter() - .rposition(|b| b != value) - .map_or_else(|| &slice[..0], |len| &slice[..=len]) -} - -pub(crate) fn trim_end_vec(vec: &mut Vec, value: &T) { - if let Some(last) = vec.iter().rposition(|b| b != value) { - vec.truncate(last + 1); - } else { - vec.clear(); - } -} - // Branch prediction hints. #[cfg(feature = "nightly")] pub(crate) use core::intrinsics::{likely, unlikely};