bit
Fixed-length little-endian bit tuples and bitwise operations.
import type {
Bit, BitAnd, BitOr, BitXor, BitNot,
PopCount, FromBits, ShiftLeft, ShiftRight,
} from '@gentleduck/ttest/bit'
Bits are modeled as fixed-length little-endian tuples of 0 | 1. TypeScript doesn't model numbers as binary, so each helper costs recursion depth per bit. Keep tuple length modest — 8-bit and 16-bit shapes are comfortable, 32+ bits start to strain the type-checker.
Bit
type Bit = 0 | 1
The single-bit alphabet. Use as the constraint on every tuple-shaped generic in this module.
type Byte = readonly [Bit, Bit, Bit, Bit, Bit, Bit, Bit, Bit]
BitAnd
type BitAnd<A extends readonly Bit[], B extends readonly Bit[]>
Element-wise logical AND over two same-length bit tuples.
type a = BitAnd<[1, 1, 0, 1], [1, 0, 1, 1]> // [1, 0, 0, 1]
type b = BitAnd<[1, 1], [1, 1]> // [1, 1]
BitOr
type BitOr<A extends readonly Bit[], B extends readonly Bit[]>
Element-wise logical OR.
type a = BitOr<[1, 0, 0, 1], [0, 1, 0, 1]> // [1, 1, 0, 1]
BitXor
type BitXor<A extends readonly Bit[], B extends readonly Bit[]>
Element-wise XOR.
type a = BitXor<[1, 1, 0, 1], [1, 0, 1, 1]> // [0, 1, 1, 0]
type b = BitXor<[1, 1, 1], [1, 1, 1]> // [0, 0, 0]
BitNot
type BitNot<A extends readonly Bit[]>
Element-wise inversion.
type a = BitNot<[1, 0, 1]> // [0, 1, 0]
type b = BitNot<[0, 0, 0, 0]> // [1, 1, 1, 1]
PopCount
type PopCount<A extends readonly Bit[], Acc extends unknown[] = []>
Number of 1 bits in the tuple. The Acc parameter is internal — don't pass it explicitly.
type a = PopCount<[1, 0, 1, 1, 0]> // 3
type b = PopCount<[0, 0, 0, 0]> // 0
type c = PopCount<[1, 1, 1, 1]> // 4
FromBits
type FromBits<B extends readonly Bit[]>
Convert a little-endian bit tuple to a number literal.
type a = FromBits<[1, 0, 1]> // 5 (1*1 + 0*2 + 1*4)
type b = FromBits<[1, 1, 1, 1]> // 15
type c = FromBits<[0, 0, 0]> // 0
Edge case: arithmetic happens via tuple length, so the result is bounded by the recursive arithmetic limits of number. Larger numbers will hit Type instantiation is excessively deep.
ShiftLeft
type ShiftLeft<A extends readonly Bit[], K extends number>
Fixed-length left shift. Drops the high bit, fills the low end with 0.
type a = ShiftLeft<[1, 0, 1, 0], 1> // [0, 1, 0, 1]
type b = ShiftLeft<[1, 0, 1, 0], 2> // [0, 0, 1, 0]
ShiftRight
type ShiftRight<A extends readonly Bit[], K extends number>
Fixed-length right shift. Drops the low bit, fills the high end with 0.
type a = ShiftRight<[1, 0, 1, 0], 1> // [0, 1, 0, 0]
type b = ShiftRight<[1, 1, 1, 1], 2> // [1, 1, 0, 0]
Edge case across all helpers: tuples are little-endian — index 0 is the least-significant bit. This matters when interpreting FromBits results or comparing with hand-written binary literals.