Skip to content

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

// =============================================================================
//  bitwise.rs — CosmoDev polyglot showcase port of the `bitwise` tool
// -----------------------------------------------------------------------------
//  Language : Rust (standard library only)
//  Source   : ported from src/lib/bitwise.ts (the canonical, live TypeScript lib)
//  License  : display source — part of CosmoDev's polyglot tool pages
//  (dev.cosmolabs.org). Shown verbatim alongside the JS/TS/Go/PHP/Python 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.
//
//  Rust note: the TS source uses arbitrary-precision `bigint`. The Rust stdlib
//  has no bignum type, so signed parsing/formatting uses `i128` and masked
//  results use `u128`. This is exact for every supported width (8/16/32/64) and
//  for all shift intermediates (a 64-bit value shifted left by up to 63 needs at
//  most 127 bits). Only literals beyond ~10^38 diverge from the TS lib — far
//  outside any realistic bitwise-calculator input.
// =============================================================================

/// Numeric radix used for parsing and formatting.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Base {
    Bin,
    Oct,
    Dec,
    Hex,
}

impl Base {
    /// Positional radix (2/8/10/16).
    fn radix(self) -> u32 {
        match self {
            Base::Bin => 2,
            Base::Oct => 8,
            Base::Dec => 10,
            Base::Hex => 16,
        }
    }

    /// Lowercase digit alphabet valid for this base.
    fn digits(self) -> &'static str {
        match self {
            Base::Bin => "01",
            Base::Oct => "01234567",
            Base::Dec => "0123456789",
            Base::Hex => "0123456789abcdef",
        }
    }

    /// Lowercase name, for error messages that match the TS lib.
    fn lower(self) -> &'static str {
        match self {
            Base::Bin => "bin",
            Base::Oct => "oct",
            Base::Dec => "dec",
            Base::Hex => "hex",
        }
    }
}

/// Bit-width of the virtual register (8/16/32/64).
pub type Width = u32;

/// Bitwise operation. `Not` is unary on `a`; the rest are binary, with `b` as
/// the shift/rotate count for the shift/rotate ops.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Op {
    And,
    Or,
    Xor,
    Not,
    Shl,
    Shr,
    Rol,
    Ror,
}

/// Parse error: an empty value, an out-of-base digit, or an out-of-range literal.
#[derive(Debug, PartialEq, Eq)]
pub struct ParseError(pub String);

impl std::fmt::Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::error::Error for ParseError {}

/// Parse a numeric string in `base` into a signed integer. Strips 0x/0b/0o
/// prefixes and an optional leading sign. Errors on empty input, any
/// out-of-base digit, or a literal that overflows i128. The raw signed value is
/// returned (no width normalization); callers fold it into a field via
/// `normalize` / `bitwise`.
pub fn parse(value: &str, base: Base) -> Result<i128, ParseError> {
    let trimmed = value.trim();
    if trimmed.is_empty() || trimmed == "-" {
        return Err(ParseError(format!("Empty {} value", base.lower())));
    }

    // Peel off an optional leading '-' so negative literals parse correctly.
    let (negative, body) = match trimmed.strip_prefix('-') {
        Some(rest) => (true, rest),
        None => (false, trimmed),
    };

    // 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 an uppercase mantissa like "0xFF" valid.
    let lowered = body.to_ascii_lowercase();
    let mut digits = lowered.as_str();
    for prefix in ["0x", "0b", "0o"] {
        if let Some(rest) = digits.strip_prefix(prefix) {
            digits = rest;
        }
    }

    if digits.is_empty() {
        return Err(ParseError(format!("Empty {} value", base.lower())));
    }

    let allowed = base.digits();
    let radix = base.radix() as i128;
    let mut acc: i128 = 0;
    for ch in digits.chars() {
        let digit = match allowed.find(ch) {
            Some(i) => i as i128,
            None => {
                return Err(ParseError(format!(
                    "Invalid digit '{}' for base {}",
                    ch, base.lower()
                )))
            }
        };
        // Checked arithmetic: an over-wide literal reports a clean error
        // instead of panicking on overflow.
        acc = acc
            .checked_mul(radix)
            .and_then(|v| v.checked_add(digit))
            .ok_or_else(|| ParseError(format!("Value out of range for base {}", base.lower())))?;
    }

    Ok(if negative { -acc } else { acc })
}

/// Mask for a `width`-bit field: 2^width - 1. (Valid for width <= 127.)
pub fn mask(width: Width) -> u128 {
    (1u128 << width) - 1
}

/// Normalize any signed value to its unsigned width-bit two's-complement value,
/// i.e. into the half-open range [0, 2^width). The double-modulo form
/// `((n % m) + m) % m` maps negative dividends into the canonical positive
/// bucket (Rust's `%` is truncated, so the add + second mod is required).
pub fn normalize(n: i128, width: Width) -> u128 {
    let m: i128 = 1 << width; // 2^width (fits i128 for width <= 126)
    (((n % m) + m) % m) as u128
}

/// Format `n` in `base`, zero-padded to at least `width` digits. Negatives
/// carry a leading '-' and format their magnitude. (The third arg is a minimum
/// digit count — the binary rendering of a width-bit value needs exactly
/// `width` digits.)
pub fn format(n: i128, base: Base, width: usize) -> String {
    if n < 0 {
        // Format the magnitude and prepend the sign.
        return format!("-{}", format(-n, base, width));
    }
    let alphabet = base.digits();
    let radix = base.radix() as u128;
    let v = n as u128;

    // Manual base conversion keeps us stdlib-only and yields lowercase digits
    // matching the TS `bigint.toString(radix)` output.
    let mut bytes: Vec<u8> = if v == 0 {
        vec![b'0']
    } else {
        let mut buf = Vec::new();
        let mut t = v;
        while t > 0 {
            let d = (t % radix) as usize;
            buf.push(alphabet.as_bytes()[d]);
            t /= radix;
        }
        buf.reverse();
        buf
    };
    while bytes.len() < width {
        bytes.insert(0, b'0');
    }
    String::from_utf8(bytes).unwrap()
}

/// Apply a width-bit operation. `a` is the (unary) operand for `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 `width`:
/// every significant bit is shifted out, so the masked result is zero. This
/// also avoids shifting a u128 by >= 128 (undefined behavior in Rust).
pub fn bitwise(op: Op, a: i128, b: i128, width: Width) -> u128 {
    let m = mask(width);
    let x = normalize(a, width);
    let y = normalize(b, width);
    let w = width as u128;

    match op {
        Op::And => x & y,
        Op::Or => x | y,
        Op::Xor => x ^ y,
        Op::Not => (!x) & m,
        Op::Shl => {
            if y >= w {
                0
            } else {
                (x << y) & m
            }
        }
        Op::Shr => {
            // x is normalized non-negative → logical (zero-filling) shift.
            if y >= w {
                0
            } else {
                x >> y
            }
        }
        Op::Rol | Op::Ror => {
            let shift = y % w; // rotate amount wraps within width
            if shift == 0 {
                return x;
            }
            // A right-rotate by `shift` is a left-rotate by (width - shift).
            let s = if op == Op::Rol { shift } else { w - shift };
            ((x << s) | (x >> (w - s))) & m
        }
    }
}

/// Fixed-width binary string of `width` bits (MSB first).
pub fn to_bits(n: i128, width: Width) -> String {
    format(normalize(n, width) as i128, Base::Bin, width as usize)
}

/// Indices of set bits (LSB = index 0), normalized to `width`. Stops at the
/// highest set bit.
pub fn flags(n: i128, width: Width) -> Vec<u32> {
    let mut v = normalize(n, width);
    let mut out = Vec::new();
    let mut i = 0u32;
    while v > 0 {
        if v & 1 != 0 {
            out.push(i);
        }
        v >>= 1;
        i += 1;
    }
    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 →