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 →