Dividing an integer by another to get a floating-point result

Hi there!

I currently need to compute the result of a given integer divided by another integer, and get the result as a floating-point number.

I can guarantee the result is going be less than 2^24 + 1 (which is the maximum representable integer number without loss in IEEE 754) but the dividend and/or the dividor may exceed that value (and be up to usize::MAX each).

I need perfect integer precision. So I can't convert both integers to a f64 before dividing them.

So concretely I have to compute a / b where a: usize, b: usize and the result is in the [0, 2^24].

I then need to print this result with a precision if N decimals (up to 3). How can I do this?

Thanks in advance for your help :slight_smile: !

Do you just need to print it?

fn print_with_3_digits(a: usize, b: usize) {
    let a_mul = (a as u128) * 1000;
    let b = b as u128;
    let div = a_mul / b;
    
    let frac = div % 1000;
    let rest = div / 1000;
    
    println!("{}.{:#03}", rest, frac);
}
7 Likes

That's exactly what I needed, I just didn't think about converting a higher integer representation and also didn't know you could get the nth first digits of a number.

Thanks for your help :smiley: !

1 Like

Here is my final solution, generating a string for any set of (divident, dividor, precision) - type changed from usize to u64 in the meantime but it works with smaller number types as well.

Note that it could very probably be optimized, I just don't know how, and it works fine enough for me :laughing:

// Example: div_round_str(2, 3, 0) -> "1"
// Example: div_round_str(2, 3, 1) -> "0.7"
// Example: div_round_str(2, 3, 2) -> "0.67"
pub fn div_round_str(a: u64, b: u64, mut precision: u8) -> String {
    let max_prec = 10_u128.pow(u32::from(precision));

    let div = u128::from(a) * max_prec / u128::from(b);

    let mut int_part = div / max_prec;
    let mut frac_part = div % max_prec;

    let last_digit = (u128::from(a) * max_prec * 10 / u128::from(b)) % 10;

    if last_digit > 5 {
        if precision == 0 {
            int_part += 1;
            frac_part = 0;
        } else {
            frac_part += 1;

            if frac_part >= max_prec {
                int_part += 1;
                frac_part = 0;
            }
        }
    }

    while frac_part % 10 == 0 {
        frac_part /= 10;
        precision -= 1;
    }

    format!(
        "{int_part}{}",
        if frac_part > 0 && precision > 0 {
            format!(
                ".{:#0precision$}",
                frac_part,
                precision = usize::from(precision)
            )
        } else {
            String::new()
        }
    )
}
1 Like

Why do you do the division a second time to find the rounding decimal place? Just do it once with the higher precision an change the rest of the code to take that into account.

1 Like

How would you do that exactly? I tried but don't know how to do this given this would modify the resulting int / frac part...

EDIT: Okay I think I got it:

pub fn approx_int_div(a: u64, b: u64, precision: u8) -> String {
    let max_prec = 10_u128.pow(u32::from(precision));

    let div = u128::from(a) * max_prec * 10 / u128::from(b);
    let div = (div / 10) + if div % 10 >= 5 { 1 } else { 0 };

    let int_part = div / max_prec;
    let frac_part = div % max_prec;

    let mut out = int_part.to_string();

    if frac_part > 0 && precision > 0 {
        out.push('.');
        out.push_str(&format!(
            "{:#0precision$}",
            frac_part,
            precision = precision.into()
        ));
    }

    out
}

Here's a version with no unnecessary intermediate allocations: Playground

pub struct RationalDisplay {
    int: u128,
    frac: u128,
    prec: usize,
}

impl Display for RationalDisplay {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.int.fmt(&mut *formatter)?;

        if self.frac > 0 && self.prec > 0 {
            write!(formatter, ".{:#0precision$}", self.frac, precision = self.prec)
        } else {
            Ok(())
        }
    }
}

pub fn approx_int_div(a: u64, b: u64, precision: u8) -> RationalDisplay {
    let max_prec = 10_u128.pow(u32::from(precision));

    let quotient = u128::from(a) * max_prec * 10 / u128::from(b);
    let quotient = (quotient / 10) + (quotient % 10) / 5;

    RationalDisplay {
        int: quotient / max_prec,
        frac: quotient % max_prec,
        prec: precision.into(),
    }
}
4 Likes

Nice! In the end I wanted to get a string instead of printing it, hence the allocation. But otherwise your solution seems perfect :slight_smile:

Note that .to_string() works on anything that's impl Display, so I think @H2CO3's suggestion here is to write it via Display so that you can approx_int_div(…).to_string() in places that actually need the string, but still efficiently include it in other places -- like println! -- without being forced to have the intermediate String.

3 Likes

Also, even if you do want a String as the final result, the .push_str(&format!(…)) is still unconditionally an unnecessary allocation, as it's a temporary.

2 Likes

Indeed, didn't think of that!