diff --git a/README.md b/README.md index 3a85623..d91e38e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ In addition to supporting the standard base 10 conversion, this implementation a your choice. Therefore, if you want a binary representation, set the base to 2. If you want hexadecimal, set the base to 16. +## Supports Const Contexts + +This library's API includes `const` functions that can be used to convert numbers into their string representation at compile time, allowing developers to build smaller & faster executables. + ## `&str` Example ```rust diff --git a/src/lib.rs b/src/lib.rs index 661596d..3214fc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,31 +124,37 @@ const DEC_LOOKUP: &[u8; 200] = b"0001020304050607080910111213141516171819\ 6061626364656667686970717273747576777879\ 8081828384858687888990919293949596979899"; +macro_rules! copy_2_dec_lut_bytes { + ($to:ident,$to_index:expr,$lut_index:expr) => { + $to[$to_index as usize] = DEC_LOOKUP[$lut_index as usize]; + $to[$to_index as usize+1] = DEC_LOOKUP[$lut_index as usize+1]; + }; +} + macro_rules! base_10 { ($number:ident, $index:ident, $string:ident) => { // Decode four characters at the same time while $number > 9999 { let rem = ($number % 10000) as u16; let (frst, scnd) = ((rem / 100) * 2, (rem % 100) * 2); - $string[$index-3..$index-1].copy_from_slice(&DEC_LOOKUP[frst as usize..frst as usize+2]); - $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[scnd as usize..scnd as usize+2]); + copy_2_dec_lut_bytes!($string, $index-3, frst); + copy_2_dec_lut_bytes!($string, $index-1, scnd); $index = $index.wrapping_sub(4); $number /= 10000; } - if $number > 999 { let (frst, scnd) = (($number / 100) * 2, ($number % 100) * 2); - $string[$index-3..$index-1].copy_from_slice(&DEC_LOOKUP[frst as usize..frst as usize+2]); - $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[scnd as usize..scnd as usize+2]); + copy_2_dec_lut_bytes!($string, $index-3, frst); + copy_2_dec_lut_bytes!($string, $index-1, scnd); $index = $index.wrapping_sub(4); } else if $number > 99 { let section = ($number as u16 / 10) * 2; - $string[$index-2..$index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]); + copy_2_dec_lut_bytes!($string, $index-2, section); $string[$index] = LOOKUP[($number % 10) as usize]; $index = $index.wrapping_sub(3); } else if $number > 9 { $number *= 2; - $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[$number as usize..$number as usize+2]); + copy_2_dec_lut_bytes!($string, $index-1, $number); $index = $index.wrapping_sub(2); } else { $string[$index] = LOOKUP[$number as usize]; @@ -157,59 +163,75 @@ macro_rules! base_10 { } } -macro_rules! impl_unsized_numtoa_for { - ($t:ty) => { - impl NumToA for $t { - fn numtoa(mut self, base: $t, string: &mut [u8]) -> &[u8] { - // Check if the buffer is large enough and panic on debug builds if it isn't - if cfg!(debug_assertions) { - if base == 10 { - match size_of::<$t>() { - 2 => debug_assert!(string.len() >= 5, "u16 base 10 conversions require at least 5 bytes"), - 4 => debug_assert!(string.len() >= 10, "u32 base 10 conversions require at least 10 bytes"), - 8 => debug_assert!(string.len() >= 20, "u64 base 10 conversions require at least 20 bytes"), - 16 => debug_assert!(string.len() >= 39, "u128 base 10 conversions require at least 39 bytes"), - _ => unreachable!() - } - } - } - - let mut index = string.len() - 1; - if self == 0 { - string[index] = b'0'; - return &string[index..]; - } +macro_rules! impl_unsigned_numtoa_for { + ( + $type_name:ty, + $core_function_name:ident, + $str_function_name:ident + ) => { + pub const fn $core_function_name(mut num: $type_name, base: $type_name, string: &mut [u8]) -> &[u8] { + // Check if the buffer is large enough and panic on debug builds if it isn't + if cfg!(debug_assertions) { if base == 10 { - // Convert using optimized base 10 algorithm - base_10!(self, index, string); - } else { - while self != 0 { - let rem = self % base; - string[index] = LOOKUP[rem as usize]; - index = index.wrapping_sub(1); - self /= base; + match size_of::<$type_name>() { + 2 => debug_assert!(string.len() >= 5, "u16 base 10 conversions require at least 5 bytes"), + 4 => debug_assert!(string.len() >= 10, "u32 base 10 conversions require at least 10 bytes"), + 8 => debug_assert!(string.len() >= 20, "u64 base 10 conversions require at least 20 bytes"), + 16 => debug_assert!(string.len() >= 39, "u128 base 10 conversions require at least 39 bytes"), + _ => unreachable!() } } + } - &string[index.wrapping_add(1)..] + let mut index = string.len() - 1; + if num == 0 { + string[index] = b'0'; + return string.split_at(index).1; } - - fn numtoa_str(self, base: $t, buf: &mut [u8]) -> &str { - unsafe { str::from_utf8_unchecked(self.numtoa(base, buf)) } + if base == 10 { + // Convert using optimized base 10 algorithm + base_10!(num, index, string); + } else { + while num != 0 { + let rem = num % base; + string[index] = LOOKUP[rem as usize]; + index = index.wrapping_sub(1); + num /= base; + } + } + + string.split_at(index.wrapping_add(1)).1 + } + + pub const fn $str_function_name(num: $type_name, base: $type_name, string: &mut [u8]) -> &str { + unsafe { core::str::from_utf8_unchecked($core_function_name(num, base, string)) } + } + + impl NumToA for $type_name { + fn numtoa(self, base: $type_name, string: &mut [u8]) -> &[u8] { + $core_function_name(self, base, string) + } + fn numtoa_str(self, base: $type_name, buf: &mut [u8]) -> &str { + $str_function_name(self, base, buf) } } + } } -macro_rules! impl_sized_numtoa_for { - ($t:ty) => { - impl NumToA for $t { - fn numtoa(mut self, base: $t, string: &mut [u8]) -> &[u8] { +macro_rules! impl_signed_numtoa_for { + ( + $type_name:ty, + $core_function_name:ident, + $str_function_name:ident + ) => { + impl NumToA for $type_name { + fn numtoa(mut self, base: $type_name, string: &mut [u8]) -> &[u8] { if cfg!(debug_assertions) { if base == 10 { - match size_of::<$t>() { + match size_of::<$type_name>() { 2 => debug_assert!(string.len() >= 6, "i16 base 10 conversions require at least 6 bytes"), 4 => debug_assert!(string.len() >= 11, "i32 base 10 conversions require at least 11 bytes"), 8 => debug_assert!(string.len() >= 19, "i64 base 10 conversions require at least 19 bytes"), @@ -227,15 +249,15 @@ macro_rules! impl_sized_numtoa_for { self = match self.checked_abs() { Some(value) => value, None => { - let value = <$t>::max_value(); + let value = <$type_name>::max_value(); string[index] = LOOKUP[((value % base + 1) % base) as usize]; index -= 1; - value / base + ((value % base == base - 1) as $t) + value / base + ((value % base == base - 1) as $type_name) } }; } else if self == 0 { string[index] = b'0'; - return &string[index..]; + return string.split_at(index).1; } if base == 10 { @@ -255,133 +277,149 @@ macro_rules! impl_sized_numtoa_for { index = index.wrapping_sub(1); } - &string[index.wrapping_add(1)..] + string.split_at(index.wrapping_add(1)).1 } - fn numtoa_str(self, base: $t, buf: &mut [u8]) -> &str { + fn numtoa_str(self, base: $type_name, buf: &mut [u8]) -> &str { unsafe { str::from_utf8_unchecked(self.numtoa(base, buf)) } } } } } -impl_sized_numtoa_for!(i16); -impl_sized_numtoa_for!(i32); -impl_sized_numtoa_for!(i64); -impl_sized_numtoa_for!(i128); -impl_sized_numtoa_for!(isize); -impl_unsized_numtoa_for!(u16); -impl_unsized_numtoa_for!(u32); -impl_unsized_numtoa_for!(u64); -impl_unsized_numtoa_for!(u128); -impl_unsized_numtoa_for!(usize); - -impl NumToA for i8 { - fn numtoa(mut self, base: i8, string: &mut [u8]) -> &[u8] { - if cfg!(debug_assertions) { - if base == 10 { - debug_assert!(string.len() >= 4, "i8 conversions need at least 4 bytes"); - } - } - - let mut index = string.len() - 1; - let mut is_negative = false; - - if self < 0 { - is_negative = true; - self = match self.checked_abs() { - Some(value) => value, - None => { - let value = ::max_value(); - string[index] = LOOKUP[((value % base + 1) % base) as usize]; - index -= 1; - value / base + ((value % base == base - 1) as i8) - } - }; - } else if self == 0 { - string[index] = b'0'; - return &string[index..]; +impl_signed_numtoa_for!(i16,numtoa_i16,numtoa_i16_str); +impl_signed_numtoa_for!(i32,numtoa_i32,numtoa_i32_str); +impl_signed_numtoa_for!(i64,numtoa_i64,numtoa_i64_str); +impl_signed_numtoa_for!(i128,numtoa_i128,numtoa_i128_str); +impl_signed_numtoa_for!(isize,numtoa_isize,numtoa_isize_str); +impl_unsigned_numtoa_for!(u16,numtoa_u16,numtoa_u16_str); +impl_unsigned_numtoa_for!(u32,numtoa_u32,numtoa_u32_str); +impl_unsigned_numtoa_for!(u64,numtoa_u64,numtoa_u64_str); +impl_unsigned_numtoa_for!(u128,numtoa_u128,numtoa_u128_str); +impl_unsigned_numtoa_for!(usize,numtoa_usize,numtoa_usize_str); + +pub const fn numtoa_i8(mut num: i8, base: i8, string: &mut [u8]) -> &[u8] { + if cfg!(debug_assertions) { + if base == 10 { + debug_assert!(string.len() >= 4, "i8 conversions need at least 4 bytes"); } + } - if base == 10 { - if self > 99 { - let section = (self / 10) * 2; - string[index-2..index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]); - string[index] = LOOKUP[(self % 10) as usize]; - index = index.wrapping_sub(3); - } else if self > 9 { - let idx = self as usize * 2; - string[index-1..index+1].copy_from_slice(&DEC_LOOKUP[idx..idx+2]); - index = index.wrapping_sub(2); - } else { - string[index] = LOOKUP[self as usize]; - index = index.wrapping_sub(1); + let mut index = string.len() - 1; + let mut is_negative = false; + + if num < 0 { + is_negative = true; + num = match num.checked_abs() { + Some(value) => value, + None => { + let value = ::max_value(); + string[index] = LOOKUP[((value % base + 1) % base) as usize]; + index -= 1; + value / base + ((value % base == base - 1) as i8) } - } else { - while self != 0 { - let rem = self % base; - string[index] = LOOKUP[rem as usize]; - index = index.wrapping_sub(1); - self /= base; - } - } + }; + } else if num == 0 { + string[index] = b'0'; + return string.split_at(index).1; + } - if is_negative { - string[index] = b'-'; + if base == 10 { + if num > 99 { + let section = (num / 10) * 2; + copy_2_dec_lut_bytes!(string, index-2, section); + string[index] = LOOKUP[(num % 10) as usize]; + index = index.wrapping_sub(3); + } else if num > 9 { + let idx = num as usize * 2; + copy_2_dec_lut_bytes!(string, index-1, idx); + index = index.wrapping_sub(2); + } else { + string[index] = LOOKUP[num as usize]; index = index.wrapping_sub(1); } + } else { + while num != 0 { + let rem = num % base; + string[index] = LOOKUP[rem as usize]; + index = index.wrapping_sub(1); + num /= base; + } + } + + if is_negative { + string[index] = b'-'; + index = index.wrapping_sub(1); + } - &string[index.wrapping_add(1)..] + string.split_at(index.wrapping_add(1)).1 +} + +pub const fn numtoa_i8_str(num: i8, base: i8, string: &mut [u8]) -> &str { + unsafe { str::from_utf8_unchecked(numtoa_i8(num, base, string)) } +} + +impl NumToA for i8 { + fn numtoa(self, base: i8, string: &mut [u8]) -> &[u8] { + numtoa_i8(self, base, string) } fn numtoa_str(self, base: Self, buf: &mut [u8]) -> &str { - unsafe { str::from_utf8_unchecked(self.numtoa(base, buf)) } + numtoa_i8_str(self, base, buf) } } -impl NumToA for u8 { - fn numtoa(mut self, base: u8, string: &mut [u8]) -> &[u8] { - if cfg!(debug_assertions) { - if base == 10 { - debug_assert!(string.len() >= 3, "u8 conversions need at least 3 bytes"); - } +pub const fn numtoa_u8(mut num: u8, base: u8, string: &mut [u8]) -> &[u8] { + if cfg!(debug_assertions) { + if base == 10 { + debug_assert!(string.len() >= 3, "u8 conversions need at least 3 bytes"); } + } - let mut index = string.len() - 1; - if self == 0 { - string[index] = b'0'; - return &string[index..]; - } + let mut index = string.len() - 1; + if num == 0 { + string[index] = b'0'; + return string.split_at(index).1; + } - if base == 10 { - if self > 99 { - let section = (self / 10) * 2; - string[index-2..index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]); - string[index] = LOOKUP[(self % 10) as usize]; - index = index.wrapping_sub(3); - } else if self > 9 { - self *= 2; - string[index-1..index+1].copy_from_slice(&DEC_LOOKUP[self as usize..self as usize+2]); - index = index.wrapping_sub(2); - } else { - string[index] = LOOKUP[self as usize]; - index = index.wrapping_sub(1); - } + if base == 10 { + if num > 99 { + let section = (num / 10) * 2; + copy_2_dec_lut_bytes!(string, index-2, section); + string[index] = LOOKUP[(num % 10) as usize]; + index = index.wrapping_sub(3); + } else if num > 9 { + num *= 2; + copy_2_dec_lut_bytes!(string, index-1, num); + index = index.wrapping_sub(2); } else { - while self != 0 { - let rem = self % base; - string[index] = LOOKUP[rem as usize]; - index = index.wrapping_sub(1); - self /= base; - } + string[index] = LOOKUP[num as usize]; + index = index.wrapping_sub(1); } + } else { + while num != 0 { + let rem = num % base; + string[index] = LOOKUP[rem as usize]; + index = index.wrapping_sub(1); + num /= base; + } + } - &string[index.wrapping_add(1)..] + string.split_at(1).1 +} + +pub const fn numtoa_u8_str(num: u8, base: u8, string: &mut [u8]) -> &str { + unsafe { str::from_utf8_unchecked(numtoa_u8(num, base, string)) } +} + +impl NumToA for u8 { + fn numtoa(self, base: u8, string: &mut [u8]) -> &[u8] { + numtoa_u8(self, base, string) } fn numtoa_str(self, base: Self, buf: &mut [u8]) -> &str { - unsafe { str::from_utf8_unchecked(self.numtoa(base, buf)) } + numtoa_u8_str(self, base, buf) } }