@floatToInt, @intToFloat gone

I don’t know what’s planned precisely for this feature but what you see now is not necessarily the full implementation. Don’t forget that you’re using a random commit from master branch.

I think most of the issues that people experience have to do with arithmetic expressions. If there is no issue in having that information permeate through the expression, then it’s going to be improved.

2 Likes

I ported my codebase of a little shootem up game I’m working on to the new @intFromFloat/@floatFromInt/etc. behavior and overall I had a very good experience and think the new style reads much better. :+1: I suppose my coding style aligns well with the new cast operators.

Would love to see your code.
More specifically, what is the percentage of @intFromFloat/@floatFromInt/@ptrCast/etc.'s you had to help with @as?

I don’t have the code on public GitHub but since it’s a hobby project, I don’t mind sharing the diff between old @intCast and new: porting to new intCast · GitHub

There isn’t much @as (except in editor.zig where I just saved with zig fmt and forgot to clean up.)

I guess my experience is different as I don’t have so many integer expressions, rather all my existing casts were pretty straightforward (e.g., copy from A to B, maybe do a cast from unsigned to signed, or from sized int to usize). Most of the “mathier” code is using f32 and @Vector(f32) so no casts there.

Esp. @ptrCasts ended up less noisy now. Also found an alignment bug as I was @alignCasting my ECS SOA arrays and some of the pointer offsets actually weren’t correctly aligned.

I see.

You know, I made a habit of spelling out the type that was previously there in old @intToFloat, or @floatToInt, or @ptrCast in a comment. Something like // @ptrCast -> [*c]f32.
Unless, of course, it’s spelled out in @as I had to add.

I just like being explicit, and not having to look up the type next time I read the line (could be in a diff, or a comment - you don’t always have zls to help you).

You could always make wrapper functions which reflect the old functionality:

pub inline fn intToFloat(comptime DestType: type, int: anytype) DestType {
    return @as(DestType, @intToFloat(int));
}
pub inline fn floatToInt(comptime DestType: type, float: anytype) DestType {
    return @as(DestType, @floatToInt(float));
}
pub inline fn ptrCast(comptime DestType: type, value: anytype) DestType {
    return @as(DestType, @ptrCast(value));
}

Then all you need to do is find replace the builtin names to remove the @ and add an import to the files.

5 Likes

Don’t be afraid of @as. It’s unambiguous and safe, and is the preferred way to convert between types, whenever possible. It’s what happens implicitly when one type is expected but another is provided.

Although the destination type parameter was removed from the language, this change is actually a new feature. The old semantics are obtained via @as, and new semantics are available that were not possible before (making the cast depend on the result type).

You’ll get used to seeing @as(@foo(...)) sometimes, and it will be fine, just ignore the short-term discomfort for now and your eyes will adjust. It happens every time the syntax of the language changes. Even comptime weirded people out at first.

5 Likes

It’s not that I’m afraid… I just don’t like how the use of @as adds yet another layer of wrapping parenthesis.
Another thing is that before you HAD to specify the target type, and when reading the code you had a clear indication of what it was. Now sometimes you do (in @as), sometimes you don’t (if inferred).

You know, the more I think about it, the more I like C type casting syntax, (<type>)<expression> - no functions to remember; clear indication of what you want; compact syntax. I’m not a fan of implicit type casting so prevalent in C, but that’s a different story.

Oh well, I guess I’ll just have to live with @as or implicit target types.

Edit: imagine something like this:

var a: f32 = 1.0;
var b = @i32(a) * 10;
// Instead of
var b = @as(i32, @intFromFloat(a)) * 10;

A lot less visual clutter, and this is just a simple expression. What if it had several type coersions?

2 Likes

I agree that simplifying the cast syntax would help reduce the clutter a lot.
I implemented this function, which needs to compute some values using mixed types, and it’s a bit hard to see the actual operation I am doing.
Maybe there’s a better way to write this function, though…

/// Blur an image by applying a box filter using an integral image.
fn applyBoxFilter(integral: [*]i32, blur: [*]u8, rows: usize, cols: usize, radius: usize) void {
    for (0..rows) |r| {
        for (0..cols) |c| {
            const pos = r * cols + c;
            const r1 = @max(@as(i32, @intCast(r)) - @as(i32, @intCast(radius)), 0);
            const c1 = @max(@as(i32, @intCast(c)) - @as(i32, @intCast(radius)), 0);
            const r2 = @min(@as(i32, @intCast(r)) + @as(i32, @intCast(radius)), @as(i32, @intCast(rows)) - 1);
            const c2 = @min(@as(i32, @intCast(c)) + @as(i32, @intCast(radius)), @as(i32, @intCast(cols)) - 1);
            const pos11: usize = @intCast(r1 * @as(i32, @intCast(cols)) + c1);
            const pos12: usize = @intCast(r1 * @as(i32, @intCast(cols)) + c2);
            const pos21: usize = @intCast(r2 * @as(i32, @intCast(cols)) + c1);
            const pos22: usize = @intCast(r2 * @as(i32, @intCast(cols)) + c2);
            const area: f32 = @floatFromInt((r2 - r1) * (c2 - c1));
            const sum: f32 = @floatFromInt(integral[pos22] - integral[pos21] - integral[pos12] + integral[pos11]);
            blur[pos] = @intFromFloat(@round(sum / area));
        }
    }
}

For the curious, this function blurs an image using a box filter, but does it really fast by using an integral image, which was computed beforehand.

1 Like

A fine example, indeed.

Especially when I have to collaborate with my past self! I mean what was that guy even thinking? In his defense though, he’s been improving.

6 Likes

Suggestion:

     for (0..rows) |r_usize| {
+        const r: i32 = @intCast(r_usize);
         for (0..cols) |c_usize| {
+            const c: i32 = @intCast(c_usize);
7 Likes

Thank you!

I think it is relevant to see what the code looks like when you have applied Andrew’s suggestions all the way through. Aesthetics are important, after all.

/// Blur an image by applying a box filter using an integral image.
fn applyBoxFilter(integral: [*]i32, blur: [*]u8, rows_usize: usize, cols_usize: usize, radius_usize: usize) void {
    const rows: i32 = @intCast(rows_usize);
    const cols: i32 = @intCast(cols_usize);
    const radius: i32 = @intCast(radius_usize);
    for (0..rows) |r_usize| {
        const r: i32 = @intCast(r_usize);
        for (0..cols) |c_usize| {
            const c: i32 = @intCast(c_usize);

            const pos = r * cols + c;
            const r1 = @max(r - radius, 0);
            const c1 = @max(c - radius, 0);
            const r2 = @min(r + radius, rows - 1);
            const c2 = @min(c + radius, cols - 1);
            const pos11: usize = @intCast(r1 * cols + c1);
            const pos12: usize = @intCast(r1 * cols + c2);
            const pos21: usize = @intCast(r2 * cols + c1);
            const pos22: usize = @intCast(r2 * cols + c2);
            const area: f32 = @floatFromInt((r2 - r1) * (c2 - c1));
            const sum: f32 = @floatFromInt(integral[pos22] - integral[pos21] - integral[pos12] + integral[pos11]);
            blur[pos] = @intFromFloat(@round(sum / area));
        }
    }
}
5 Likes

Yes, that’s what I got on my end, except for:

const pos = r * cols + c;

which should be

const pos = r_usize * cols_usize + c_usize

Since it’s being used to index an array in the last line.

2 Likes

I wanted to use this example to learn a bit more about Zig’s reflection features, so I wrote this helper function as an exercise:

/// Conveniently cast between number types (.Int and .Float)
fn asNum(comptime T: type, from: anytype) T {
    switch (@typeInfo(@TypeOf(from))) {
        .Int => {
            switch (@typeInfo(T)) {
                .Int => return @intCast(from),
                .Float => return @floatFromInt(from),
                else => @compileError("Only .Int and .Float types supported"),
            }
        },
        .Float => {
            switch (@typeInfo(T)) {
                .Float => return @floatCast(from),
                .Int => return @intFromFloat(from),
                else => @compileError("Only .Int and .Float types supported"),
            }
        },
        else => @compileError("Only .Int and .Float types supported"),
    }
}

Now, instead of doing something like:

@as(f32, @floatFromInt(val));

I can just do:

asNum(f32, val);

So, the previous code would look like this:

/// Blur an image by applying a box filter using an integral image.
fn applyBoxFilter(integral: [*]i32, blur: [*]u8, rows: usize, cols: usize, radius: usize) void {
    for (0..rows) |r| {
        for (0..cols) |c| {
            const pos = r * cols + c;
            const r1 = @max(asNum(i32, r) - asNum(i32, radius), 0);
            const c1 = @max(asNum(i32, c) - asNum(i32, radius), 0);
            const r2 = @min(asNum(i32, r) + asNum(i32, radius), asNum(i32, rows) - 1);
            const c2 = @min(asNum(i32, c) + asNum(i32, radius), asNum(i32, cols) - 1);
            const pos11: usize = @intCast(r1 * asNum(i32, cols) + c1);
            const pos12: usize = @intCast(r1 * asNum(i32, cols) + c2);
            const pos21: usize = @intCast(r2 * asNum(i32, cols) + c1);
            const pos22: usize = @intCast(r2 * asNum(i32, cols) + c2);
            const area: f32 = @floatFromInt((r2 - r1) * (c2 - c1));
            const sum: f32 = @floatFromInt(integral[pos22] - integral[pos21] - integral[pos12] + integral[pos11]);
            blur[pos] = @intFromFloat(@round(sum / area));
        }
    }
}

Not as good as Andrew’s suggestion, though.
Feedback appreciated.

1 Like

I can’t seem to wrap my head around why are the casts needed in the first place.
What’s wrong with this?

fn applyBoxFilter(integral: [*]i32, blur: [*]u8, rows: usize, cols: usize, radius: usize) void {
    for (0..rows) |r| {
        for (0..cols) |c| {
            const pos = r * cols + c;
            const r1 = r -| radius; //@max(r - radius, 0);
            const c1 = c -| radius; //@max( c - radius, 0);
            const r2 = @min(r + radius, rows - 1);
            const c2 = @min(c + radius, cols - 1);
            const pos11 = r1 * cols + c1;
            const pos12 = r1 * cols + c2;
            const pos21 = r2 * cols + c1;
            const pos22 = r2 * cols + c2;
            const area = (r2 - r1) * (c2 - c1);
            const sum = integral[pos22] - integral[pos21] - integral[pos12] + integral[pos11];
            blur[pos] = @intFromFloat(@round(@as(f32, @floatFromInt(sum)) / @as(f32, @floatFromInt(area))));
        }
    }
}

I’m wondering if the blur[pos] = casts can be skipped too…

3 Likes

Oh, that’s really nice!

I had initially written that code in C++, where the saturation operators are not available (and where almost all casts are implicit) so I just wrote it with a C++ mindset.

I really like your version, thanks for sharing :slight_smile:

2 Likes