Easier conversion of Vectors between Int and Float

After updating my code to use the result type inference, I tried to use it with Vectors and found it didn’t quite work like I had hoped, I suspect/hope that it will eventually work. Here is what I tried:

const a: @Vector(2, u32) = .{ 1, 2 };
const b: @Vector(2, f32) = @floatFromInt(a);
std.debug.print("{}\n", .{b});
test.zig:5:32: error: expected float type, found '@Vector(2, f32)'
    const b: @Vector(2, f32) = @floatFromInt(a);
                               ^~~~~~~~~~~~~~~~

With @truncate it already works Using Cast Result Type Inference · ziglang/zig Wiki · GitHub


Until @intFromFloat and @floatFromInt support using vector types directly, I came up with some utility functions that allow you to convert vectors between Int and Float (by guessing or specifying the type):

const std = @import("std");

const bEnum = std.meta.Tag(std.builtin.Type);

fn ToType(comptime To: bEnum, comptime bits: u32) type {
    return switch (To) {
        .Int => std.meta.Int(.signed, bits),
        .Float => std.meta.Float(bits),
        else => @compileError(@tagName(To) ++ " is not supported"),
    };
}

pub fn len(comptime T: type) u32 {
    switch (@typeInfo(T)) {
        .Vector => |v| return v.len,
        else => @compileError("Vector type expected"),
    }
}

fn getBits(comptime T: type, comptime From: bEnum) u32 {
    switch (@typeInfo(T)) {
        .Vector => |v| {
            switch (@typeInfo(v.child)) {
                From => |f| {
                    return f.bits;
                },
                else => @compileError(@tagName(From) ++ " type expected, but got: " ++ @typeName(v.child)),
            }
        },
        else => @compileError("Vector type expected, but got: " ++ @typeName(T)),
    }
}

fn ConvertVectorType(comptime T: type, comptime From: bEnum, comptime To: bEnum) type {
    return @Type(.{ .Vector = .{ .len = len(T), .child = ToType(To, getBits(T, From)) } });
}

fn ConvertVectorTypeBits(comptime T: type, comptime From: bEnum, comptime To: bEnum, comptime bits: u32) type {
    _ = getBits(T, From); // lazy way to trigger error if from is different
    return @Type(.{ .Vector = .{ .len = len(T), .child = ToType(To, bits) } });
}

pub fn IntVector(comptime T: type) type {
    return ConvertVectorType(T, .Float, .Int);
}
pub fn intFromFloat(vec: anytype) IntVector(@TypeOf(vec)) {
    return intConvert(@TypeOf(vec), vec, IntVector(@TypeOf(vec)));
}

pub fn IntVectorBits(comptime T: type, comptime bits: u32) type {
    return ConvertVectorTypeBits(T, .Float, .Int, bits);
}
pub fn intFromFloatBits(vec: anytype, comptime bits: u32) IntVectorBits(@TypeOf(vec), bits) {
    return intConvert(@TypeOf(vec), vec, IntVectorBits(@TypeOf(vec), bits));
}
pub fn intFromFloatTo(vec: anytype, comptime To: type) To {
    return intConvert(@TypeOf(vec), vec, To);
}

fn intConvert(comptime From: type, from: From, comptime To: type) To {
    switch (@typeInfo(From)) {
        .Vector => |v| {
            var res: To = undefined;
            inline for (0..v.len) |i| res[i] = @intFromFloat(from[i]);
            return res;
        },
        else => @compileError("Vector type expected, but got: " ++ @typeName(From)),
    }
}

pub fn FloatVector(comptime T: type) type {
    return ConvertVectorType(T, .Int, .Float);
}
pub fn floatFromInt(vec: anytype) FloatVector(@TypeOf(vec)) {
    return floatConvert(@TypeOf(vec), vec, FloatVector(@TypeOf(vec)));
}

pub fn FloatVectorBits(comptime T: type, comptime bits: u32) type {
    return ConvertVectorTypeBits(T, .Int, .Float, bits);
}
pub fn floatFromIntBits(vec: anytype, comptime bits: u32) FloatVectorBits(@TypeOf(vec), bits) {
    return floatConvert(@TypeOf(vec), vec, FloatVectorBits(@TypeOf(vec), bits));
}
pub fn floatFromIntTo(vec: anytype, comptime To: type) To {
    return floatConvert(@TypeOf(vec), vec, To);
}

fn floatConvert(comptime From: type, from: From, comptime To: type) To {
    switch (@typeInfo(From)) {
        .Vector => |v| {
            var res: To = undefined;
            inline for (0..v.len) |i| res[i] = @floatFromInt(from[i]);
            return res;
        },
        else => @compileError("Vector type expected, but got: " ++ @typeName(From)),
    }
}

const fake_import = @This(); // for demonstrating intended usage

pub fn main() void {
    // for demonstrating intended usage from another module
    const vec = fake_import;

    const a: @Vector(2, u32) = .{ 1, 2 };
    const b: @Vector(2, f32) = vec.floatFromInt(a);
    std.debug.print("{}\n", .{b});

    const c: @Vector(2, f16) = vec.floatFromIntBits(a, 16);
    std.debug.print("{}\n", .{c});

    const F2 = @Vector(2, f16);
    const d: F2 = vec.floatFromIntTo(a, F2);
    std.debug.print("{}\n", .{d});

    // this lets you avoid having to write:
    // const b: @Vector(2, f32) = .{ @floatFromInt(a[0]), @floatFromInt(a[1])};
    //
    // for many different vector type conversions
    //
    // in the future hopefully we can just write:
    // const b: @Vector(2, f32) = @floatFromInt(a);
}

I haven’t thoroughly tested it yet, but I have replaced my manual vector conversions in my code base and it seems to work. If you find bugs or have ideas how to improve this workaround, let me know!

I hope this is useful for others, that are annoyed with writing these types of conversions manually.

1 Like

Yes, that conversion is for sure intended to work. In fact, vectors was one of the motivations for removing the destination type from cast builtins.

Would you mind filing a bug report?

3 Likes

Here it is: Type Interference not working with @intFromFloat() and @floatFromInt() when used with Vectors · Issue #16267 · ziglang/zig · GitHub

2 Likes