How do I calculate a multiple factorial using num_bigint in Rust?

I am trying to calculate a factorial of a factorial in Rust using the Num-BigInt library. I've gotten to the point where I can calculate a factorial:

use num_bigint::BigUint;
use num_traits::{One, Zero, FromPrimitive};

fn factorial(n: usize) -> BigUint {
    let mut f: BigUint = One::one();
    for i in 1..(n+1) {
        let bu: BigUint = FromPrimitive::from_usize(i).unwrap();
        f = f * bu;
    }
    f
}

pub fn main() {
    println!("Starting calculation...");
    println!("{}", factorial(5));
}

I want to do a double factorial, like:

pub fn main() {
    println!("Starting calculation...");
    println!("{}", factorial(factorial(5))); 
}

However, this throws the following error because the data types are different:

error[E0308]: mismatched types
  --> src/main.rs:16:30
   |
16 |     println!("{}", factorial(factorial(5)));
   |                              ^^^^^^^^^^^^ expected `usize`, found struct `BigUint`

How can I repeat this function using BigUint instead of usize?

1 answer

  • answered 2021-05-04 06:32 frankenapps

    The problem is that you first want to use a usize and then a BigUint as your function parameter, while your parameter is set to be a usize.

    To fix this you should make your factorial function generic and then only allow those types that make sense for your particular method.

    I took your example and extended it to allow for all unsigned integer types:

    use num_bigint::BigUint;
    use num_traits::{One, FromPrimitive, Unsigned};
    use num::{Integer, NumCast, ToPrimitive};
    
    fn factorial<N: Unsigned + Integer + ToPrimitive>(n: N) -> BigUint {
        let mut f: BigUint = One::one();
        let end: usize = NumCast::from(n).unwrap();
        for i in 1..(end + 1) {
            let bu: BigUint = FromPrimitive::from_usize(i).unwrap();
            f = f * bu;
        }
        f
    }
    
    pub fn main() {
        println!("Starting calculation...");
        println!("{}", factorial(factorial(5 as u32)));
    }
    

    This will however lead to not allowing factorial(5), because 5 is treated as an i32 by default, which can be signed. If you wish to allow for signed types and instead fail at runtime you can do something like that:

    use num_bigint::BigUint;
    use num_traits::{One, FromPrimitive};
    use num::{Integer, NumCast, ToPrimitive};
    
    fn factorial<N: Integer + ToPrimitive>(n: N) -> BigUint {
        let mut f: BigUint = One::one();
        let end: usize = NumCast::from(n).expect("Number too big or negative number used.");
        for i in 1..(end + 1) {
            let bu: BigUint = FromPrimitive::from_usize(i).unwrap();
            f = f * bu;
        }
        f
    }
    
    pub fn main() {
        println!("Starting calculation...");
        println!("{}", factorial(factorial(5)));
    }