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

Implement decimal string conversions, cleanup #40

Merged
merged 6 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fixed-bigint"
version = "0.1.10"
version = "0.1.11"
authors = ["kaidokert <kaidokert@gmail.com>"]
documentation = "https://docs.rs/fixed-bigint"
edition = "2018"
Expand Down
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
106 changes: 74 additions & 32 deletions src/fixeduint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
ret
}

/// Create a little-endian integer value from its representation as a byte array in big endian.
/// Create a big-endian integer value from its representation as a byte array in big endian.
pub fn from_be_bytes(bytes: &[u8]) -> Self {
let iter: usize = core::cmp::min(bytes.len() / Self::WORD_SIZE, N);
let total_bytes = iter * Self::WORD_SIZE;
Expand All @@ -107,7 +107,7 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
ret
}

// Converts the FixedUInt into a little-endian byte array.
/// Converts the FixedUInt into a little-endian byte array.
pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
let total_bytes = N * Self::WORD_SIZE;
if output_buffer.len() < total_bytes {
Expand All @@ -122,7 +122,7 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
Ok(&output_buffer[..total_bytes])
}

// Converts the FixedUInt into a big-endian byte array.
/// Converts the FixedUInt into a big-endian byte array.
pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
let total_bytes = N * Self::WORD_SIZE;
if output_buffer.len() < total_bytes {
Expand All @@ -137,7 +137,7 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
Ok(&output_buffer[..total_bytes])
}

/// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zero
/// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zeroes
pub fn to_hex_str<'a>(&self, result: &'a mut [u8]) -> Result<&'a str, core::fmt::Error> {
type Error = core::fmt::Error;

Expand All @@ -160,9 +160,9 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
let word = self.array[iter_words];
let mut encoded = [0u8; LONGEST_WORD_IN_BITS / 4];
let encode_slice = &mut encoded[0..word_size * 2];
let mut wordbytes = word.to_ne_bytes();
let wordslice = &mut wordbytes[0..word_size];
wordslice.reverse();
let mut wordbytes = word.to_le_bytes();
wordbytes.as_mut().reverse();
let wordslice = wordbytes.as_ref();
to_slice_hex(wordslice, encode_slice).map_err(|_| Error {})?;
for iter_chars in 0..encode_slice.len() {
let copy_char_to = (iter_words * word_size * 2) + iter_chars;
Expand Down Expand Up @@ -190,6 +190,57 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
}
}

/// Converts to decimal string, given a buffer. CAVEAT: This method removes any leading zeroes
pub fn to_radix_str<'a>(
&self,
result: &'a mut [u8],
radix: u8,
) -> Result<&'a str, core::fmt::Error> {
type Error = core::fmt::Error;

if !(2..=16).contains(&radix) {
return Err(Error {}); // Radix out of supported range
}
for byte in result.iter_mut() {
*byte = b'0';
}
if self.is_zero() {
if !result.is_empty() {
result[0] = b'0';
return core::str::from_utf8(&result[0..1]).map_err(|_| Error {});
} else {
return Err(Error {});
}
}

let mut number = *self;
let mut idx = result.len();

let radix_t = Self::from(radix);

while !number.is_zero() {
if idx == 0 {
return Err(Error {}); // not enough space in result...
}

idx -= 1;
let (quotient, remainder) = number.div_rem(&radix_t);

let digit = remainder.to_u8().unwrap();
result[idx] = match digit {
0..=9 => b'0' + digit, // digits
10..=16 => b'a' + (digit - 10), // alphabetic digits for bases > 10
_ => return Err(Error {}),
};

number = quotient;
}

let start = result[idx..].iter().position(|&c| c != b'0').unwrap_or(0);
let radix_str = core::str::from_utf8(&result[idx + start..]).map_err(|_| Error {})?;
Ok(radix_str)
}

fn hex_fmt(
&self,
formatter: &mut core::fmt::Formatter<'_>,
Expand Down Expand Up @@ -1115,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 @@ -1172,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
69 changes: 19 additions & 50 deletions src/machineword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,72 +29,41 @@ pub trait MachineWord:
type DoubleWord: num_traits::PrimInt;
fn to_double(self) -> Self::DoubleWord;
fn from_double(word: Self::DoubleWord) -> Self;

// Todo: get rid of this, single use
fn to_ne_bytes(self) -> [u8; 8];
}

impl MachineWord for u8 {
type DoubleWord = u16;
fn to_double(self) -> u16 {
self as u16
}
fn from_double(word: u16) -> u8 {
word as u8
fn to_double(self) -> Self::DoubleWord {
self as Self::DoubleWord
}
fn to_ne_bytes(self) -> [u8; 8] {
let mut ret = [0; 8];
ret[0] = self;
ret
fn from_double(word: Self::DoubleWord) -> Self {
word as Self
}
}
impl MachineWord for u16 {
type DoubleWord = u32;
fn to_double(self) -> u32 {
self as u32
fn to_double(self) -> Self::DoubleWord {
self as Self::DoubleWord
}
fn from_double(word: u32) -> u16 {
word as u16
}
fn to_ne_bytes(self) -> [u8; 8] {
let mut ret = [0; 8];
let halfslice = &mut ret[0..2];
halfslice.copy_from_slice(&self.to_ne_bytes());
ret
fn from_double(word: Self::DoubleWord) -> Self {
word as Self
}
}
impl MachineWord for u32 {
type DoubleWord = u64;
fn to_double(self) -> u64 {
self as u64
}
fn from_double(word: u64) -> u32 {
word as u32
fn to_double(self) -> Self::DoubleWord {
self as Self::DoubleWord
}
fn to_ne_bytes(self) -> [u8; 8] {
let mut ret = [0; 8];
let halfslice = &mut ret[0..4];
halfslice.copy_from_slice(&self.to_ne_bytes());
ret
fn from_double(word: Self::DoubleWord) -> Self {
word as Self
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn machineword_to_ne() {
fn compare<T: MachineWord>(input: T, reference: [u8; 8]) {
assert_eq!(input.to_ne_bytes(), reference);
}
compare(1u8, [1u8, 0, 0, 0, 0, 0, 0, 0]);
compare(2u8, [2u8, 0, 0, 0, 0, 0, 0, 0]);
compare(255u8, [255u8, 0, 0, 0, 0, 0, 0, 0]);
compare(0xa3f4u16, [0xf4, 0xa3, 0, 0, 0, 0, 0, 0]);
compare(2u16, [2u8, 0, 0, 0, 0, 0, 0, 0]);
compare(257u16, [1u8, 1, 0, 0, 0, 0, 0, 0]);
compare(2u32, [2u8, 0, 0, 0, 0, 0, 0, 0]);
compare(65537u32, [1u8, 0, 1, 0, 0, 0, 0, 0]);
impl MachineWord for u64 {
type DoubleWord = u128;
fn to_double(self) -> Self::DoubleWord {
self as Self::DoubleWord
}
fn from_double(word: Self::DoubleWord) -> Self {
word as Self
}
}
Loading
Loading