How to multiply f32 and u32?

I need to multiply float (in the range 0.0…1.0) and integer, like:

const std = @import("std");

pub fn main() void {
    const f: f32 = 0.5;
    const u: u32 = 10;
    const r: u32 = f * u; // expect r == 5
    std.debug.print("r = {}\n", .{r});
}

Compiler rejects this and error message is:

fu.zig:7:22: error: incompatible types: 'f32' and 'u32'
    const r: u32 = f * u;
                   ~~^~~
fu.zig:7:20: note: type 'f32' here
    const r: u32 = f * u;
                   ^
fu.zig:7:24: note: type 'u32' here
    const r: u32 = f * u;
                       ^

How to perform such kind of multiplication in Zig?

use @as to convert the u32 to f32 when multiplying
https://ziglang.org/documentation/master/#Type-Coercion-Coercion-Float-to-Int

    const r: u32 = f * @as(f32, u);
2 Likes

I discovered just now @intToFloat() and @floatToInt(). Are they “better/worse” than @as()?

@as() does not work with vars.

yeah, and this makes no sense to me

@as(comptime T: type, expression) T

Evidently variable is not an expression, compare

@intToFloat(comptime DestType: type, int: anytype) DestType

1 Like

a variable on the right hand size is definitely an expression. i think the difference is between comptime and runtime expressions, but i can only guess since it is not documented.

Grepping though stdlib shows that @as() is typically used with numeric literals, like @as(u128, 42)

looks like this.

Basically, the answer to my question is:

const std = @import("std");
pub fn main() void {
    var f: f32 = 0.5;
    var u: u32 = 10;
    var r: u32 = @floatToInt(u32, f * @intToFloat(f32, u));
    std.debug.print("r = {}\n", .{r}); // prints '5' as needed
}
1 Like

@as() only works for compile-time-known expressions and it’s only type coercion, for runtime conversions you need @intToFloat() or similar.

@as works with runtime-known values as well. It, however, does not change the underlying memory and only works for safe and unambiguous conversions. That’s why you can for example cast a u8 to a u16, but not vice versa.

Floats don’t have an unambiguous conversion to (and sometimes from) ints. Their memory representation of numbers is also wildly different from ints (see IEEE 754). Thus they have their own conversion functions, namely: @intToFloat and @floatToInt.

1 Like

This makes me wonder… How would you implement the inverse square root trick in zig (check the “overview of the code” section in that link)? It involves casting a float to a long and back.

It is not casting, it is “evil floating point bit level hacking”, i.e re-interpreting the memory at bit level.

const std = @import("std");
const mem = std.mem;

fn fisqrt(number: f32) f32 {
    var i: i32 = 0;
    var x2: f32 = 0.0;
    var y: f32 = 0.0;
    const threehalfs = 1.5;

    x2 = number * 0.5;
    y = number;
    const i_ptr = @ptrCast(*i32, &y);
    i = i_ptr.*;
    i = 0x5f3759df - (i >> 1);
    const y_ptr = @ptrCast(*f32, &i);
    y = y_ptr.*;
    y = y * (threehalfs - ( x2 * y * y ));
    return y;
}

pub fn main() void {
    const x: f32 = 4;
    std.debug.print("{}\n", .{fisqrt(x)});
}
$ ./fisqrt 
4.99153584e-01
1 Like

Yeah, that’s probably a better (more honest) way to put it :slight_smile: . Thanks for the code example – you should have kept the colorful comments in the original, though.

And with @bitCast you can really have some fun :laughing:

const std = @import("std");

fn fisqrt(number: f32) f32 {
    const y = @bitCast(f32, 0x5f3759df - (@bitCast(i32, number) >> 1));
    return y * (1.5 - ( number * 0.5 * y * y ));
}

pub fn main() void {
    std.debug.print("{}\n", .{fisqrt(@as(f32, 4))});
}

I know this goes against the Zig zen of readability over all else, but as I said, it’s just for fun!

4 Likes

initially i wanted to use mem.copy() but it did not fit.