Skip to content

Bitwise Calculator — JavaScript 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 JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// =============================================================================
//  bitwise.js - CosmoDev polyglot showcase port of the `bitwise` tool
// -----------------------------------------------------------------------------
//  Language : JavaScript (ES2020+, runs on BigInt)
//  Source   : ported from src/lib/bitwise.ts (the canonical, live TypeScript lib)
//  License  : display source - part of CosmoDev's polyglot tool pages
//  (dev.cosmolabs.org). Shown verbatim alongside the TS/Go/Rust/PHP/Python ports.
// -----------------------------------------------------------------------------
//  Pure, deterministic bitwise calculator. Zero deps. Operates on `bigint` so
//  results are exact across all supported widths (8/16/32/64-bit). Operands are
//  interpreted as width-bit two's-complement values: any bigint 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 bigint is always the unsigned
//  bit-pattern of the width-bit result.
// =============================================================================

'use strict';

/** Numeric radix for each supported base. */
const BASE_RADIX = { bin: 2, oct: 8, dec: 10, hex: 16 };

/** Digits valid for each base (lowercased). Used for both validation and parsing. */
const BASE_DIGITS = {
  bin: '01',
  oct: '01234567',
  dec: '0123456789',
  hex: '0123456789abcdef',
};

/**
 * Parse a numeric string in `base` into a bigint. Strips 0x/0b/0o prefixes and
 * an optional leading sign. Throws on empty input or any out-of-base digit.
 */
export function parse(value, base) {
  const trimmed = String(value).trim();
  if (trimmed === '' || trimmed === '-') {
    throw new Error(`Empty ${base} value`);
  }

  // Peel off an optional leading '-' so negative literals parse correctly.
  let sign = 1n;
  let body = trimmed;
  if (body[0] === '-') {
    sign = -1n;
    body = body.slice(1);
  }

  // Strip each base prefix in turn (0x, then 0b, then 0o, case-insensitive),
  // applied sequentially - this mirrors the reference's chained replacement.
  body = body
    .replace(/^0x/i, '')
    .replace(/^0b/i, '')
    .replace(/^0o/i, '');

  if (body === '') throw new Error(`Empty ${base} value`);

  // Horner's method over the digit alphabet: one pass, exact for any length.
  const allowed = BASE_DIGITS[base];
  const radix = BigInt(BASE_RADIX[base]);
  let acc = 0n;
  for (const ch of body.toLowerCase()) {
    const digitValue = allowed.indexOf(ch);
    if (digitValue < 0) {
      throw new Error(`Invalid digit '${ch}' for base ${base}`);
    }
    acc = acc * radix + BigInt(digitValue);
  }
  return sign * acc;
}

/** Mask for a `width`-bit field: 2^width - 1. (Internal helper, not exported.) */
function mask(width) {
  return (1n << BigInt(width)) - 1n;
}

/**
 * Normalize any bigint to its unsigned width-bit two's-complement value.
 *
 * The double-modulo `((n % m) + m) % m` maps negative dividends into the
 * canonical unsigned range [0, 2^width): e.g. -1 at width 8 yields 255.
 */
export function normalize(n, width) {
  const m = 1n << BigInt(width);
  return (((n % m) + m) % m);
}

/**
 * Format `n` in `base`, zero-padded to at least `width` digits. Negatives carry
 * a leading '-'. (The third arg is a minimum digit count - the binary rendering
 * of a width-bit value needs exactly `width` digits.)
 */
export function format(n, base, width) {
  if (n < 0n) return '-' + format(-n, base, width);
  const radix = BASE_RADIX[base];
  let digits = n === 0n ? '0' : n.toString(radix);
  while (digits.length < width) digits = '0' + digits;
  return digits;
}

/**
 * Apply 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.
 */
export function bitwise(op, a, b, width) {
  const m = mask(width);
  const x = normalize(a, width);
  const y = normalize(b, width);
  const w = BigInt(width);

  switch (op) {
    case 'and': return x & y;
    case 'or': return x | y;
    case 'xor': return x ^ y;
    case 'not': return (~x) & m;            // mask: BigInt ~ is unbounded
    case 'shl':
      // Left shift grows; for shift >= width every significant bit leaves the
      // field, so the masked result is 0. Short-circuit to avoid materializing
      // a (potentially huge) intermediate bigint.
      return y >= w ? 0n : ((x << y) & m);
    case 'shr':
      // x is normalized non-negative, so >> is a logical (zero-filling) shift.
      return y >= w ? 0n : (x >> y);
    case 'rol':
    case 'ror': {
      const shift = y % w;                  // rotate amount wraps within width
      if (shift === 0n) return x;
      // A right-rotate by `shift` is a left-rotate by (width - shift).
      const s = op === 'rol' ? shift : w - shift;
      return ((x << s) | (x >> (w - s))) & m;
    }
    default:
      throw new Error(`Unknown bitwise operation: ${op}`);
  }
}

/** Fixed-width binary string of `width` bits (MSB first). */
export function toBits(n, width) {
  return format(normalize(n, width), 'bin', width);
}

/** Indices of set bits (LSB = index 0), normalized to `width`. */
export function flags(n, width) {
  const bits = normalize(n, width);
  const out = [];
  let i = 0;
  for (let v = bits; v > 0n; v >>= 1n) {
    if (v & 1n) out.push(i);
    i++;
  }
  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 →