Skip to content

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

# =============================================================================
#  bitwise.rb — CosmoDev polyglot showcase port of the `bitwise` tool
#  -----------------------------------------------------------------------------
#  Language : Ruby (3.2, standard library only)
#  Source:   ported from src/lib/bitwise.ts (the canonical, live TypeScript
#             lib); mirrors src/tool-sources/bitwise/{python.py,rust.rs}
#  License  : display source — part of CosmoDev's polyglot tool pages
#             (dev.cosmolabs.org). Shown verbatim alongside the JS/TS/Go/Rust/
#             Python ports and the other language ports.
#  -----------------------------------------------------------------------------
#  Pure, deterministic bitwise calculator. Zero deps. Operands are interpreted
#  as width-bit two's-complement values: any integer 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 Integer is always the unsigned
#  bit-pattern of the width-bit result.
#
#  Ruby note: Integer is arbitrary precision, so this is a near-direct port of
#  the reference TypeScript; results are exact across all supported widths
#  (8/16/32/64-bit).
# =============================================================================

module Bitwise
  module_function

  # Numeric base for parsing and formatting bit patterns.
  BASE_RADIX = { bin: 2, oct: 8, dec: 10, hex: 16 }.freeze

  # Lowercase digit alphabet valid for each base — drives validation and the
  # per-character digit-value lookup.
  BASE_DIGITS = {
    bin: '01',
    oct: '01234567',
    dec: '0123456789',
    hex: '0123456789abcdef'
  }.freeze

  # Parse a numeric string in +base+ into an Integer.
  #
  # Accepts an optional leading sign and a single optional base prefix
  # (0x/0b/0o, case-insensitive). Raises ArgumentError on empty input or any
  # digit invalid for the requested base. The raw signed value is returned
  # (no width normalization); callers fold it into a field via
  # normalize/bitwise.
  def parse(value, base)
    trimmed = value.strip
    raise ArgumentError, "Empty #{base} value" if trimmed.empty? || trimmed == '-'

    # Peel off an optional leading '-' so negative literals parse correctly.
    sign = 1
    body = trimmed
    if body[0] == '-'
      sign = -1
      body = body[1..]
    end

    # Strip each base prefix in turn (0x, then 0b, then 0o),
    # case-insensitive — mirrors the reference's chained leading-prefix
    # removal. Matching against the lowercased copy keeps "0xFF" valid.
    body = body.downcase
    %w[0x 0b 0o].each do |prefix|
      body = body.delete_prefix(prefix)
    end

    raise ArgumentError, "Empty #{base} value" if body.empty?

    # Horner's method over the digit alphabet: one pass, exact for any length.
    alphabet = BASE_DIGITS[base]
    radix = BASE_RADIX[base]
    acc = 0
    body.each_char do |ch|
      digit = alphabet.index(ch)
      raise ArgumentError, "Invalid digit '#{ch}' for base #{base}" if digit.nil?

      acc = acc * radix + digit
    end
    sign * acc
  end

  # Bitmask for a +width+-bit field: 2**width - 1. (Internal helper.)
  def mask(width)
    (1 << width) - 1
  end

  # Fold +n+ into 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.
  # (Ruby's % is already floored, but the double form is kept for parity with
  # the reference algorithm and the other language ports.)
  def normalize(n, width)
    m = 1 << width
    ((n % m) + m) % m
  end

  # Render +n+ in +base+, zero-padded to at least +min_digits+ places.
  #
  # Negative values carry a leading '-' and format their magnitude.
  # +min_digits+ corresponds to the +width+ parameter in the TypeScript
  # reference (a width-bit binary value needs exactly +width+ digits).
  #
  # Note: shadows Kernel#format within this module, matching the TS lib's
  # exported `format` for API parity across the polyglot ports.
  # Integer#to_s(radix) already emits lowercase digits matching the TS
  # bigint#toString(radix) output, and renders "0" for zero naturally.
  def format(n, base, min_digits)
    return "-#{format(-n, base, min_digits)}" if n.negative?

    n.to_s(BASE_RADIX[base]).rjust(min_digits, '0')
  end

  # Apply a +width+-bit bitwise operation.
  #
  # Both operands are normalized to +width+ bits first, and the result is
  # masked back into range — so the returned Integer is always the unsigned
  # bit-pattern of the +width+-bit result.
  def bitwise(op, a, b, width)
    m = mask(width)
    x = normalize(a, width)
    y = normalize(b, width)

    case op
    when :and then x & y
    when :or  then x | y
    when :xor then x ^ y
    when :not then ~x & m
    when :shl
      # Left shift grows; for shift >= width the masked result is 0.
      # Short-circuit to avoid building a (potentially huge) intermediate.
      return 0 if y >= width

      (x << y) & m
    when :shr
      # x is normalized non-negative → logical (zero-filling) shift.
      return 0 if y >= width

      x >> y
    when :rol, :ror
      shift = y % width # rotate amount wraps within width
      return x if shift.zero?

      # A right-rotate by +shift+ is a left-rotate by (width - shift).
      s = op == :rol ? shift : width - shift
      ((x << s) | (x >> (width - s))) & m
    else
      raise ArgumentError, "Unknown bitwise operation: #{op.inspect}"
    end
  end

  # Fixed-width binary string of +width+ bits (MSB first).
  def to_bits(n, width)
    format(normalize(n, width), :bin, width)
  end

  # Indices of set bits in the normalized +width+-bit pattern (LSB = 0).
  def flags(n, width)
    bits = normalize(n, width)
    out = []
    i = 0
    while bits.positive?
      out << i if bits[0] == 1
      bits >>= 1
      i += 1
    end
    out
  end
end

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 →