Why is `[4]u6` 30 bits?

So I basically have this code

const std = @import("std");

pub fn main() !void {
    
    const chunk: [4]u6 = [_]u6{0b111_111, 0b111_000, 0b000_111, 0b101_010};
    const res: u24 = @bitCast(chunk);
    
    std.debug.print("{d}", .{res});
}

Which results in

error: @bitCast size mismatch: destination type 'u24' has 24 bits but source type '[4]u6' has 30 bits

(Zig Playground)

However, I do not understand why? Why is a [4]u6 30 bits and not 24 bits?

u6s will be byte-aligned, causing the array to hold 1 byte per element (you can verify that @sizeOf([4]u6) == 4).

I would have guessed that it would have been 32 bits, but I guess bitSizeOf is not counting the 2 trailing unused bits, i.e. only counting the region of bits that are used, but still accounting for padding within the array.

7 Likes

Ah I see, thanks!

If you’re looking for a way to store data like this, I think the most natural way to represent it is with a packed union. Something like this:

const Block = packed union {
    as_chunk: packed struct {
        a: u6,
        b: u6,
        c: u6,
        d: u6,
    },
    as_res: u24,

    pub fn fromArray(arr: [4]u6) Block {
        return .{ .as_chunk = .{
            .a = arr[0],
            .b = arr[1],
            .c = arr[2],
            .d = arr[3],
        } };
    }

    pub fn toArray(self: Block) [4]u6 {
        return .{
            self.as_chunk.a,
            self.as_chunk.b,
            self.as_chunk.c,
            self.as_chunk.d,
        };
    }
};

That way, you can reinterpret the data without needing casts (with block.as_x) while still letting you access each block of 4 as an array of u6, if that’s the most natural way for your program to process the data.

3 Likes