aboutsummaryrefslogtreecommitdiff
path: root/src/passwordmaker/base_conversion/iterative_conversion.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/passwordmaker/base_conversion/iterative_conversion.rs')
-rw-r--r--src/passwordmaker/base_conversion/iterative_conversion.rs144
1 files changed, 144 insertions, 0 deletions
diff --git a/src/passwordmaker/base_conversion/iterative_conversion.rs b/src/passwordmaker/base_conversion/iterative_conversion.rs
new file mode 100644
index 0000000..94d28c0
--- /dev/null
+++ b/src/passwordmaker/base_conversion/iterative_conversion.rs
@@ -0,0 +1,144 @@
+//! This module aims to provide iterative computation of the base-converted result, starting at the
+//! most significant digit.
+//!
+//! # Warning
+//! This is optimized for passwordmaker-rs domain specific number ranges. If you want to use this
+//! somewhere else, make sure to adapt some maths. For instance you might want to early-out for leading zeros.
+//!
+//! The maths is not great, sorry. It's way easier to start at the least significant digit...
+//! If you have any great idea how to improve it: Make a merge request!
+
+use std::convert::TryInto;
+use std::ops::{Mul, DivAssign};
+use std::iter::successors;
+
+pub(super) struct IterativeBaseConversion<V,B>{
+ current_value : V,
+ current_base_potency : V,
+ remaining_digits : usize,
+ base : B,
+}
+
+impl<V,B> IterativeBaseConversion<V,B>
+ where V: for<'a> From<&'a B>, //could be replaced by num::traits::identities::One.
+ for<'a> &'a V : Mul<&'a B, Output = Option<V>> //used to get the first current_base_potency.
+{
+ pub(super) fn new(value : V, base : B) -> Self{
+ let PotencyAndExponent{potency : current_base_potency, count : remaining_digits} = Self::find_highest_fitting_potency(&base);
+ Self{
+ current_value : value,
+ current_base_potency,
+ remaining_digits,
+ base,
+ }
+ }
+
+ fn find_highest_fitting_potency(base : &B) -> PotencyAndExponent<V> {
+ //If we also required B: Mul<V> the new() function could be made a bit faster by going base^2 -> base^4 -> base^8 -> and so on.
+ //However, for realistic inputs, we have just about 100 multiplications, so, gut feeling says: simple === faster.
+ let base_v = base.into();
+ let result = successors(Some(base_v), |potency| potency * base)
+ .enumerate()
+ .last()
+ .expect("Cannot fail, first entry is Some (required V : From<B>) and there's no filtering.");
+ PotencyAndExponent{ potency : result.1, count : result.0 + 2 }
+ }
+}
+
+impl<V,B> std::iter::Iterator for IterativeBaseConversion<V,B>
+ where V : for<'a> DivAssign<&'a B> + //used between steps to go to next-lower current_base_potency
+ RemAssignWithQuotient+ //used to get the result of each step.
+ TryInto<B> //used to convert the result of each step. We _know_ this cannot fail, but requiring Into would be wrong.
+{
+ type Item = B;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.remaining_digits == 0 {
+ None
+ } else {
+ let result = self.current_value.rem_assign_with_quotient(&self.current_base_potency);
+
+ self.current_base_potency /= &self.base;
+ self.remaining_digits = self.remaining_digits - 1;
+
+ //this cannot ever yield None.
+ result.try_into().ok()
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.remaining_digits, Some(self.remaining_digits))
+ }
+}
+
+impl<V,B> std::iter::ExactSizeIterator for IterativeBaseConversion<V,B>
+ where IterativeBaseConversion<V,B> : Iterator
+{}
+
+struct PotencyAndExponent<V>{
+ potency : V,
+ count : usize,
+}
+
+pub(super) trait RemAssignWithQuotient{
+ /// Replaces self with remainder of division, and returns quotient.
+ fn rem_assign_with_quotient(&mut self, divisor : &Self) -> Self;
+}
+
+//tests general behaviour, using primitive types.
+#[cfg(test)]
+mod iterative_conversion_tests{
+ use std::{ops::Mul, convert::{From, TryFrom}};
+
+ use super::*;
+
+ #[derive(Debug,Clone)]
+ struct MyU128(u128);
+ impl Mul<&u64> for &MyU128 {
+ type Output = Option<MyU128>;
+ fn mul(self, rhs: &u64) -> Self::Output {
+ self.0.checked_mul(*rhs as u128).map(|s| MyU128(s))
+ }
+ }
+
+ impl RemAssignWithQuotient for MyU128{
+ fn rem_assign_with_quotient(&mut self, divisor : &Self) -> Self {
+ let quotient = self.0 / divisor.0;
+ self.0 %= divisor.0;
+ Self(quotient)
+ }
+ }
+ impl From<&u64> for MyU128{
+ fn from(v: &u64) -> Self {
+ MyU128(v.clone() as u128)
+ }
+ }
+
+ impl DivAssign<&u64> for MyU128{
+ fn div_assign(&mut self, rhs: &u64) {
+ self.0 = self.0 / (*rhs as u128);
+ }
+ }
+
+ impl TryFrom<MyU128> for u64{
+ type Error = std::num::TryFromIntError;
+
+ fn try_from(value: MyU128) -> Result<Self, Self::Error> {
+ value.0.try_into()
+ }
+ }
+
+
+ #[test]
+ fn test_simple_u128_to_hex_conversion(){
+ let i = IterativeBaseConversion::new(MyU128(12345678u128), 16u64);
+ assert_eq!(i.len(), 32);
+ assert_eq!(i.skip_while(|x| *x == 0_u64).collect::<Vec<_>>(), vec![0xB, 0xC, 0x6, 0x1, 0x4, 0xE]);
+ }
+ #[test]
+ fn test_simple_u128_to_base_17_conversion(){
+ let i = IterativeBaseConversion::new(MyU128(1234567890123456789u128), 17u64);
+ assert_eq!(i.len(), 32);
+ assert_eq!(i.skip_while(|x| *x == 0_u64).collect::<Vec<_>>(), vec![7, 5, 0xA, 0x10, 0xC, 0xC, 3, 0xD, 3, 0xA, 3,8,4,8,3]);
+ }
+} \ No newline at end of file