bits
import bits from "bits";bits works on the bits and bytes of unsigned integers: counting bits,
rotating, swapping byte order, and reading or writing fixed-width integers
in a byte buffer. It is a pure t0 module: it allocates nothing, has no
state, and works in every profile.
Counts and offsets are i64, like .len. The functions exist for the
widths shown in their names; for a signed value, cast it (x as u64).
Counting bits
| Function | Returns |
|---|---|
popcount_u64(x: u64) -> i64 | the number of 1 bits (0 to 64) |
popcount_u32(x: u32) -> i64 | the number of 1 bits (0 to 32) |
clz_u64(x: u64) -> i64 | the 0 bits above the highest 1 bit; 64 when x is 0 |
clz_u32(x: u32) -> i64 | the same for 32 bits; 32 when x is 0 |
ctz_u64(x: u64) -> i64 | the 0 bits below the lowest 1 bit; 64 when x is 0 |
ctz_u32(x: u32) -> i64 | the same for 32 bits; 32 when x is 0 |
io.println("{bits.popcount_u64(0xF0F0)}"); // 8
io.println("{bits.clz_u64(1)}"); // 63
io.println("{bits.clz_u64(0)}"); // 64
io.println("{bits.ctz_u64(0x100)}"); // 8
io.println("{bits.ctz_u32(0)}"); // 32
// Bits needed to hold n: 64 - clz, which is 0 for n == 0
let n: u64 = 1000;
let width: i64 = 64 - bits.clz_u64(n); // 10Rotating
| Function | Returns |
|---|---|
rotl_u64(x: u64, n: i64) -> u64 | x rotated left by n bits |
rotr_u64(x: u64, n: i64) -> u64 | x rotated right by n bits |
rotl_u32(x: u32, n: i64) -> u32 | x rotated left by n bits |
rotr_u32(x: u32, n: i64) -> u32 | x rotated right by n bits |
Bits that leave one end come back in at the other. Any n works: it is
taken modulo the width, so rotating by 64 (or 32) changes nothing and a
negative n rotates the other way.
let a: u64 = bits.rotl_u64(0x8000000000000001, 4); // 0x18
let b: u64 = bits.rotr_u64(1, 1); // 0x8000000000000000
let c: u32 = bits.rotl_u32(0x12345678, 8); // 0x34567812
let d: u32 = bits.rotr_u32(0x12345678, 8); // 0x78123456Swapping byte order
| Function | Returns |
|---|---|
bswap_u16(x: u16) -> u16 | x with its 2 bytes reversed |
bswap_u32(x: u32) -> u32 | x with its 4 bytes reversed |
bswap_u64(x: u64) -> u64 | x with its 8 bytes reversed |
let s: u16 = bits.bswap_u16(0x1234); // 0x3412
let w: u32 = bits.bswap_u32(0x12345678); // 0x78563412
let q: u64 = bits.bswap_u64(0x0102030405060708); // 0x0807060504030201To read or write integers in a byte buffer, use the loads and stores below instead: they don’t depend on the machine’s byte order.
Reading integers from bytes
| Function | Reads |
|---|---|
load_u16_le(src: []const u8, off: i64) -> u16 | 2 bytes at off, least significant first |
load_u16_be(src: []const u8, off: i64) -> u16 | 2 bytes at off, most significant first |
load_u32_le(src: []const u8, off: i64) -> u32 | 4 bytes, little-endian |
load_u32_be(src: []const u8, off: i64) -> u32 | 4 bytes, big-endian |
load_u64_le(src: []const u8, off: i64) -> u64 | 8 bytes, little-endian |
load_u64_be(src: []const u8, off: i64) -> u64 | 8 bytes, big-endian |
off can be any position; there is no alignment requirement. A load
traps with offset out of range if off is negative or the bytes run
past the end of src.
// The first chunk of a PNG file: its length is a big-endian u32 at offset 8
let header: []const u8 = "\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR";
let len: u32 = bits.load_u32_be(header, 8); // 13
let le: []const u8 = "\x34\x12";
let v: u16 = bits.load_u16_le(le, 0); // 0x1234Writing integers to bytes
| Function | Writes |
|---|---|
store_u16_le(dst: []u8, off: i64, v: u16) | v as 2 bytes at off, least significant first |
store_u16_be(dst: []u8, off: i64, v: u16) | 2 bytes, most significant first |
store_u32_le(dst: []u8, off: i64, v: u32) | 4 bytes, little-endian |
store_u32_be(dst: []u8, off: i64, v: u32) | 4 bytes, big-endian |
store_u64_le(dst: []u8, off: i64, v: u64) | 8 bytes, little-endian |
store_u64_be(dst: []u8, off: i64, v: u64) | 8 bytes, big-endian |
Only those bytes of dst change. A store traps with offset out of range if off is negative or the bytes would run past the end of dst,
and then writes nothing.
var buf: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0];
bits.store_u16_be(buf, 0, 0xCAFE); // buf[0] == 0xCA, buf[1] == 0xFE
bits.store_u32_le(buf, 4, 0x01020304); // buf[4] == 4, buf[7] == 1
bits.store_u64_be(buf, 0, 42);
let back: u64 = bits.load_u64_be(buf, 0); // 42Constant time
The rotations, byte swaps, loads and stores are
@constant_time: they
never branch on, or index by, the bytes and values they work on, so
constant-time code (cryptography) can call them with secrets. Their offset
parameters are @public; the rotation count may be secret. The counting
functions are not marked.
Last modified: