Skip to content

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

// =============================================================================
//  bitwise.java — CosmoDev polyglot showcase port of the `bitwise` tool
//  -----------------------------------------------------------------------------
//  Language : Java (Java 17, 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.
//
//  Java note: the TS source uses arbitrary-precision `bigint`. Java has no
//  unsigned or 128-bit integer type, so every normalized value lives in a
//  `long` holding the UNSIGNED bit pattern (values in [2^63, 2^64) appear
//  "negative" — only the bits are meaningful). All arithmetic that can exceed
//  2^63 goes through Long's unsigned helpers (divideUnsigned /
//  remainderUnsigned / compareUnsigned) or wrapping two's-complement
//  arithmetic, which is exact for the masked result. Only literals beyond
//  2^64-1 diverge from the TS lib (thrown as a clean out-of-range), far
//  outside any realistic bitwise-calculator input.
// =============================================================================

import java.util.ArrayList;
import java.util.List;

public final class Bitwise {

    private Bitwise() {
    }

    /** Numeric radix used for parsing and formatting. */
    public enum Base {
        BIN("bin", 2, "01"),
        OCT("oct", 8, "01234567"),
        DEC("dec", 10, "0123456789"),
        HEX("hex", 16, "0123456789abcdef");

        final String lower;
        final int radix;
        final String digits;

        Base(String lower, int radix, String digits) {
            this.lower = lower;
            this.radix = radix;
            this.digits = digits;
        }
    }

    /** Bit-width of the virtual register (8/16/32/64). */
    public static final int[] WIDTHS = {8, 16, 32, 64};

    /**
     * Supported bitwise operation. {@code NOT} is unary on {@code a}; the rest
     * are binary, with {@code b} as the shift/rotate count for the
     * shift/rotate ops.
     */
    public enum Op {AND, OR, XOR, NOT, SHL, SHR, ROL, ROR}

    /**
     * Parses a numeric string in {@code base} into a signed {@code long}.
     * Strips 0x/0b/0o prefixes and an optional leading sign. Throws
     * {@link 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
     * {@link #normalize} / {@link #bitwise}.
     *
     * <p>Magnitudes accumulate as unsigned bit patterns in a {@code long} 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.
     */
    public static long parse(String value, Base base) {
        String trimmed = value.strip();
        if (trimmed.isEmpty() || trimmed.equals("-")) {
            throw new IllegalArgumentException("Empty " + base.lower + " value");
        }

        // Peel off an optional leading '-' so negative literals parse correctly.
        boolean negative = trimmed.charAt(0) == '-';
        String body = negative ? trimmed.substring(1) : 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.
        String lowered = body.toLowerCase(java.util.Locale.ROOT);
        for (String prefix : new String[]{"0x", "0b", "0o"}) {
            if (lowered.startsWith(prefix)) {
                lowered = lowered.substring(prefix.length());
            }
        }
        if (lowered.isEmpty()) {
            throw new IllegalArgumentException("Empty " + base.lower + " value");
        }

        // Horner's method over the digit alphabet, with a checked accumulate
        // (unsigned view): an over-wide literal reports a clean error instead
        // of wrapping.
        String allowed = base.digits;
        long radix = base.radix;
        long acc = 0L;
        for (int i = 0; i < lowered.length(); i++) {
            char ch = lowered.charAt(i);
            int digit = allowed.indexOf(ch);
            if (digit < 0) {
                throw new IllegalArgumentException("Invalid digit '" + ch + "' for base " + base.lower);
            }
            // acc must satisfy acc * radix + digit <= 2^64 - 1, i.e.
            // acc <= floor((2^64 - 1 - digit) / radix), computed unsigned.
            long limit = Long.divideUnsigned(-1L - digit, radix); // -1L - digit wraps to 2^64-1-digit
            if (Long.compareUnsigned(acc, limit) > 0) {
                throw new IllegalArgumentException("Value out of range for base " + base.lower);
            }
            // Wrapping multiply/add: exact mod 2^64, and the check above
            // guarantees no wrap actually occurs.
            acc = acc * radix + digit;
        }

        // Wrapping negate is exact mod 2^64 (two's complement).
        return negative ? 0L - acc : acc;
    }

    /**
     * Mask for a {@code width}-bit field: 2^width - 1. Width 64 is spelled
     * directly because 1 &lt;&lt; 64 would wrap in a long.
     */
    public static long mask(int width) {
        return width == 64 ? -1L : (1L << width) - 1L;
    }

    /**
     * Normalizes 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 result is the unsigned bit pattern in a long.
     *
     * <p>The reference computes {@code ((n % m) + m) % m} with m = 2^width on
     * arbitrary-precision ints. For width &lt; 64 that is exactly
     * {@link Math#floorMod}; for width 64 a long already IS n mod 2^64
     * (two's-complement truncation), so the pattern passes through as-is.
     */
    public static long normalize(long n, int width) {
        return width == 64 ? n : Math.floorMod(n, 1L << width);
    }

    /**
     * Renders {@code n} in {@code base}, zero-padded to at least
     * {@code minDigits} digits. Negatives carry a leading '-' and format
     * their magnitude (via {@link Long#toUnsignedString(long, int)}, which
     * reads the magnitude off the bit pattern — exact even past 2^63).
     * {@code minDigits} corresponds to the {@code width} parameter of the TS
     * reference (a width-bit binary value needs exactly {@code width} digits).
     */
    public static String format(long n, Base base, int minDigits) {
        if (n < 0) {
            // Wrapping negate yields the true magnitude as a bit pattern.
            long mag = 0L - n;
            return "-" + formatUnsigned(mag, base, minDigits);
        }
        return formatUnsigned(n, base, minDigits);
    }

    // Long.toUnsignedString produces the lowercase digits the TS
    // `bigint.toString(radix)` call produces; manual padding adds the zeros.
    private static String formatUnsigned(long v, Base base, int minDigits) {
        String digits = Long.toUnsignedString(v, base.radix); // "0" renders naturally for zero
        if (digits.length() < minDigits) {
            digits = "0".repeat(minDigits - digits.length()) + digits;
        }
        return digits;
    }

    /**
     * Applies a width-bit operation. {@code a} is the (unary) operand for
     * NOT; {@code 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 (an unsigned bit pattern)
     * is masked to {@code width} bits.
     *
     * <p>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 — Java masks
     * long-shift counts to 6 bits, so an unguarded shift would silently wrap).
     * Left shifts keep only the low 64 bits of the true bigint shift, which is
     * lossless because the mask never keeps bits at or above bit 64.
     */
    public static long bitwise(Op op, long a, long b, int width) {
        long m = mask(width);
        long x = normalize(a, width);
        long y = normalize(b, width);

        switch (op) {
            case AND:
                return x & y;
            case OR:
                return x | y;
            case XOR:
                return x ^ y;
            case NOT:
                return ~x & m;
            case SHL:
                if (Long.compareUnsigned(y, width) >= 0) {
                    return 0L;
                }
                return (x << y) & m; // y < 64 here, so the shift count is exact
            case SHR:
                // x is normalized non-negative as a pattern → logical
                // (zero-filling) shift, which is Java's >>> on the pattern.
                if (Long.compareUnsigned(y, width) >= 0) {
                    return 0L;
                }
                return x >>> y;
            case ROL:
            case ROR: {
                long shift = Long.remainderUnsigned(y, width); // rotate amount wraps within width
                if (shift == 0) {
                    return x;
                }
                // A right-rotate by `shift` is a left-rotate by (width - shift).
                long s = op == Op.ROL ? shift : width - shift;
                return ((x << s) | (x >>> (width - s))) & m; // s, width - s < 64 here
            }
            default:
                throw new IllegalArgumentException("Unknown bitwise operation: " + op);
        }
    }

    /**
     * Fixed-width binary string of {@code 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.
     */
    public static String toBits(long n, int width) {
        return formatUnsigned(normalize(n, width), Base.BIN, width);
    }

    /**
     * Indices of set bits (LSB = index 0), normalized to {@code width}, in
     * ascending order. Stops at the highest set bit (v != 0 is pattern
     * non-zero).
     */
    public static List<Integer> flags(long n, int width) {
        long v = normalize(n, width);
        List<Integer> out = new ArrayList<>();
        int i = 0;
        while (v != 0L) {
            if ((v & 1L) != 0L) {
                out.add(i);
            }
            v >>>= 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 →