Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constant space from base conversions #293

Merged
merged 7 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use self::{
add::{adc_n, cmp, sbb_n},
div::div,
gcd::{gcd, gcd_extended, inv_mod, LehmerMatrix},
mul::{add_nx1, addmul, addmul_n, addmul_nx1, addmul_ref, submul_nx1},
mul::{mul_nx1, add_nx1, addmul, addmul_n, addmul_nx1, addmul_ref, submul_nx1},
ops::{adc, sbb},
shift::{shift_left_small, shift_right_small},
};
Expand Down
13 changes: 12 additions & 1 deletion src/algorithms/mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,18 @@ fn mac(lhs: &mut u64, a: u64, b: u64, c: u64) -> u64 {
prod.high()
}

/// Computes `lhs += a * b` and returns the borrow.
/// Computes `lhs *= a` and returns the carry.
pub fn mul_nx1(lhs: &mut [u64], a: u64) -> u64 {
let mut carry = 0;
for lhs in lhs.iter_mut() {
Copy link
Collaborator

@prestwich prestwich Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if lhs is empty this will return 0. is this intended, or should it return a? my belief is that a 0-byte uint always represents the number 0, so carry on a mul is always 0.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my belief is that a 0-byte uint always represents the number 0

This is correct. See for example U0::ZERO. (This is also the reason why no Uint::ONE can exits)

let product = u128::muladd(*lhs, a, carry);
*lhs = product.low();
carry = product.high();
}
carry
}

/// Computes `lhs += a * b` and returns the carry.
///
/// Requires `lhs.len() == a.len()`.
///
Expand Down
105 changes: 54 additions & 51 deletions src/base_convert.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Uint;
use crate::{Uint, algorithms::{mul_nx1, addmul_nx1}};
use core::fmt;

/// Error for [`from_base_le`][Uint::from_base_le] and
Expand Down Expand Up @@ -85,29 +85,6 @@
}
}

/// Adds a digit in base `base` to the number. This is used internally by
/// [`Uint::from_base_le`] and [`Uint::from_base_be`].
#[inline]
fn add_digit(&mut self, digit: u64, base: u64) -> Result<(), BaseConvertError> {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));
}
// Multiply by base.
// OPT: keep track of non-zero limbs and mul the minimum.
let mut carry: u128 = u128::from(digit);
#[allow(clippy::cast_possible_truncation)]
for limb in &mut self.limbs {
carry += u128::from(*limb) * u128::from(base);
*limb = carry as u64;
carry >>= 64;
}
if carry > 0 || (LIMBS != 0 && self.limbs[LIMBS - 1] > Self::MASK) {
return Err(BaseConvertError::Overflow);
}

Ok(())
}

/// Constructs the [`Uint`] from digits in the base `base` in little-endian.
///
/// # Errors
Expand All @@ -124,36 +101,48 @@
if base < 2 {
return Err(BaseConvertError::InvalidBase(base));
}

let mut tail = digits.into_iter();
match tail.next() {
Some(digit) => Self::from_base_le_recurse(digit, base, &mut tail),
None => Ok(Self::ZERO),
if BITS == 0 {
for digit in digits {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));
}
if digit != 0 {
return Err(BaseConvertError::Overflow);
}

Check warning on line 111 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L105-L111

Added lines #L105 - L111 were not covered by tests
}
return Ok(Self::ZERO);

Check warning on line 113 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L113

Added line #L113 was not covered by tests
}
}

/// This is the recursive part of [`Uint::from_base_le`].
///
/// We drain the iterator via the recursive calls, and then perform the
/// same construction loop as [`Uint::from_base_be`] while exiting the
/// recursive callstack.
#[inline]
fn from_base_le_recurse<I: Iterator<Item = u64>>(
digit: u64,
base: u64,
tail: &mut I,
) -> Result<Self, BaseConvertError> {
if digit > base {
return Err(BaseConvertError::InvalidDigit(digit, base));
}
let mut iter = digits.into_iter();
let mut result = Self::ZERO;
let mut power = Self::from(1);
while let Some(digit) = iter.next() {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));

Check warning on line 121 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L121

Added line #L121 was not covered by tests
}

let mut acc = match tail.next() {
Some(digit) => Self::from_base_le_recurse::<I>(digit, base, tail)?,
None => Self::ZERO,
};
// Add digit to result
let overflow = addmul_nx1(&mut result.limbs, &power.limbs, digit);
if overflow != 0 || result.limbs[LIMBS - 1] > Self::MASK {
return Err(BaseConvertError::Overflow);

Check warning on line 127 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L127

Added line #L127 was not covered by tests
}

acc.add_digit(digit, base)?;
Ok(acc)
// Update power
let overflow = mul_nx1(&mut power.limbs, base);
if overflow != 0 || power.limbs[LIMBS - 1] > Self::MASK {
// Following digits must be zero
break;
}
}
while let Some(digit) = iter.next() {
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));
}
if digit != 0 {
return Err(BaseConvertError::Overflow);
}

Check warning on line 143 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L138-L143

Added lines #L138 - L143 were not covered by tests
}
Ok(result)
}

/// Constructs the [`Uint`] from digits in the base `base` in big-endian.
Expand All @@ -178,7 +167,21 @@

let mut result = Self::ZERO;
for digit in digits {
result.add_digit(digit, base)?;
if digit >= base {
return Err(BaseConvertError::InvalidDigit(digit, base));

Check warning on line 171 in src/base_convert.rs

View check run for this annotation

Codecov / codecov/patch

src/base_convert.rs#L171

Added line #L171 was not covered by tests
}
// Multiply by base.
// OPT: keep track of non-zero limbs and mul the minimum.
let mut carry: u128 = u128::from(digit);
#[allow(clippy::cast_possible_truncation)]
for limb in &mut result.limbs {
carry += u128::from(*limb) * u128::from(base);
*limb = carry as u64;
carry >>= 64;
}
if carry > 0 || (LIMBS != 0 && result.limbs[LIMBS - 1] > Self::MASK) {
return Err(BaseConvertError::Overflow);
}
}

Ok(result)
Expand Down
Loading