this repo has no description
at main 6.7 kB view raw
1//! Golomb codec implementation for encoding and decoding integers. 2//! Golomb coding is a lossless, variable-length encoding scheme that is optimal for geometric distributions. 3 4const std = @import("std"); 5 6const Self = @This(); 7 8/// Golomb parameter M - determines the division point for quotient and remainder 9m: usize, 10 11/// Internal bit buffer for accumulating bits during encoding/decoding 12bit_buffer: u8 = 0, 13/// Current bit position within the bit buffer (0-7) 14bit_idx: u8 = 0, 15/// Current byte position in the buffer 16byte_idx: usize = 0, 17 18/// Encodes a value using Golomb coding into the provided buffer. 19/// Returns the number of bits written. 20pub fn encode( 21 self: *Self, 22 buffer: []u8, 23 value: usize, 24 opts: struct { 25 write_padding_bits: bool = false, 26 reset_tmp_values: bool = true, 27 }, 28) error{BufferTooSmall}!usize { 29 if (self.m == 0) @panic("The Golomb parameter M must be larger than 0"); 30 31 const b_m = self.bM(); 32 33 const q = @divFloor(value, self.m) + 1; 34 const b_q = bitLength(q) - 1; 35 const r = @rem(value, self.m); 36 const b_r = bitLength(r); 37 38 const needed_bits = b_q + b_q + 1 + b_m; 39 const buffer_len_bits = needed_bits + self.byte_idx * 8 + self.bit_idx; 40 41 if (buffer_len_bits > buffer.len * 8) return error.BufferTooSmall; 42 43 // Write q 44 for (0..b_q) |_| { 45 self.writeBit(buffer, 0); 46 } 47 self.writeBits(buffer, q, b_q + 1); 48 49 // Write r 50 for (0..(b_m - b_r)) |_| { 51 self.writeBit(buffer, 0); 52 } 53 self.writeBits(buffer, r, b_r); 54 55 // Write padding bits 56 if (opts.write_padding_bits) { 57 const padding = buffer.len * 8 - buffer_len_bits; 58 59 for (0..padding) |_| { 60 self.writeBit(buffer, 0); 61 } 62 63 std.debug.assert(self.bit_buffer == 0); 64 } 65 66 // Reset helper vars 67 if (opts.reset_tmp_values) { 68 self.reset(); 69 } 70 71 return buffer_len_bits; 72} 73 74/// Decodes a Golomb-encoded value from the buffer. 75/// Returns the decoded value. 76pub fn decode( 77 self: *Self, 78 buffer: []const u8, 79 opts: struct { reset_tmp_values: bool = true }, 80) error{InvalidFormat}!usize { 81 if (self.m == 0) @panic("The Golomb parameter M must be larger than 0"); 82 83 const b_m = self.bM(); 84 85 var q: usize = 0; 86 var b_q: u8 = 0; 87 var r: usize = 0; 88 89 // Read b_q 90 while (self.readBit(buffer)) |bit| { 91 if (bit == 0) { 92 b_q += 1; 93 } else if (bit == 1) { 94 q = 1; 95 break; 96 } else { 97 return error.InvalidFormat; 98 } 99 } 100 101 // Read q 102 q <<= @as(u6, @intCast(b_q)); 103 q |= self.readBits(buffer, b_q) catch return error.InvalidFormat; 104 q -= 1; 105 106 // Read r 107 r = self.readBits(buffer, b_m) catch return error.InvalidFormat; 108 109 // Reset helper vars 110 if (opts.reset_tmp_values) { 111 self.reset(); 112 } 113 114 return q * self.m + r; 115} 116 117/// Resets internal state variables used during encoding/decoding. 118pub fn reset(self: *Self) void { 119 self.bit_buffer = 0; 120 self.bit_idx = 0; 121 self.byte_idx = 0; 122} 123 124/// Calculates the number of bits needed to represent the remainder in Golomb coding. 125fn bM(self: *const Self) u8 { 126 const b = bitLength(self.m); 127 return if (isPowerOfTwo(self.m)) b - 1 else b; 128} 129 130/// Writes a single bit to the buffer. 131fn writeBit(self: *Self, buffer: []u8, bit: u8) void { 132 self.bit_buffer = (self.bit_buffer << 1) | (bit & 1); 133 self.bit_idx += 1; 134 135 if (self.bit_idx == 8) { 136 buffer[self.byte_idx] = self.bit_buffer; 137 self.byte_idx += 1; 138 self.bit_buffer = 0; 139 self.bit_idx = 0; 140 } 141} 142 143/// Writes multiple bits from a value to the buffer. 144fn writeBits(self: *Self, buffer: []u8, value: usize, count: u8) void { 145 var i = count; 146 while (i > 0) { 147 i -= 1; 148 const bit = @as(u8, @intCast((value >> @as(u6, @intCast(i))) & 1)); 149 self.writeBit(buffer, bit); 150 } 151} 152 153/// Reads a single bit from the buffer. Returns null if at end of buffer. 154fn readBit(self: *Self, buffer: []const u8) ?u8 { 155 if (self.byte_idx > buffer.len) return null; 156 157 const bit = (buffer[self.byte_idx] >> @as(u3, @intCast(7 - self.bit_idx))) & 1; 158 self.bit_idx += 1; 159 160 if (self.bit_idx == 8) { 161 self.byte_idx += 1; 162 self.bit_idx = 0; 163 } 164 165 return bit; 166} 167 168/// Reads multiple bits from the buffer and returns them as a value. 169fn readBits(self: *Self, buffer: []const u8, count: u8) !usize { 170 var result: usize = 0; 171 172 for (0..count) |_| { 173 const bit = self.readBit(buffer) orelse return error.OutOfBounds; 174 result = (result << 1) | @as(usize, bit); 175 } 176 177 return result; 178} 179 180/// Calculates the number of bits required to represent a value. 181fn bitLength(value: anytype) u8 { 182 return @bitSizeOf(@TypeOf(value)) - @clz(value); 183} 184 185/// Checks if a value is a power of two. 186fn isPowerOfTwo(value: usize) bool { 187 return (value & (value - 1)) == 0; 188} 189 190test "golomb.encode val = 42, m = 8" { 191 const testing = std.testing; 192 193 var gol = Self{ .m = 8 }; 194 const input: usize = 42; 195 var encoded: [1]u8 = undefined; 196 _ = try gol.encode(&encoded, input, .{}); 197 try testing.expectEqualSlices(u8, &.{50}, &encoded); 198} 199 200test "golomb.decode val = {50}, m = 8" { 201 const testing = std.testing; 202 203 var gol = Self{ .m = 8 }; 204 const input = &[_]u8{50}; 205 const decoded = try gol.decode(input, .{}); 206 207 try testing.expectEqual(42, decoded); 208} 209 210test "golomb.encode + decode val = 1564, m = 457" { 211 const testing = std.testing; 212 213 var gol = Self{ .m = 457 }; 214 const input: usize = 1564; 215 var encoded: [2]u8 = undefined; 216 217 _ = try gol.encode( 218 &encoded, 219 input, 220 .{ .write_padding_bits = true }, 221 ); 222 try testing.expectEqualSlices(u8, &.{ 35, 4 }, &encoded); 223 224 const decoded = try gol.decode(&encoded, .{}); 225 try testing.expectEqual(input, decoded); 226} 227 228test "golomb.encode multiple val = { 1564, 42 }, m = 457" { 229 const testing = std.testing; 230 231 var gol = Self{ .m = 457 }; 232 const input = [_]usize{ 1564, 42 }; 233 var encoded: [3]u8 = undefined; 234 235 _ = try gol.encode(&encoded, input[0], .{ .reset_tmp_values = false }); 236 _ = try gol.encode(&encoded, input[1], .{}); 237 238 try testing.expectEqualSlices(u8, &.{ 35, 6, 42 }, &encoded); 239} 240 241test "golomb.decode multiple val = { 35, 6, 8 }, m = 457" { 242 const testing = std.testing; 243 244 var gol = Self{ .m = 457 }; 245 const input = &[_]u8{ 35, 6, 42 }; 246 247 const decoded1 = try gol.decode(input, .{ .reset_tmp_values = false }); 248 try testing.expectEqual(1564, decoded1); 249 250 const decoded2 = try gol.decode(input, .{}); 251 try testing.expectEqual(42, decoded2); 252}