BigInt two's complement adjustments

- rename `_twos_complement_` methods to just `_signed_`
- make `from_` variants take &[u8]
- refactor helper functions twos_complement (they take byte slice but use a generic function underneath)
- fix issues in `to_signed_` functions (only two's complement negative numbers; perform byte extension where needed)
- add tests to `to_signed_` methods
This commit is contained in:
Eduardo Pinho 2017-06-18 01:42:56 +01:00
parent 1660590125
commit 61cfdc37b3
2 changed files with 115 additions and 76 deletions

View File

@ -1,5 +1,5 @@
use std::default::Default; use std::default::Default;
use std::ops::{Add, Div, Mul, Neg, Rem, Shl, Shr, Sub, Not}; use std::ops::{Add, Div, Mul, Neg, Rem, Shl, Shr, Sub, Not, DerefMut};
use std::str::{self, FromStr}; use std::str::{self, FromStr};
use std::fmt; use std::fmt;
use std::cmp::Ordering::{self, Less, Greater, Equal}; use std::cmp::Ordering::{self, Less, Greater, Equal};
@ -930,50 +930,43 @@ impl BigInt {
/// ///
/// The digits are in little-endian base 2^8. /// The digits are in little-endian base 2^8.
#[inline] #[inline]
pub fn from_twos_complement_bytes_le(mut digits: Vec<u8>) -> BigInt { pub fn from_signed_bytes_le(digits: &[u8]) -> BigInt {
let sign = if digits.iter().all(Zero::is_zero) { let sign = match digits.last() {
Sign::NoSign Some(v) if *v > 0x7f => Sign::Minus,
} else { Some(_) => Sign::Plus,
digits.iter().rev().next() None => return BigInt::zero(),
.map(|v| if *v > 127 {
Sign::Minus
} else {
Sign::Plus
})
// we have at least one non-zero digit
.unwrap()
}; };
if sign == Sign::Minus { if sign == Sign::Minus {
// two's-complement the content to retrieve the magnitude // two's-complement the content to retrieve the magnitude
let mut digits = Vec::from(digits);
twos_complement_le(&mut digits); twos_complement_le(&mut digits);
}
BigInt::from_biguint(sign, BigUint::from_bytes_le(&*digits)) BigInt::from_biguint(sign, BigUint::from_bytes_le(&*digits))
} else {
BigInt::from_biguint(sign, BigUint::from_bytes_le(digits))
}
} }
/// Creates and initializes a `BigInt` from an array of bytes in two's complement. /// Creates and initializes a `BigInt` from an array of bytes in
/// two's complement binary representation.
/// ///
/// The digits are in big-endian base 2^8. /// The digits are in big-endian base 2^8.
#[inline] #[inline]
pub fn from_twos_complement_bytes_be(mut digits: Vec<u8>) -> BigInt { pub fn from_signed_bytes_be(digits: &[u8]) -> BigInt {
let sign = if digits.iter().all(Zero::is_zero) { let sign = match digits.first() {
Sign::NoSign Some(v) if *v > 0x7f => Sign::Minus,
} else { Some(_) => Sign::Plus,
digits.iter().next() None => return BigInt::zero(),
.map(|v| if *v > 127 {
Sign::Minus
} else {
Sign::Plus
})
// we have at least one non-zero digit
.unwrap()
}; };
if sign == Sign::Minus { if sign == Sign::Minus {
// two's-complement the content to retrieve the magnitude // two's-complement the content to retrieve the magnitude
let mut digits = Vec::from(digits);
twos_complement_be(&mut digits); twos_complement_be(&mut digits);
}
BigInt::from_biguint(sign, BigUint::from_bytes_be(&*digits)) BigInt::from_biguint(sign, BigUint::from_bytes_be(&*digits))
} else {
BigInt::from_biguint(sign, BigUint::from_bytes_be(digits))
}
} }
/// Creates and initializes a `BigInt`. /// Creates and initializes a `BigInt`.
@ -1053,12 +1046,19 @@ impl BigInt {
/// use num_bigint::ToBigInt; /// use num_bigint::ToBigInt;
/// ///
/// let i = -1125.to_bigint().unwrap(); /// let i = -1125.to_bigint().unwrap();
/// assert_eq!(i.to_twos_complement_bytes_le(), vec![155, 251]); /// assert_eq!(i.to_signed_bytes_le(), vec![155, 251]);
/// ``` /// ```
#[inline] #[inline]
pub fn to_twos_complement_bytes_le(&self) -> Vec<u8> { pub fn to_signed_bytes_le(&self) -> Vec<u8> {
let mut bytes = self.data.to_bytes_le(); let mut bytes = self.data.to_bytes_le();
let last_byte = bytes.last().map(|v| *v).unwrap_or(0);
if last_byte > 0x7f && !(last_byte == 0x80 && bytes.iter().rev().skip(1).all(Zero::is_zero)) {
// msb used by magnitude, extend by 1 byte
bytes.push(0);
}
if self.sign == Sign::Minus {
twos_complement_le(&mut bytes); twos_complement_le(&mut bytes);
}
bytes bytes
} }
@ -1085,12 +1085,19 @@ impl BigInt {
/// use num_bigint::ToBigInt; /// use num_bigint::ToBigInt;
/// ///
/// let i = -1125.to_bigint().unwrap(); /// let i = -1125.to_bigint().unwrap();
/// assert_eq!(i.to_twos_complement_bytes_be(), vec![251, 155]); /// assert_eq!(i.to_signed_bytes_be(), vec![251, 155]);
/// ``` /// ```
#[inline] #[inline]
pub fn to_twos_complement_bytes_be(&self) -> Vec<u8> { pub fn to_signed_bytes_be(&self) -> Vec<u8> {
let mut bytes = self.data.to_bytes_be(); let mut bytes = self.data.to_bytes_be();
let first_byte = bytes.first().map(|v| *v).unwrap_or(0);
if first_byte > 0x7f && !(first_byte == 0x80 && bytes.iter().skip(1).all(Zero::is_zero)) {
// msb used by magnitude, extend by 1 byte
bytes.insert(0, 0);
}
if self.sign == Sign::Minus {
twos_complement_be(&mut bytes); twos_complement_be(&mut bytes);
}
bytes bytes
} }
@ -1190,30 +1197,30 @@ impl BigInt {
} }
} }
/// Perform in-place two's complement of the given digit range, /// Perform in-place two's complement of the given binary representation,
/// in little-endian byte order. /// in little-endian byte order.
#[inline] #[inline]
fn twos_complement_le<T>(digits: &mut [T]) fn twos_complement_le(digits: &mut [u8]) {
where T: Clone + Not<Output = T> + WrappingAdd + Zero + One twos_complement(digits)
{
let mut carry = true;
for d in digits {
*d = d.clone().not();
if carry {
*d = d.wrapping_add(&T::one());
carry = d.is_zero();
}
}
} }
/// Perform in-place two's complement of the given digit range /// Perform in-place two's complement of the given binary representation
/// in big-endian byte order. /// in big-endian byte order.
#[inline] #[inline]
fn twos_complement_be<T>(digits: &mut [T]) fn twos_complement_be(digits: &mut [u8]) {
where T: Clone + Not<Output = T> + WrappingAdd + Zero + One twos_complement(digits.iter_mut().rev())
}
/// Perform in-place two's complement of the given digit iterator
/// starting from the least significant byte.
#[inline]
fn twos_complement<T, R, I>(digits: I)
where I: IntoIterator<Item = R>,
R: DerefMut<Target = T>,
T: Clone + Not<Output = T> + WrappingAdd + Zero + One
{ {
let mut carry = true; let mut carry = true;
for d in digits.iter_mut().rev() { for mut d in digits {
*d = d.clone().not(); *d = d.clone().not();
if carry { if carry {
*d = d.wrapping_add(&T::one()); *d = d.wrapping_add(&T::one());
@ -1221,4 +1228,3 @@ fn twos_complement_be<T>(digits: &mut [T])
} }
} }
} }

View File

@ -107,41 +107,74 @@ fn test_to_bytes_le() {
} }
#[test] #[test]
fn test_from_twos_complement_bytes_le() { fn test_to_signed_bytes_le() {
fn check(s: Vec<u8>, result: &str) { fn check(s: &str, result: Vec<u8>) {
assert_eq!(BigInt::from_twos_complement_bytes_le(s), assert_eq!(BigInt::parse_bytes(s.as_bytes(), 10).unwrap().to_signed_bytes_le(),
BigInt::parse_bytes(result.as_bytes(), 10).unwrap()); result);
} }
check(vec![], "0"); check("0", vec![0]);
check(vec![0], "0"); check("32767", vec![0xff, 0x7f]);
check(vec![0; 10], "0"); check("-1", vec![0xff]);
check(vec![255, 127], "32767"); check("16777216", vec![0, 0, 0, 1]);
check(vec![255], "-1"); check("-100", vec![156]);
check(vec![0, 0, 0, 1], "16777216"); check("-8388608", vec![0, 0, 0x80]);
check(vec![156], "-100"); check("-192", vec![0x40, 0xff]);
check(vec![0, 0, 128], "-8388608");
check(vec![255; 10], "-1");
} }
#[test] #[test]
fn test_from_twos_complement_bytes_be() { fn test_from_signed_bytes_le() {
fn check(s: Vec<u8>, result: &str) { fn check(s: &[u8], result: &str) {
assert_eq!(BigInt::from_twos_complement_bytes_be(s), assert_eq!(BigInt::from_signed_bytes_le(s),
BigInt::parse_bytes(result.as_bytes(), 10).unwrap()); BigInt::parse_bytes(result.as_bytes(), 10).unwrap());
} }
check(vec![], "0"); check(&[], "0");
check(vec![0], "0"); check(&[0], "0");
check(vec![0; 10], "0"); check(&[0; 10], "0");
check(vec![127, 255], "32767"); check(&[0xff, 0x7f], "32767");
check(vec![255], "-1"); check(&[0xff], "-1");
check(vec![1, 0, 0, 0], "16777216"); check(&[0, 0, 0, 1], "16777216");
check(vec![156], "-100"); check(&[156], "-100");
check(vec![128, 0, 0], "-8388608"); check(&[0, 0, 0x80], "-8388608");
check(vec![255; 10], "-1"); check(&[0xff; 10], "-1");
check(&[0x40, 0xff], "-192");
} }
#[test]
fn test_to_signed_bytes_be() {
fn check(s: &str, result: Vec<u8>) {
assert_eq!(BigInt::parse_bytes(s.as_bytes(), 10).unwrap().to_signed_bytes_be(),
result);
}
check("0", vec![0]);
check("32767", vec![0x7f, 0xff]);
check("-1", vec![255]);
check("16777216", vec![1, 0, 0, 0]);
check("-100", vec![156]);
check("-8388608", vec![128, 0, 0]);
check("-192", vec![0xff, 0x40]);
}
#[test]
fn test_from_signed_bytes_be() {
fn check(s: &[u8], result: &str) {
assert_eq!(BigInt::from_signed_bytes_be(s),
BigInt::parse_bytes(result.as_bytes(), 10).unwrap());
}
check(&[], "0");
check(&[0], "0");
check(&[0; 10], "0");
check(&[127, 255], "32767");
check(&[255], "-1");
check(&[1, 0, 0, 0], "16777216");
check(&[156], "-100");
check(&[128, 0, 0], "-8388608");
check(&[255; 10], "-1");
check(&[0xff, 0x40], "-192");
}
#[test] #[test]
fn test_cmp() { fn test_cmp() {