Skip to content

Commit

Permalink
Implement decimal and other radix from string conversion
Browse files Browse the repository at this point in the history
Fixes #36
  • Loading branch information
kaidokert committed Oct 6, 2024
1 parent f4528a1 commit 108942c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 78 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ In addition to basic arithmetic, two main traits are implemented: [num_traits::P
_TODO list_:
* Implement experimental `unchecked_math` operands, unchecked_mul, unchecked_div etc.
* Probably needs its own error structs instead of reusing core::fmt::Error and core::num::ParseIntError
* Decimal string to/from conversion, currently only binary and hex strings are supported.
* Comprehensive testing fixture, fully validate all ops up to 32-bit against native types
* Some test code relies on 64-bit ToPrimitive/FromPrimitive conversions, clean this up
* Lots of test code can be written cleaner
* Maybe implement signed version as well.
Expand Down
41 changes: 16 additions & 25 deletions src/fixeduint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,9 +1166,6 @@ fn make_overflow_err() -> core::num::ParseIntError {
fn make_empty_error() -> core::num::ParseIntError {
<u8>::from_str_radix("", 8).err().unwrap()
}
fn make_neg_overflow_err() -> core::num::ParseIntError {
<u8>::from_str_radix("-ff", 16).err().unwrap()
}

fn to_slice_hex<T: AsRef<[u8]>>(
input: T,
Expand Down Expand Up @@ -1223,35 +1220,29 @@ impl<T: MachineWord, const N: usize> num_traits::Num for FixedUInt<T, N> {
if input.is_empty() {
return Err(make_empty_error());
}

if !(2..=16).contains(&radix) {
return Err(make_overflow_err()); // Invalid radix
}

let mut ret = Self::zero();
let range = match input.find(|c: char| c != '0') {
Some(x) => &input[x..],
_ => input,
};
let bits_per_char = match radix {
2 => 1,
4 => 2,
16 => 4,
_ => return Err(make_neg_overflow_err()),
};
let input_chars = range.len();
let input_bits = input_chars * bits_per_char;
if input_bits > Self::BIT_SIZE {
return Err(make_overflow_err());
}
let chars_per_word = Self::WORD_BITS / bits_per_char;
let input_words = ((input_chars - 1) / chars_per_word) + 1;
for idx in 0..input_words {
let slice_end = input_chars - idx * chars_per_word;
let slice_start =
core::cmp::max(0, slice_end as isize - chars_per_word as isize) as usize;
let slice = &range[slice_start..slice_end];
let val = match T::from_str_radix(slice, radix) {
Ok(x) => x,
Err(_) => return Err(make_parse_int_err()),

for c in range.chars() {
let digit = match c.to_digit(radix) {
Some(d) => d,
None => return Err(make_parse_int_err()), // Invalid character for the radix
};
ret.array[idx] = val;

ret = num_traits::CheckedMul::checked_mul(&ret, &Self::from(radix as u8))
.ok_or(make_overflow_err())?;
ret = num_traits::CheckedAdd::checked_add(&ret, &Self::from(digit as u8))
.ok_or(make_overflow_err())?;
}

Ok(ret)
}
}
Expand Down
135 changes: 84 additions & 51 deletions tests/string_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,75 +178,108 @@ fn test_to_radix_str() {

#[test]
fn from_str_radix() {
fn test8_bit<
fn test_radices<
INT: num_traits::PrimInt<FromStrRadixErr = core::num::ParseIntError>
+ core::fmt::Debug
+ From<u8>,
+ From<u64>,
>() {
assert_eq!(INT::from_str_radix("", 3), Err(make_empty_error()));
assert_eq!(INT::from_str_radix("08", 16), Ok(8.into()));
assert_eq!(INT::from_str_radix("008", 16), Ok(8.into()));
assert_eq!(INT::from_str_radix("108", 16), Err(make_overflow_err()));

assert_eq!(INT::from_str_radix("3333", 4), Ok(255.into()));
assert_eq!(INT::from_str_radix("10001000", 2), Ok(0x88.into()));
assert_eq!(
INT::from_str_radix("110001000", 2),
Err(make_overflow_err())
);
let radices = [2, 3, 4, 7, 10, 13, 16];
let max_value: u64 = INT::max_value().to_u64().unwrap();

for &radix in &radices {
assert_eq!(INT::from_str_radix("", radix), Err(make_empty_error()));

assert_eq!(INT::from_str_radix("0", radix), Ok(0.into()));
assert_eq!(INT::from_str_radix("1", radix), Ok(1.into()));

match radix {
2 => {
assert_eq!(INT::from_str_radix("10", 2), Ok(2.into()));
assert_eq!(INT::from_str_radix("10001", 2), Ok(17.into()));
assert_eq!(INT::from_str_radix("11001", 2), Ok(25.into()));
if max_value < 255 {
assert_eq!(
INT::from_str_radix("100000000", 2),
Err(make_overflow_err())
);
}
}
3 => {
assert_eq!(INT::from_str_radix("21", 3), Ok(7.into()));
assert_eq!(INT::from_str_radix("222", 3), Ok(26.into()));
}
4 => {
assert_eq!(INT::from_str_radix("33", 4), Ok(15.into()));
assert_eq!(INT::from_str_radix("123", 4), Ok(27.into()));
}
7 => {
if max_value >= 123456 {
assert_eq!(INT::from_str_radix("1022634", 7), Ok(123456.into()));
}
}
10 => {
if max_value >= 123456 {
assert_eq!(INT::from_str_radix("12345", 10), Ok(12345.into()));
}
if max_value >= 100000 {
assert_eq!(INT::from_str_radix("100000", 10), Ok(100000.into()));
}
if max_value >= 987654321 {
assert_eq!(INT::from_str_radix("987654321", 10), Ok(987654321.into()));
}
}
13 => {
assert_eq!(INT::from_str_radix("123", 13), Ok(198.into()));
if max_value >= 1298065482 {
assert_eq!(INT::from_str_radix("178c0B5ac", 13), Ok(1298065482.into()));
}
}
16 => {
assert_eq!(INT::from_str_radix("08", 16), Ok(8.into()));
if max_value >= 0x1234cbaf {
assert_eq!(INT::from_str_radix("1234cbaf", 16), Ok(0x1234cbaf.into()));
}
if max_value >= 0xcbaf1234 {
assert_eq!(INT::from_str_radix("cbaf1234", 16), Ok(0xcbaf1234.into()));
}
}
_ => {}
}
}
}

test8_bit::<u8>();
test8_bit::<Bn<u8, 1>>();
fn test8_bit<
INT: num_traits::PrimInt<FromStrRadixErr = core::num::ParseIntError>
+ core::fmt::Debug
+ From<u64>,
>() {
test_radices::<INT>();
}

fn test16_bit<
INT: num_traits::PrimInt<FromStrRadixErr = core::num::ParseIntError>
+ core::fmt::Debug
+ From<u16>,
+ From<u64>,
>() {
assert_eq!(INT::from_str_radix("08", 16), Ok(8.into()));
assert_eq!(INT::from_str_radix("108", 16), Ok(264.into()));
assert_eq!(INT::from_str_radix("10008", 16), Err(make_overflow_err()));

assert_eq!(INT::from_str_radix("033333333", 4), Ok(65535.into()));
assert_eq!(INT::from_str_radix("ffff", 16), Ok(65535.into()));
assert_eq!(INT::from_str_radix("afaf", 16), Ok(0xafaf.into()));
assert_eq!(INT::from_str_radix("fabc", 16), Ok(0xfabc.into()));
assert_eq!(INT::from_str_radix("cbaf", 16), Ok(0xcbaf.into()));

assert_eq!(
INT::from_str_radix("1101010101010101", 2),
Ok(0xD555.into())
);
assert_eq!(
INT::from_str_radix("11101010101010101", 2),
Err(make_overflow_err())
);
test_radices::<INT>();
}

test16_bit::<u16>();
test16_bit::<Bn<u16, 1>>();
test16_bit::<Bn<u8, 2>>();

fn test32_bit<
INT: num_traits::PrimInt<FromStrRadixErr = core::num::ParseIntError>
+ core::fmt::Debug
+ From<u32>,
+ From<u64>,
>() {
assert_eq!(INT::from_str_radix("08", 16), Ok(8.into()));
assert_eq!(INT::from_str_radix("1234cbaf", 16), Ok(0x1234cbaf.into()));
assert_eq!(INT::from_str_radix("cbaf1234", 16), Ok(0xcbaf1234.into()));
assert_eq!(
INT::from_str_radix("1cbaf1234", 16),
Err(make_overflow_err())
);
assert_eq!(
INT::from_str_radix("3210012332210112", 4),
Ok(3827034390.into())
);
test_radices::<INT>();
}

test32_bit::<u32>();
// First check the test cases are correct
test32_bit::<u64>();
test8_bit::<Bn<u8, 1>>();
test8_bit::<Bn<u8, 2>>();

test16_bit::<Bn<u16, 1>>();
test16_bit::<Bn<u8, 2>>();

test32_bit::<Bn<u8, 4>>();
test32_bit::<Bn<u16, 2>>();
test32_bit::<Bn<u32, 1>>();
Expand Down

0 comments on commit 108942c

Please sign in to comment.