Skip to content

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

// =============================================================================
//  bitwise.cpp — CosmoDev polyglot showcase port of the `bitwise` tool
//  -----------------------------------------------------------------------------
//  Language : C++ (C++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.
//
//  C++ note: the TS source uses arbitrary-precision `bigint`. The C++ standard
//  library has no bignum type, so values live in std::int64_t/std::uint64_t.
//  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 shift/rotate intermediates pre-mask the operand so
//  nothing exceeds 64 bits — arithmetically identical to the reference's
//  masked bigint shifts. Only literals beyond 2^64-1 diverge from the TS lib
//  (thrown as a clean out_of_range), far outside any realistic input.
//
//  The enum spellings `and_`/`or_`/`xor_`/`not_` carry a trailing underscore
//  because `and`, `or`, `xor`, `not` are alternative operator tokens in C++.
// =============================================================================

#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>

namespace bitwise {

/// Numeric base for parsing and formatting bit patterns.
enum class base { bin, oct, dec, hex };

/// Supported bitwise operation. `not_` is unary on `a`; the binary ops take
/// `a` and `b`; for the shift/rotate ops `b` is the count.
enum class op { and_, or_, xor_, not_, shl, shr, rol, ror };

/// Operating field width, in bits: one of 8, 16, 32, 64.
using width = std::uint32_t;

/// Positional radix (2/8/10/16) consumed by the parser/formatter.
inline std::uint32_t radix(base b) {
    switch (b) {
    case base::bin: return 2;
    case base::oct: return 8;
    case base::dec: return 10;
    case base::hex: return 16;
    }
    return 10;
}

/// Lowercase digit alphabet valid for each base — drives validation and the
/// per-character digit-value lookup.
inline const char* digits(base b) {
    switch (b) {
    case base::bin: return "01";
    case base::oct: return "01234567";
    case base::dec: return "0123456789";
    case base::hex: return "0123456789abcdef";
    }
    return "0123456789";
}

/// Lowercase name, for error messages that match the TS lib.
inline const char* lower(base b) {
    switch (b) {
    case base::bin: return "bin";
    case base::oct: return "oct";
    case base::dec: return "dec";
    case base::hex: return "hex";
    }
    return "dec";
}

/// Lowercase ASCII copy of `s` — keeps an uppercase mantissa like "0xFF"
/// valid, mirroring the reference's lowered-string prefix handling.
inline std::string lowered(const std::string& s) {
    std::string out(s);
    std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) {
        return static_cast<char>(std::tolower(c));
    });
    return out;
}

/// Parse a numeric string in `base` into a signed 64-bit integer. Strips an
/// optional leading sign and the 0x/0b/0o prefixes (case-insensitive). Throws
/// std::invalid_argument on empty input or any digit invalid for the requested
/// base, and std::out_of_range on 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 std::uint64_t 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.
inline std::int64_t parse(const std::string& value, base b) {
    // Trim ASCII whitespace from both ends.
    const char* ws = " \t\n\r";
    size_t begin = value.find_first_not_of(ws);
    if (begin == std::string::npos)
        throw std::invalid_argument("Empty " + std::string(lower(b)) + " value");
    size_t end = value.find_last_not_of(ws);
    std::string trimmed = value.substr(begin, end - begin + 1);

    // Peel off an optional leading '-' so negative literals parse correctly.
    bool negative = false;
    if (trimmed.front() == '-') {
        negative = true;
        trimmed.erase(0, 1);
    }
    if (trimmed.empty())
        throw std::invalid_argument("Empty " + std::string(lower(b)) + " value");

    // Strip each base prefix in turn (0x, then 0b, then 0o) from a lowered
    // copy — mirrors the reference's chained leading-prefix removal.
    std::string body = lowered(trimmed);
    for (const char* prefix : {"0x", "0b", "0o"})
        if (body.rfind(prefix, 0) == 0)
            body.erase(0, 2);
    if (body.empty())
        throw std::invalid_argument("Empty " + std::string(lower(b)) + " value");

    // Horner's method over the digit alphabet, with a checked accumulate:
    // an over-wide literal reports a clean error instead of wrapping.
    const char* alphabet = digits(b);
    std::uint64_t r = radix(b);
    std::uint64_t acc = 0;
    for (char c : body) {
        const char* hit = std::strchr(alphabet, c);
        if (hit == nullptr)
            throw std::invalid_argument(std::string("Invalid digit '") + c +
                                        "' for base " + lower(b));
        std::uint64_t digit = static_cast<std::uint64_t>(hit - alphabet);
        if (acc > (UINT64_MAX - digit) / r)
            throw std::out_of_range("Value out of range for base " +
                                    std::string(lower(b)));
        acc = acc * r + digit;
    }

    // Wrapping negate is exact mod 2^64 (all target platforms are two's
    // complement); the cast reinterprets the pattern as signed.
    return negative ? static_cast<std::int64_t>(UINT64_C(0) - acc)
                    : static_cast<std::int64_t>(acc);
}

/// Bitmask for a `w`-bit field: 2^w - 1. Width 64 is spelled directly
/// because 1 << 64 would overflow std::uint64_t.
inline std::uint64_t mask(width w) {
    return w == 64 ? UINT64_MAX : ((UINT64_C(1) << w) - 1);
}

/// Normalize any signed value to its unsigned w-bit two's-complement value,
/// i.e. into the half-open range [0, 2^w) — e.g. -1 at width 8 yields 255.
/// The reference computes ((n % m) + m) % m with m = 2^w on arbitrary-precision
/// ints; casting to std::uint64_t already yields n mod 2^64 (two's-complement
/// truncation), and ANDing with the width-bit mask reduces that to n mod 2^w —
/// the same canonical value, without materializing 2^64 itself.
inline std::uint64_t normalize(std::int64_t n, width w) {
    return static_cast<std::uint64_t>(n) & mask(w);
}

/// Render an unsigned value in `b`, zero-padded to at least `min_digits`
/// places. Manual base conversion keeps the port stdlib-only and yields the
/// lowercase digits of the TS `bigint.toString(radix)` output.
inline std::string format_u64(std::uint64_t v, base b, size_t min_digits) {
    static const char alphabet[] = "0123456789abcdef";
    std::uint64_t r = radix(b);

    // Extract digits LSB-first; the do/while renders "0" for zero naturally.
    std::string out;
    do {
        out.push_back(alphabet[v % r]);
        v /= r;
    } while (v != 0);
    if (out.size() < min_digits)
        out.insert(0, min_digits - out.size(), '0'); // zero-pad up to the minimum
    std::reverse(out.begin(), out.end());
    return out;
}

/// Render `n` in `b`, zero-padded to at least `min_digits` places. Negative
/// values carry a leading '-' and format their magnitude (computed with
/// wrapping subtraction — exact and INT64_MIN-safe). `min_digits` corresponds
/// to the `width` parameter of the TS reference (a width-bit binary value
/// needs exactly `width` digits).
inline std::string format(std::int64_t n, base b, size_t min_digits) {
    if (n < 0) {
        std::uint64_t mag = UINT64_C(0) - static_cast<std::uint64_t>(n);
        return "-" + format_u64(mag, b, min_digits);
    }
    return format_u64(static_cast<std::uint64_t>(n), b, min_digits);
}

/// Apply a w-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 w-bit two's complement first; the
/// result is masked to `w` bits.
///
/// For shl/shr we short-circuit when the shift count meets or exceeds `w`:
/// every significant bit is shifted out, so the masked result is zero (this
/// also keeps every shift amount below 64 — C++ leaves shifts >= 64
/// undefined). For shl/rol the shifted operand is pre-masked with
/// (mask >> count): the low `w` bits of the true bigint shift are identical,
/// but the intermediate can no longer exceed 64 bits.
inline std::uint64_t bitwise(op o, std::int64_t a, std::int64_t b, width w) {
    std::uint64_t m = mask(w);
    std::uint64_t x = normalize(a, w);
    std::uint64_t y = normalize(b, w);

    switch (o) {
    case op::and_:
        return x & y;
    case op::or_:
        return x | y;
    case op::xor_:
        return x ^ y;
    case op::not_:
        return ~x & m;
    case op::shl:
        if (y >= w)
            return 0;
        return (x & (m >> y)) << y; // == (x << y) & m, without overflow
    case op::shr:
        // x is normalized non-negative → logical (zero-filling) shift.
        return y >= w ? 0 : x >> y;
    case op::rol:
    case op::ror: {
        std::uint64_t shift = y % w; // rotate amount wraps within width
        if (shift == 0)
            return x;
        // A right-rotate by `shift` is a left-rotate by (w - shift).
        std::uint64_t s = (o == op::rol) ? shift : static_cast<std::uint64_t>(w) - shift;
        return ((x & (m >> s)) << s) | (x >> (w - s));
    }
    }
    return 0; // unreachable with a valid op
}

/// Fixed-width binary string of `w` bits (MSB first). Formats the unsigned
/// normalized pattern directly: a width-64 pattern can exceed INT64_MAX and
/// must not round-trip through the signed path.
inline std::string to_bits(std::int64_t n, width w) {
    return format_u64(normalize(n, w), base::bin, w);
}

/// Indices of set bits (LSB = index 0), normalized to `w`, in ascending
/// order. The loop stops at the highest set bit.
inline std::vector<std::uint32_t> flags(std::int64_t n, width w) {
    std::uint64_t v = normalize(n, w);
    std::vector<std::uint32_t> out;
    std::uint32_t i = 0;
    while (v != 0) {
        if (v & 1)
            out.push_back(i);
        v >>= 1;
        ++i;
    }
    return out;
}

} // namespace bitwise

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 →