Convert an f64 to an i32

The integral part of the f64 is over the limit of the i32. I have the corresponding C++ code.

// compiler - g++
// version - 10.2.0
// standard - C++14
int32_t n_int = (long int) round(n_double);

Can you show me the rust code which does exactly this?

I tried using wraps. But instead of wrapping around, the variable takes a limit of i32, i.e. either 2147483647 or -2147483648 depending on the sign of the f64.

The other ways I considered were to

  1. Just keep the least significant 32 bits (I know how to do this for an i64, but not an f64, also when truncated, we need to worry about the sign and the twos complement).
  2. Subtract the limit over and over till the number gets under the limit (I don't know if I should consider MAX or MAX-MIN or something else).

I'm not sure if either method does exactly what the C code does (even in the extreme/corner cases).

Edit -
Very good solutions below. I'll just mark this for future reference.

The behavior of the C++ code is undefined if the integer part of the double cannot be represented by a long int. If you want that exact behavior, you can use https://doc.rust-lang.org/std/primitive.f64.html#method.to_int_unchecked.

3 Likes

By long int, do you mean the 64-bit one?

In Rust 1.45.0 and later, casting using the as keyword has the behavior that you desire

/// If `x` is out of range, this returns `i32::MAX` or `i32::MIN`.
fn convert(x: f64) -> i32 {
    x as i32
}

For more details, see PR #71269 and the Rust Reference.

1 Like

I need it to wrap around the i32 boundaries.

To be clear, keep subtracting MAX - MIN (+1?) until the number falls inside the boundary, i.e. less that MAX and greater than MIN.

By long int, I mean the long int type you are using in that C++ code.

Ah, sorry, I misunderstood which behavior you wanted.

Something like this should work:

fn convert(x: f64) -> i32 {
    x.round().rem_euclid(2f64.powi(32)) as u32 as i32
}
1 Like

So I experimented a bit on the C++ code using a few values for the f64/double.

I think this fits the best, I'm not completely sure though.

let a: f64 = <some_f64>;
let M: i64 = i32::MAX as i64;
let m: i64 = i32::MIN as i64;
let q: i64 = M - m + 1;
let mut b: i64 = a.round() as i64 % q;
b = if b > M { b-q } else if b < m { b+q } else { b };
let c: i32 = b as i32;
dbg!(c);

That's a handful of terms. I didn't know converting a u32 to an i32 was safe.

To imitate C++ code which has undefined behavior seems like a fool's errand. Even if you get it right today, it could change tomorrow, with a new compiler, different optimizations...

1 Like

See here: https://github.com/AssemblyScript/assemblyscript/issues/895/mcdvoice