Skip to content

Bitwise Calculator — Go source

Perform AND, OR, XOR, NOT, shifts and rotates on 8/16/32/64-bit values with exact bigint math. Enter operands in binary, octal, decimal or hex and read the result in every base plus a live bit grid. Runs 100% in your browser.

This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Package bitwise is the Go twin of CosmoDev's src/lib/bitwise.ts (dual source:
// the web lib is TypeScript, the CLI lib is Go — kept in lock-step). Pure +
// deterministic, never panics. The table-driven tests in bitwise_test.go share
// vectors with src/lib/bitwise.test.ts so the two implementations are held to
// the same contract.
//
// The TS lib operates on bigint to stay exact across all widths (8/16/32/64-bit);
// this twin uses math/big's Int for the same reason — a 64-bit result such as
// (1<<64)-1 overflows int64. Operands are interpreted as width-bit
// two's-complement values: any Int is normalized to the half-open range
// [0, 2^width) before an operation, and every result is masked back into that
// range, so the returned Int is always the unsigned bit-pattern of the
// width-bit result.
package bitwise

import (
	"fmt"
	"math/big"
	"strings"
)

// Base is the numeric radix of an input/output string. It is the Go counterpart
// of the TS `Base` union ('bin' | 'oct' | 'dec' | 'hex').
type Base string

const (
	Bin Base = "bin"
	Oct Base = "oct"
	Dec Base = "dec"
	Hex Base = "hex"
)

// Op is a width-bit bitwise operation. It is the Go counterpart of the TS `Op`
// union ('and' | 'or' | 'xor' | 'not' | 'shl' | 'shr' | 'rol' | 'ror').
type Op string

const (
	And Op = "and"
	Or  Op = "or"
	Xor Op = "xor"
	Not Op = "not"
	Shl Op = "shl"
	Shr Op = "shr"
	Rol Op = "rol"
	Ror Op = "ror"
)

// baseRadix holds the numeric radix for each Base — mirrors BASE_RADIX.
var baseRadix = map[Base]int{Bin: 2, Oct: 8, Dec: 10, Hex: 16}

// baseDigits holds the digits valid for each base (lowercased) — mirrors
// BASE_DIGITS. The index of a rune in its string equals that digit's value.
var baseDigits = map[Base]string{
	Bin: "01",
	Oct: "01234567",
	Dec: "0123456789",
	Hex: "0123456789abcdef",
}

// Parse parses a numeric string in base into an *big.Int. It trims surrounding
// whitespace and strips a single optional 0x/0b/0o prefix (case-insensitive),
// mirroring parse() in src/lib/bitwise.ts. It returns an error in the same
// cases the TS throws (empty value, prefix with no digits, out-of-range digit,
// stray sign).
func Parse(value string, base Base) (*big.Int, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" || trimmed == "-" {
		return nil, fmt.Errorf("empty %s value", base)
	}

	sign := int64(1)
	body := trimmed
	if body[0] == '-' {
		sign = -1
		body = body[1:]
	}

	// Strip a single optional base prefix (case-insensitive), in the same
	// 0x → 0b → 0o order as the TS chained replaces.
	if len(body) >= 2 && (body[:2] == "0x" || body[:2] == "0X") {
		body = body[2:]
	}
	if len(body) >= 2 && (body[:2] == "0b" || body[:2] == "0B") {
		body = body[2:]
	}
	if len(body) >= 2 && (body[:2] == "0o" || body[:2] == "0O") {
		body = body[2:]
	}

	if body == "" {
		return nil, fmt.Errorf("empty %s value", base)
	}

	allowed := baseDigits[base]
	radix := big.NewInt(int64(baseRadix[base]))
	acc := new(big.Int)
	for _, ch := range strings.ToLower(body) {
		idx := strings.IndexRune(allowed, ch)
		if idx < 0 {
			return nil, fmt.Errorf("invalid digit '%c' for base %s", ch, base)
		}
		acc.Mul(acc, radix)
		acc.Add(acc, big.NewInt(int64(idx)))
	}
	if sign < 0 {
		acc.Neg(acc)
	}
	return acc, nil
}

// mask returns the width-bit field mask 2^width - 1 (unexported, like the TS
// mask() helper).
func mask(width int) *big.Int {
	return new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), uint(width)), big.NewInt(1))
}

// Normalize reduces any *big.Int to its unsigned width-bit two's-complement
// value in [0, 2^width). big.Int.Mod is Euclidean (the result takes the sign
// of the divisor), so Mod(n, 2^width) is exactly ((n % m) + m) % m from the TS.
func Normalize(n *big.Int, width int) *big.Int {
	m := new(big.Int).Lsh(big.NewInt(1), uint(width))
	return new(big.Int).Mod(n, m)
}

// Format formats n in base, zero-padded to at least width digits. Negatives
// carry a leading '-' (the magnitude is padded). Mirrors format() in the TS lib;
// like the TS it never truncates when the value exceeds width.
func Format(n *big.Int, base Base, width int) string {
	if n.Sign() < 0 {
		mag := new(big.Int).Neg(n)
		return "-" + Format(mag, base, width)
	}
	radix := baseRadix[base]
	var digits string
	if n.Sign() == 0 {
		digits = "0"
	} else {
		digits = n.Text(radix) // lowercase for radix > 10, matching JS toString
	}
	for len(digits) < width {
		digits = "0" + digits
	}
	return digits
}

// Bitwise applies a width-bit operation. a is the (unary) operand for Not; b is
// the second operand for binary ops and the shift/rotate count for Shl/Shr/
// Rol/Ror. Both operands are normalized to width-bit two's complement first;
// the result is masked to width bits. Mirrors bitwise() in the TS lib. An
// unrecognized op returns 0 (the TS returns undefined for an unmatched switch).
func Bitwise(op Op, a, b *big.Int, width int) *big.Int {
	m := mask(width)
	x := Normalize(a, width)
	y := Normalize(b, width)

	switch op {
	case And:
		return new(big.Int).And(x, y)
	case Or:
		return new(big.Int).Or(x, y)
	case Xor:
		return new(big.Int).Xor(x, y)
	case Not:
		return new(big.Int).And(new(big.Int).Not(x), m)
	case Shl:
		// (x << y) & m. A shift count >= width makes the product a multiple of
		// 2^width, clearing every bit — short-circuit to avoid a giant Lsh.
		if y.Cmp(big.NewInt(int64(width))) >= 0 {
			return new(big.Int)
		}
		shifted := new(big.Int).Lsh(x, uint(y.Uint64()))
		return new(big.Int).And(shifted, m)
	case Shr:
		// x is normalized non-negative → logical shift.
		return new(big.Int).Rsh(x, uint(y.Uint64()))
	case Rol, Ror:
		w := width
		shift := int(new(big.Int).Mod(y, big.NewInt(int64(w))).Uint64()) // wraps within width → [0, w)
		if shift == 0 {
			return x // identity
		}
		s := shift
		if op == Ror {
			s = w - shift
		}
		left := new(big.Int).Lsh(x, uint(s))
		right := new(big.Int).Rsh(x, uint(w-s))
		rot := new(big.Int).Or(left, right)
		return new(big.Int).And(rot, m)
	}
	return new(big.Int)
}

// ToBits returns the fixed-width binary string of width bits (MSB first).
// Mirrors toBits() in the TS lib.
func ToBits(n *big.Int, width int) string {
	return Format(Normalize(n, width), Bin, width)
}

// Flags returns the indices of set bits (LSB = index 0), with n normalized to
// width bits. Mirrors flags() in the TS lib.
func Flags(n *big.Int, width int) []int {
	bits := Normalize(n, width)
	out := []int{}
	i := 0
	for v := new(big.Int).Set(bits); v.Sign() > 0; i++ {
		if v.Bit(0) == 1 {
			out = append(out, i)
		}
		v.Rsh(v, 1)
	}
	return out
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →