11

Currently I'm using the following code to return a number as a binary (base 2), octal (base 8), or hexadecimal (base 16) string.

fn convert(inp: u32, out: u32, numb: &String) -> Result<String, String> {
    match isize::from_str_radix(numb, inp) {
        Ok(a) => match out {
            2 => Ok(format!("{:b}", a)),
            8 => Ok(format!("{:o}", a)),
            16 => Ok(format!("{:x}", a)),
            10 => Ok(format!("{}", a)),
            0 | 1 => Err(format!("No base lower than 2!")),
            _ => Err(format!("printing in this base is not supported")),
        },
        Err(e) => Err(format!(
            "Could not convert {} to a number in base {}.\n{:?}\n",
            numb, inp, e
        )),
    }
}

Now I want to replace the inner match statement so I can return the number as an arbitrarily based string (e.g. base 3.) Are there any built-in functions to convert a number into any given radix, similar to JavaScript's Number.toString() method?

miken32
  • 42,008
  • 16
  • 111
  • 154
Meltinglava
  • 179
  • 1
  • 11
  • 4
    [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?](https://stackoverflow.com/q/40006219/155423). – Shepmaster May 10 '18 at 16:11

4 Answers4

14

For now, you cannot do it using the standard library, but you can:

  • use my crate radix_fmt
  • or roll your own implementation:

    fn format_radix(mut x: u32, radix: u32) -> String {
        let mut result = vec![];
    
        loop {
            let m = x % radix;
            x = x / radix;
    
            // will panic if you use a bad radix (< 2 or > 36).
            result.push(std::char::from_digit(m, radix).unwrap());
            if x == 0 {
                break;
            }
        }
        result.into_iter().rev().collect()
    }
    
    fn main() {
        assert_eq!(format_radix(1234, 10), "1234");
        assert_eq!(format_radix(1000, 10), "1000");
        assert_eq!(format_radix(0, 10), "0");
    }
    
Boiethios
  • 38,438
  • 19
  • 134
  • 183
4

If you wanted to eke out a little more performance, you can create a struct and implement Display or Debug for it. This avoids allocating a String. For maximum over-engineering, you can also have a stack-allocated array instead of the Vec.

Here is Boiethios' answer with these changes applied:

struct Radix {
    x: i32,
    radix: u32,
}

impl Radix {
    fn new(x: i32, radix: u32) -> Result<Self, &'static str> {
        if radix < 2 || radix > 36 {
            Err("Unnsupported radix")
        } else {
            Ok(Self { x, radix })
        }
    }
}

use std::fmt;

impl fmt::Display for Radix {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut x = self.x;
        // Good for binary formatting of `u128`s
        let mut result = ['\0'; 128];
        let mut used = 0;
        let negative = x < 0;
        if negative {
            x*=-1;
        }
        let mut x = x as u32;
        loop {
            let m = x % self.radix;
            x /= self.radix;

            result[used] = std::char::from_digit(m, self.radix).unwrap();
            used += 1;

            if x == 0 {
                break;
            }
        }

        if negative {
            write!(f, "-")?;
        }

        for c in result[..used].iter().rev() {
            write!(f, "{}", c)?;
        }

        Ok(())
    }
}

fn main() {
    assert_eq!(Radix::new(1234, 10).to_string(), "1234");
    assert_eq!(Radix::new(1000, 10).to_string(), "1000");
    assert_eq!(Radix::new(0, 10).to_string(), "0");
}

This could still be optimized by:

  • creating an ASCII array instead of a char array
  • not zero-initializing the array

Since these avenues require unsafe or an external crate like arraybuf, I have not included them. You can see sample code in internal implementation details of the standard library.

Meltinglava
  • 179
  • 1
  • 11
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Nice! With `let (x, m) = x.div_rem(&self.radix);` from `num_integer::Integer` you can certainly achieve a bit more speed. – Kaplan Sep 27 '22 at 16:34
  • @Kaplan doubtful; optimizers are powerful. See the [generated assembly for this example](https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=6faa5c1a6e6ca6c96eb84ec04ac01b9c) — the two implementations are the same. – Shepmaster Sep 27 '22 at 17:21
0

Here is an extended solution based on the first comment which does not bind the parameter x to be a u32:

fn format_radix(mut x: u128, radix: u32) -> String {
    let mut result = vec![];

    loop {
        let m = x % radix as u128;
        x = x / radix as u128;

        // will panic if you use a bad radix (< 2 or > 36).
        result.push(std::char::from_digit(m as u32, radix).unwrap());
        if x == 0 {
            break;
        }
    }
    result.into_iter().rev().collect()
}
flyingfishcattle
  • 1,817
  • 3
  • 14
  • 25
legendree
  • 13
  • 4
-2

This is faster than the other answer:

use std::char::from_digit;

fn encode(mut n: u32, r: u32) -> Option<String> {
   let mut s = String::new();
   loop {
      if let Some(c) = from_digit(n % r, r) {
         s.insert(0, c)
      } else {
         return None
      }
      n /= r;
      if n == 0 {
         break
      }
   }
   Some(s)
}

Note I also tried these, but they were slower:

Zombo
  • 1
  • 62
  • 391
  • 407