Weird thing happened when u16 multiple f32 in function call?

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

pub fn main() !void {
    var prnd = std.rand.DefaultPrng.init(0);
    const rnd = prnd.random();
    const t: u16 = 1232;
    std.debug.print("{d}\n", .{t * rnd.float(f32)}); // OK
    std.debug.print("{d}\n", .{@as(f32, t) * rnd.float(f32)}); // OK

    U16Multif32Test(t, &rnd); // NOT OK : error: expected type 'f32', found 'u16'
    AsU16Multif32Test(t, &rnd); // NOT OK : error: expected type 'f32', found 'u16'
    AsFromU16Multif32Test(t, &rnd); // OK
}

pub fn U16Multif32Test(t: u16, rnd: *const Random) void {
    std.debug.print("{d}", .{t * rnd.float(f32)});
}

pub fn AsU16Multif32Test(t: u16, rnd: *const Random) void {
    std.debug.print("{d}", .{@as(f32, t) * rnd.float(f32)});
}

pub fn AsFromU16Multif32Test(t: u16, rnd: *const Random) void {
    std.debug.print("{d}", .{@as(f32, @floatFromInt(t)) * rnd.float(f32)});
}

Thanks.

You have discovered that casting is more relaxed if the values are compile-time known.

const t: u16 = 1232;

This is compile time known, beacuse it’s a constant initialized with a literal value, which of course is known at compile time.

U16Multif32Test(t: u16, rnd: *const Random)

Here t is a runtime function parameter. As far as the compiler is concerned it could be anything.

The main reason why Zig prohibits implicit casting at runtime is because it can cause unintended behavior. E.g. consider t1 / t2 * rnd.float(f32), here it may create rounding errors if using integer division. Furthermore it’s harder to read. Was integer division used intentionally here or not?

At compile-time on the other hand, the compiler knows that the division is exact, and will throw a compiler error otherwise. Because of this the rules of implicit casting are more relaxed at comptime.

1 Like