Skip to content

Commit

Permalink
improve dec string parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ekiwi committed Jan 17, 2025
1 parent dbe16a0 commit 24e48dc
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "baa"
version = "0.16.6"
version = "0.16.7"
edition = "2021"
authors = ["Kevin Laeufer <[email protected]>"]
description = "BitVector and Array Arithmetic"
Expand Down
37 changes: 37 additions & 0 deletions src/bv/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,43 @@ pub(crate) fn is_neg(src: &[Word], width: WidthInt) -> bool {
msb_bit_value == 1
}

#[inline]
pub(crate) fn is_pow2(words: &[Word]) -> Option<WidthInt> {
// find most significant bit set
let mut bit_pos = None;
for (word_ii, &word) in words.iter().enumerate() {
if bit_pos.is_none() {
if word != 0 {
// is there only one bit set?
if word.leading_zeros() + word.trailing_zeros() == Word::BITS - 1 {
bit_pos = Some(word.trailing_zeros() + word_ii as WidthInt * Word::BITS);
} else {
// more than one bit set
return None;
}
}
} else if word != 0 {
// more than one bit set
return None;
}
}
bit_pos
}

#[inline]
pub(crate) fn min_width(words: &[Word]) -> WidthInt {
// find most significant bit set
for (word_ii, &word) in words.iter().enumerate() {
if word != 0 {
// cannot underflow since word.leading_zeros() is always less than Word::BITS
let bit_pos = Word::BITS - word.leading_zeros() - 1;
return word_ii as WidthInt * Word::BITS + bit_pos + 1;
}
}
// all words are zero
0
}

#[inline]
pub(crate) fn cmp_greater_signed(a: &[Word], b: &[Word], width: WidthInt) -> bool {
let (is_neg_a, is_neg_b) = (is_neg(a, width), is_neg(b, width));
Expand Down
52 changes: 33 additions & 19 deletions src/bv/io/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,7 @@ pub(crate) fn from_str_radix(
}
[lsb, msb] => {
debug_assert!(width <= 128);
let out = match u128::from_str_radix(value, radix) {
Ok(v) => v,
Err(e) => {
let kind = match e.kind() {
std::num::IntErrorKind::NegOverflow
| std::num::IntErrorKind::PosOverflow => IntErrorKind::ExceedsWidth,
_ => IntErrorKind::InvalidDigit,
};
return Err(ParseIntError { kind });
}
};
let out = parse_u128(value, radix)?;
*lsb = out as Word;
*msb = (out >> Word::BITS) as Word;
}
Expand All @@ -292,7 +282,7 @@ pub(crate) fn from_str_radix(

match radix {
2 => parse_base_2(digits, out, width)?,
10 => parse_base_10(digits, out, width)?,
10 => parse_base_10(digits, out)?,
16 => parse_base_16(digits, out)?,
_ => todo!("Implement support for base {radix}. Currently the following bases are available: 2, 10, 16"),
};
Expand All @@ -315,6 +305,21 @@ pub(crate) fn from_str_radix(
Ok(())
}

fn parse_u128(value: &str, radix: u32) -> Result<u128, ParseIntError> {
match u128::from_str_radix(value, radix) {
Ok(v) => Ok(v),
Err(e) => {
let kind = match e.kind() {
std::num::IntErrorKind::NegOverflow | std::num::IntErrorKind::PosOverflow => {
IntErrorKind::ExceedsWidth
}
_ => IntErrorKind::InvalidDigit,
};
Err(ParseIntError { kind })
}
}
}

fn parse_base_16(digits: &[u8], out: &mut [Word]) -> Result<WidthInt, ParseIntError> {
let num_digits = digits.len();
let words = (num_digits as u32 * BITS_PER_HEX_DIGIT).div_ceil(Word::BITS);
Expand All @@ -337,13 +342,22 @@ fn parse_base_16(digits: &[u8], out: &mut [Word]) -> Result<WidthInt, ParseIntEr
Ok(num_digits as u32 * BITS_PER_HEX_DIGIT)
}

fn parse_base_10(
_digits: &[u8],
_out: &mut [Word],
_max_width: WidthInt,
) -> Result<WidthInt, ParseIntError> {
// let other = BitVecValue::
todo!()
// format!("{}", u128::MAX).len() = 39
const MAX_U128_DEC_DIGITS: usize = 39 - 1;

fn parse_base_10(digits: &[u8], out: &mut [Word]) -> Result<WidthInt, ParseIntError> {
if digits.len() <= MAX_U128_DEC_DIGITS {
let value = parse_u128(std::str::from_utf8(digits).unwrap(), 10)?;
out[0] = value as Word;
out[1] = (value >> Word::BITS) as Word;
for o in out.iter_mut().skip(2) {
*o = 0;
}
} else {
todo!()
}
// calculate the number of bits
Ok(crate::bv::arithmetic::min_width(out))
}

fn parse_base_2(
Expand Down
26 changes: 7 additions & 19 deletions src/bv/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,25 +218,13 @@ pub trait BitVecOps {
}

fn is_pow_2(&self) -> Option<WidthInt> {
// find most significant bit set
let mut bit_pos = None;
for (word_ii, &word) in self.words().iter().enumerate() {
if bit_pos.is_none() {
if word != 0 {
// is there only one bit set?
if word.leading_zeros() + word.trailing_zeros() == Word::BITS - 1 {
bit_pos = Some(word.trailing_zeros() + word_ii as WidthInt * Word::BITS);
} else {
// more than one bit set
return None;
}
}
} else if word != 0 {
// more than one bit set
return None;
}
}
bit_pos
crate::bv::arithmetic::is_pow2(self.words())
}

/// Computes the minimum number of bits that are necessary to represent the current value.
/// This corresponds to the position of the most significant `1` plus one.
fn min_width(&self) -> WidthInt {
crate::bv::arithmetic::min_width(self.words())
}

/// Computes all ranges for which the bits are one.
Expand Down
7 changes: 7 additions & 0 deletions tests/bitvec_to_from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ fn test_to_from_dec_str_regression() {
do_test_to_from_decimal_str("1000000");
}

#[test]
fn test_to_from_dec_str() {
let dec_str = "34";
let value = BitVecValue::from_str_radix(dec_str, 10, 512).unwrap();
assert_eq!(value.to_u64().unwrap(), 34);
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]

Expand Down

0 comments on commit 24e48dc

Please sign in to comment.