number-utilities 0.0.1

Algorithms for *pseudorandom* values
Documentation
pub fn to_binary(value: usize) -> String {
    if value == 0 {
        return "0".to_owned();
    }
    let mut buf = String::new();
    let start = usize::BITS - value.leading_zeros();
    for i in (0..start).rev() {
        let bit_set = value & (0b1 << i);
        buf.push(if bit_set == 0 { '0' } else { '1' });
    }
    buf
}

pub fn to_hex(value: usize) -> String {
    if value == 0 {
        return "0".to_owned();
    }
    let mut buf = String::new();
    let start = (usize::BITS - value.leading_zeros()).div_ceil(4);
    for i in (0..start).rev() {
        let j = (value >> (i * 4)) % 16;
        buf.push("0123456789abcdef".chars().nth(j).unwrap());
    }
    buf
}

pub fn to_base10(value: usize) -> String {
    if value == 0 {
        return "0".to_owned();
    }
    let mut buf = String::new();
    for i in (0..=value.ilog10()).rev() {
        let j = (value / 10i32.pow(i) as usize) % 10;
        buf.push("0123456789".chars().nth(j).unwrap());
    }
    buf
}

/// upto 3999
pub fn to_roman_numeral(value: usize) -> String {
    let mut buf = String::new();
    if value == 0 {
        return String::default();
    }

    for i in (0..=value.ilog10()).rev() {
        let digit = (value / 10_usize.pow(i)) % 10;
        match i {
            3 => {
                buf.push_str(&"MMM"[..digit]);
            }
            2 => {
                let value = match digit {
                    0..4 => &"CCC"[..digit],
                    4 => "CD",
                    5..9 => &"DCCC"[..digit - 4],
                    9 => "CM",
                    value => unreachable!("{value}"),
                };
                buf.push_str(value);
            }
            1 => {
                let value = match digit {
                    0..4 => &"XXX"[..digit],
                    4 => "XL",
                    5..9 => &"LXXX"[..digit - 4],
                    9 => "XC",
                    value => unreachable!("{value}"),
                };
                buf.push_str(value);
            }
            0 => {
                let value = match digit {
                    0..4 => &"III"[..digit],
                    4 => "IV",
                    5..9 => &"VIII"[..digit - 4],
                    9 => "IX",
                    value => unreachable!("{value}"),
                };
                buf.push_str(value);
            }
            a => todo!("{a}"),
        }
    }
    buf
}

pub fn to_english(value: usize) -> String {
    fn format_part(part: usize, is_last: bool, buf: &mut String) {
        let hundred = part / 100;
        if hundred != 0 {
            if !buf.is_empty() {
                buf.push_str(", ");
            }
            buf.push_str(under_100_to_str(hundred));
            buf.push_str(" hundred");
        }
        let part = part % 100;
        if part != 0 {
            if !buf.is_empty() {
                if is_last || hundred != 0 {
                    buf.push_str(" and ");
                } else {
                    buf.push_str(", ");
                }
            }
            if part <= 20 {
                buf.push_str(under_100_to_str(part));
            } else {
                let digit = part % 10;
                let tens = part - digit;
                buf.push_str(under_100_to_str(tens));
                if digit != 0 {
                    buf.push(' ');
                    buf.push_str(under_100_to_str(digit));
                }
            }
        }
    }

    fn under_100_to_str(value: usize) -> &'static str {
        match value {
            0 => "",
            1 => "one",
            2 => "two",
            3 => "three",
            4 => "four",
            5 => "five",
            6 => "six",
            7 => "seven",
            8 => "eight",
            9 => "nine",
            10 => "ten",
            11 => "eleven",
            12 => "twelve",
            13 => "thirteen",
            14 => "fourteen",
            15 => "fifthteen",
            16 => "sixteen",
            17 => "seventeen",
            18 => "eighteen",
            19 => "nineteen",
            20 => "twenty",
            30 => "thirty",
            40 => "fourty",
            50 => "fifty",
            60 => "sixty",
            70 => "seventy",
            80 => "eighty",
            90 => "ninety",
            number => todo!("{number}"),
        }
    }

    const PART_WIDTH: u32 = 3;

    if value == 0 {
        return "zero".to_owned();
    }

    let mut buf = String::new();
    for i in (0..=value.ilog10() / PART_WIDTH).rev() {
        let size = 10_usize.pow(i * PART_WIDTH);
        let left = value / size;
        let part = left % 1000;
        let after = value - (left * size);
        let is_last = after == 0;
        match i {
            5 => {
                format_part(part, is_last, &mut buf);
                if !buf.is_empty() && part != 0 {
                    buf.push_str(" quadrillion");
                }
            }
            4 => {
                format_part(part, is_last, &mut buf);
                if !buf.is_empty() && part != 0 {
                    buf.push_str(" trillion");
                }
            }
            3 => {
                format_part(part, is_last, &mut buf);
                if !buf.is_empty() && part != 0 {
                    buf.push_str(" billion");
                }
            }
            2 => {
                format_part(part, is_last, &mut buf);
                if !buf.is_empty() && part != 0 {
                    buf.push_str(" million");
                }
            }
            1 => {
                format_part(part, is_last, &mut buf);
                if !buf.is_empty() && part != 0 {
                    buf.push_str(" thousand");
                }
            }
            0 => format_part(part, is_last, &mut buf),
            i => todo!("numbers of order {order}", order = i * 3),
        }
    }
    buf
}