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
2 Likes