···
+
//! Golomb codec implementation for encoding and decoding integers.
+
//! Golomb coding is a lossless, variable-length encoding scheme that is optimal for geometric distributions.
+
const std = @import("std");
+
/// Golomb parameter M - determines the division point for quotient and remainder
+
/// Internal bit buffer for accumulating bits during encoding/decoding
+
/// Current bit position within the bit buffer (0-7)
+
/// Current byte position in the buffer
+
/// Encodes a value using Golomb coding into the provided buffer.
+
/// Returns the number of bits written.
+
write_padding_bits: bool = false,
+
reset_tmp_values: bool = true,
+
) error{BufferTooSmall}!usize {
+
if (self.m == 0) @panic("The Golomb parameter M must be larger than 0");
+
const q = @divFloor(value, self.m) + 1;
+
const b_q = bitLength(q) - 1;
+
const r = @rem(value, self.m);
+
const b_r = bitLength(r);
+
const needed_bits = b_q + b_q + 1 + b_m;
+
const buffer_len_bits = needed_bits + self.byte_idx * 8 + self.bit_idx;
+
if (buffer_len_bits > buffer.len * 8) return error.BufferTooSmall;
+
self.writeBit(buffer, 0);
+
self.writeBits(buffer, q, b_q + 1);
+
for (0..(b_m - b_r)) |_| {
+
self.writeBit(buffer, 0);
+
self.writeBits(buffer, r, b_r);
+
if (opts.write_padding_bits) {
+
const padding = buffer.len * 8 - buffer_len_bits;
+
self.writeBit(buffer, 0);
+
std.debug.assert(self.bit_buffer == 0);
+
if (opts.reset_tmp_values) {
+
return buffer_len_bits;
+
/// Decodes a Golomb-encoded value from the buffer.
+
/// Returns the decoded value.
+
opts: struct { reset_tmp_values: bool = true },
+
) error{InvalidFormat}!usize {
+
if (self.m == 0) @panic("The Golomb parameter M must be larger than 0");
+
while (self.readBit(buffer)) |bit| {
+
return error.InvalidFormat;
+
q <<= @as(u6, @intCast(b_q));
+
q |= self.readBits(buffer, b_q) catch return error.InvalidFormat;
+
r = self.readBits(buffer, b_m) catch return error.InvalidFormat;
+
if (opts.reset_tmp_values) {
+
/// Resets internal state variables used during encoding/decoding.
+
pub fn reset(self: *Self) void {
+
/// Calculates the number of bits needed to represent the remainder in Golomb coding.
+
fn bM(self: *const Self) u8 {
+
const b = bitLength(self.m);
+
return if (isPowerOfTwo(self.m)) b - 1 else b;
+
/// Writes a single bit to the buffer.
+
fn writeBit(self: *Self, buffer: []u8, bit: u8) void {
+
self.bit_buffer = (self.bit_buffer << 1) | (bit & 1);
+
if (self.bit_idx == 8) {
+
buffer[self.byte_idx] = self.bit_buffer;
+
/// Writes multiple bits from a value to the buffer.
+
fn writeBits(self: *Self, buffer: []u8, value: usize, count: u8) void {
+
const bit = @as(u8, @intCast((value >> @as(u6, @intCast(i))) & 1));
+
self.writeBit(buffer, bit);
+
/// Reads a single bit from the buffer. Returns null if at end of buffer.
+
fn readBit(self: *Self, buffer: []const u8) ?u8 {
+
if (self.byte_idx > buffer.len) return null;
+
const bit = (buffer[self.byte_idx] >> @as(u3, @intCast(7 - self.bit_idx))) & 1;
+
if (self.bit_idx == 8) {
+
/// Reads multiple bits from the buffer and returns them as a value.
+
fn readBits(self: *Self, buffer: []const u8, count: u8) !usize {
+
const bit = self.readBit(buffer) orelse return error.OutOfBounds;
+
result = (result << 1) | @as(usize, bit);
+
/// Calculates the number of bits required to represent a value.
+
fn bitLength(value: anytype) u8 {
+
return @bitSizeOf(@TypeOf(value)) - @clz(value);
+
/// Checks if a value is a power of two.
+
fn isPowerOfTwo(value: usize) bool {
+
return (value & (value - 1)) == 0;
+
test "encode val = 42, m = 8" {
+
const testing = std.testing;
+
var gol = Self{ .m = 8 };
+
const input: usize = 42;
+
var encoded: [1]u8 = undefined;
+
_ = try gol.encode(&encoded, input, .{});
+
try testing.expectEqualSlices(u8, &.{50}, &encoded);
+
test "decode val = {50}, m = 8" {
+
const testing = std.testing;
+
var gol = Self{ .m = 8 };
+
const input = &[_]u8{50};
+
const decoded = try gol.decode(input, .{});
+
try testing.expectEqual(42, decoded);
+
test "encode + decode val = 1564, m = 457" {
+
const testing = std.testing;
+
var gol = Self{ .m = 457 };
+
const input: usize = 1564;
+
var encoded: [2]u8 = undefined;
+
.{ .write_padding_bits = true },
+
try testing.expectEqualSlices(u8, &.{ 35, 4 }, &encoded);
+
const decoded = try gol.decode(&encoded, .{});
+
try testing.expectEqual(input, decoded);
+
test "encode multiple val = { 1564, 42 }, m = 457" {
+
const testing = std.testing;
+
var gol = Self{ .m = 457 };
+
const input = [_]usize{ 1564, 42 };
+
var encoded: [3]u8 = undefined;
+
_ = try gol.encode(&encoded, input[0], .{ .reset_tmp_values = false });
+
_ = try gol.encode(&encoded, input[1], .{});
+
try testing.expectEqualSlices(u8, &.{ 35, 6, 42 }, &encoded);
+
test "decode multiple val = { 35, 6, 8 }, m = 457" {
+
const testing = std.testing;
+
var gol = Self{ .m = 457 };
+
const input = &[_]u8{ 35, 6, 42 };
+
const decoded1 = try gol.decode(input, .{ .reset_tmp_values = false });
+
try testing.expectEqual(1564, decoded1);
+
const decoded2 = try gol.decode(input, .{});
+
try testing.expectEqual(42, decoded2);