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 →