MadMonkey
MadMonkey

Reputation: 617

Converting {integer} to f32 in Rust

I want to convert a value from {integer} to f32:

struct Vector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo: Vector3 = Vector3 { x: x, y: y, z: z };
            // do stuff with foo
        }
    }
}

The compiler chokes on this with a type mismatch error (expecting f32 but getting {integer}). Unfortunately I can not simply change Vector3. I'm feeding a C-API with this.

Is there any easy and concise way I can convert x, y and z from {integer} to f32?

I guess there is no builtin conversion from i32 or {integer} to f32 because it could be lossy in certain situations. However, in my case the range I'm using is so small that this wouldn't be an issue. So I would like to tell the compiler to convert the value anyways.

Interestingly, the following works:

for x in -5..5 {
    let tmp: i32 = x;
    let foo: f32 = tmp as f32;
}

I'm using a lot more that just one foo and one x so this turns hideous really fast.

Also, this works:

for x in -5i32..5i32 {
    let foo: f32 = x as f32;
    // do stuff with foo here
}

But with my usecase this turns into:

for x in -5i32..5i32 {
    for y in -5i32..5i32 {
        for z in -5i32..5i32 {
            let foo: Vector3 = Vector3 {
                x: x as f32,
                y: y as f32,
                z: z as f32,
            };
            // do stuff with foo
        }
    }
}

Which I think is pretty unreadable and an unreasonable amount of cruft for a simple conversion.

What am I missing here?

Upvotes: 10

Views: 37284

Answers (7)

Fee
Fee

Reputation: 776

Other answers don't explain why From is implemented for 8- and 16-bit integers, but not for 32- or 64-bit integers. This is because f32 has 23 precision bits, with a 'hidden' preceding 1 if the exponent is nonzero. and a sign bit. Thus, maximally sign|u24=±16777216 could be represented losslessly in an f32. wiki

f32 binary representation, with sign bit, 8 exponent bits and 23 fraction bits

For larger number types, as f32 will truncate the least signinficant bits, which doesn't make a difference if your actual numbers fit. For larger numbers, it truncates the precision, which is perfectly safe, but may be unwanted. In that case, casting to 32-bit integer to f64 is losless (up to 53 bits) and therefore provided playground.

So you could go for the last option:

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo: Vector3 = Vector3 {
                x: x as f32,
                y: y as f32,
                z: z as f32,
            };
            // do stuff with foo
        }
    }
}

which defaults to i32 indexes playground

Upvotes: 0

trent
trent

Reputation: 27955

It is not necessary to specify the i32s when using as, since this works fine (playground):

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo = Vector3 { // no need to specify the type of foo
                x: x as f32,
                y: y as f32,
                z: z as f32,
            };
            // etc.
        }
    }
}

As Klitos Kyriacou's answer observes, there is no such type as {integer}; the compiler gives that error message because it couldn't infer a concrete type for x. It doesn't actually matter, because there are no implicit conversions from integer types to floating-point types in Rust, or from integer types to other integer types, for that matter. In fact, Rust is quite short on implicit conversions of any sort (the most notable exception being Deref coercions).

Casting the type with as permits the compiler to reconcile the type mismatch, and it will eventually fill in {integer} with i32 (unconstrained integer literals always default to i32, not that the concrete type matters in this case).

Another option you may prefer, especially if you use x, y and z for other purposes in the loop, is to shadow them with f32 versions instead of creating new names:

for x in -5..5 {
    let x = x as f32;
    for y in -5..5 {
        let y = y as f32;
        for z in -5..5 {
            let z = z as f32;
            let foo = Vector3 { x, y, z };
            // etc.
        }
    }
}

(You don't have to write x: x, y: y, z: z -- Rust does the right thing when the variable name is the same as the struct member name.)

Another option (last one, I promise) is to convert the iterators instead using map:

for x in (-5..5).map(|x| x as f32) {
    for y in (-5..5).map(|y| y as f32) {
        for z in (-5..5).map(|z| z as f32) {
            let foo = Vector3 { x, y, z };
            // etc.
        }
    }
}

However it is a little more dense and may be harder to read than the previous version.

Upvotes: 14

user25064
user25064

Reputation: 2120

Another solution this time using a function and traits. playground

struct Vector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl Vector3 {
    pub fn new<T: Into<f32>>(a: T, b: T, c: T) -> Vector3 {
        Vector3 {
            x: a.into(),
            y: b.into(),
            z: c.into(),
        }
    }
}

fn main() {
    for x in -5..5i8 {
        for y in -5..5i8 {
            for z in -5..5i8 {
                let foo: Vector3 = Vector3::new(x, y, z);
                // do stuff with foo
            }
        }
    }
}

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 431069

Since everyone else is answering, I'll chime in with an iterator-flavored solution. This uses Itertools::cartesian_product instead of the for loops:

extern crate itertools;

use itertools::Itertools;

fn main() {
    fn conv(x: i32) -> f32 { x as f32 }

    let xx = (-5..5).map(conv);
    let yy = xx.clone();
    let zz = xx.clone();

    let coords = xx.cartesian_product(yy.clone().cartesian_product(zz));
    let vectors = coords.map(|(x, (y, z))| Vector3 { x, y, z });
}

Unfortunately, closures don't yet implement Clone, so I used a small function to perform the mapping. These do implement Clone.

If you wanted a helper method:

extern crate itertools;

use itertools::Itertools;
use std::ops::Range;

fn f32_range(r: Range<i32>) -> std::iter::Map<Range<i32>, fn(i32) -> f32> {
    fn c(x: i32) -> f32 { x as _ }
    r.map(c)
}

fn main() {
    let xx = f32_range(-5..5);
    let yy = f32_range(-5..5);
    let zz = f32_range(-5..5);

    let coords = xx.cartesian_product(yy.cartesian_product(zz));
    let vectors = coords.map(|(x, (y, z))| Vector3 { x, y, z });
}

Upvotes: 5

Klitos Kyriacou
Klitos Kyriacou

Reputation: 11631

The only integer types available are i8, i16, i32, etc. and their unsigned equivalents. There is no such type as {integer}. This is just a placeholder emitted by the compiler before it has determined the actual type by inference from the whole-method context.

The problem is that, at the point where you call Vector3 {x: x as f32, y: y as f32, z: z as f32}, it doesn't yet know exactly what x, y and z are, and therefore doesn't know what operations are available. It could use the operations given to determine the type, if it was a bit more intelligent; see bug report for details.

There is a conversion from i32 to f32, so you should be able to do this:

let foo = Vector3 {x: (x as i32) as f32, y: (y as i32) as f32, z: (z as i32) as f32};

Upvotes: 3

Matthieu M.
Matthieu M.

Reputation: 299960

As many problems in Computer Science, it can be solved by applying another layer of indirection.

For example, defining a constructor for Vec3:

impl Vec3 {
    fn new(x: i16, y: i16, z: i16) -> Vec3 {
        Vec3 { x: x as f32, y: y as f32, z: z as f32 }
    }
}

fn main() {
    for x in -5..5 {
        for y in -5..5 {
            for z in -5..5 {
                let foo = Vector3::new(x, y, z);
                println!("{:?}", foo);
            }
        }
    }
}

You can use a plethora of other methods (generics, builders, etc...); but a good old constructor is just the simplest.

Upvotes: 2

musicmatze
musicmatze

Reputation: 4288

From<i16> is implemented for f32.

So it should be possible to

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo: Vector3 = Vector3 {
                 x: f32::from(x),
                 y: f32::from(y),
                 z: f32::from(z),
            };
            // do stuff with foo
        }
    }
}

Of course if your iteration uses values bigger than i16 (i32 or i64) this is no longer possible in a safe way and you have to try another way.

Upvotes: 2

Related Questions