Skip to content

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 →