Bitwise Calculator — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// =============================================================================
// bitwise.kt — CosmoDev polyglot showcase port of the `bitwise` tool
// -----------------------------------------------------------------------------
// Language : Kotlin (1.9, 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.
//
// Kotlin note: the TS source uses arbitrary-precision `bigint`. Kotlin/JVM
// has no 128-bit integer, so normalized values live in ULong (the unsigned
// 64-bit type) and parsed inputs in Long. This stays exact for every
// supported width (8/16/32/64): parsed magnitudes fold through two's-
// complement wrapping (invisible after width-bit normalization), and left
// shifts wrap mod 2^64 — lossless, because the mask never keeps bits at or
// above bit 64. Only literals beyond 2^64-1 diverge from the TS lib (thrown
// as a clean out-of-range), far outside any realistic input.
//
// Errors mirror the Python port's ValueError as IllegalArgumentException.
// =============================================================================
/** Numeric radix used for parsing and formatting. */
enum class Base(val radix: Int, val digits: String) {
BIN(2, "01"),
OCT(8, "01234567"),
DEC(10, "0123456789"),
HEX(16, "0123456789abcdef");
/** Lowercase name, for error messages that match the TS lib. */
val lower: String = name.lowercase()
}
/**
* Supported bitwise operation. [Op.NOT] is unary on `a`; the rest are binary,
* with `b` as the shift/rotate count for the shift/rotate ops.
*/
enum class Op { AND, OR, XOR, NOT, SHL, SHR, ROL, ROR }
/**
* Parse a numeric string in [base] into a signed Long. Strips 0x/0b/0o
* prefixes and an optional leading sign. Throws
* [IllegalArgumentException] on empty input, any out-of-base digit, or a
* literal whose magnitude exceeds 2^64-1. The raw signed value is returned
* (no width normalization); callers fold it into a field via [normalize] /
* [bitwise].
*
* Magnitudes accumulate as ULong and negate by wrapping subtraction, so the
* full unsigned range (e.g. hex 0xFFFFFFFFFFFFFFFF) round-trips exactly — a
* wrap of 2^64 is invisible once the value is width-normalized.
*/
fun parse(value: String, base: Base): Long {
val trimmed = value.trim()
require(!(trimmed.isEmpty() || trimmed == "-")) { "Empty ${base.lower} value" }
// Peel off an optional leading '-' so negative literals parse correctly.
val negative = trimmed.startsWith('-')
val body = if (negative) trimmed.substring(1) else trimmed
// Strip each base prefix in turn (0x, then 0b, then 0o) from a lowered
// copy — mirrors the reference's chained leading-prefix removal.
// Matching against the lowercased copy keeps "0xFF" valid.
var digits = body.lowercase()
for (prefix in listOf("0x", "0b", "0o")) {
if (digits.startsWith(prefix)) digits = digits.removePrefix(prefix)
}
require(digits.isNotEmpty()) { "Empty ${base.lower} value" }
// Horner's method over the digit alphabet, with a checked accumulate:
// an over-wide literal reports a clean error instead of wrapping.
val alphabet = base.digits
val radix = base.radix.toULong()
var acc = 0uL
for (ch in digits) {
val digit = alphabet.indexOf(ch).takeIf { it >= 0 }
?: throw IllegalArgumentException("Invalid digit '$ch' for base ${base.lower}")
val d = digit.toULong()
if (acc > (ULong.MAX_VALUE - d) / radix) {
throw IllegalArgumentException("Value out of range for base ${base.lower}")
}
acc = acc * radix + d
}
// Wrapping negate is exact mod 2^64 (two's complement); toLong() keeps
// the bit pattern.
return (if (negative) 0uL - acc else acc).toLong()
}
/**
* Mask for a [width]-bit field: 2^width - 1. Width 64 is spelled directly
* because 1 shl 64 would wrap in a ULong.
*/
fun mask(width: Int): ULong =
if (width == 64) ULong.MAX_VALUE else (1uL shl width) - 1uL
/**
* Normalize any signed value to its unsigned width-bit two's-complement
* value, i.e. into the half-open range [0, 2^width) — e.g. -1 at width 8
* yields 255. The reference computes `((n % m) + m) % m` with m = 2^width on
* arbitrary-precision ints; for width < 64 that is exactly Kotlin's floored
* `mod`, and for width 64 the Long bit pattern already IS n mod 2^64
* (two's-complement truncation), so it passes through as-is.
*/
fun normalize(n: Long, width: Int): ULong =
// Math.floorMod is the floored modulo (non-negative for a positive
// divisor) — exactly the reference's ((n % m) + m) % m.
if (width == 64) n.toULong() else Math.floorMod(n, 1L shl width).toULong()
/**
* Render [n] in [base], zero-padded to at least [minDigits] digits.
* Negatives carry a leading '-' and format their magnitude (via wrapping
* negation on the ULong pattern — exact even at Long.MIN_VALUE).
* [minDigits] corresponds to the `width` parameter of the TS reference
* (a width-bit binary value needs exactly `width` digits).
*/
fun format(n: Long, base: Base, minDigits: Int): String {
if (n < 0) {
// Wrapping negate yields the true magnitude as a bit pattern.
return "-" + formatUnsigned((0uL - n.toULong()), base, minDigits)
}
return formatUnsigned(n.toULong(), base, minDigits)
}
// Manual base conversion keeps parity with the sibling ports and yields the
// lowercase digits of the TS `bigint.toString(radix)` output.
private fun formatUnsigned(v: ULong, base: Base, minDigits: Int): String {
val alphabet = "0123456789abcdef"
val radix = base.radix.toULong()
// Extract digits LSB-first; the do/while renders "0" for zero naturally.
val buf = StringBuilder()
var t = v
do {
buf.append(alphabet[(t % radix).toInt()])
t /= radix
} while (t != 0uL)
return buf.toString().padStart(minDigits, '0')
}
/**
* Apply a width-bit operation. `a` is the (unary) operand for [Op.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.
*
* For shl/shr we short-circuit when the shift count meets or exceeds the
* width: every significant bit is shifted out, so the masked result is zero
* (this also keeps every shift amount below 64 — ULong.shl masks its count
* to 6 bits, so an unguarded shift would silently wrap). Left shifts wrap
* mod 2^64, which is lossless because the mask never keeps bits at or above
* bit 64; shr on ULong is already logical (zero-filling).
*/
fun bitwise(op: Op, a: Long, b: Long, width: Int): ULong {
val m = mask(width)
val x = normalize(a, width)
val y = normalize(b, width)
return when (op) {
Op.AND -> x and y
Op.OR -> x or y
Op.XOR -> x xor y
Op.NOT -> x.inv() and m
Op.SHL ->
if (y >= width.toULong()) {
0uL
} else {
(x shl y.toInt()) and m // y < 64 here, so the shift count is exact
}
Op.SHR ->
// x is normalized non-negative → logical (zero-filling) shift.
if (y >= width.toULong()) 0uL else x shr y.toInt()
Op.ROL, Op.ROR -> {
val shift = y % width.toULong() // rotate amount wraps within width
if (shift == 0uL) {
x
} else {
// A right-rotate by `shift` is a left-rotate by (width - shift).
val s = (if (op == Op.ROL) shift else width.toULong() - shift).toInt()
((x shl s) or (x shr (width - s))) and m // s, width - s < 64 here
}
}
}
}
/**
* Fixed-width binary string of [width] bits (MSB first). Formats the
* unsigned normalized pattern directly — width-64 patterns can exceed
* Long.MAX_VALUE and must not round-trip through the signed path.
*/
fun toBits(n: Long, width: Int): String =
formatUnsigned(normalize(n, width), Base.BIN, width)
/**
* Indices of set bits (LSB = index 0), normalized to [width], in ascending
* order. Stops at the highest set bit.
*/
fun flags(n: Long, width: Int): List<Int> {
var v = normalize(n, width)
val out = mutableListOf<Int>()
var i = 0
while (v != 0uL) {
if (v and 1uL != 0uL) out.add(i)
v = v shr 1
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 →