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.c — CosmoDev polyglot showcase port of the `bitwise` tool
 *  -----------------------------------------------------------------------------
 *  Language : C (C11, 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`. C11 has no bignum
 *  type, so values live in int64_t/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 (reported as a clean parse
 *  error), far outside any realistic bitwise-calculator input.
 * ========================================================================== */

#include <ctype.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* Numeric base for parsing and formatting bit patterns. */
typedef enum {
    BITWISE_BIN = 0,
    BITWISE_OCT,
    BITWISE_DEC,
    BITWISE_HEX
} bitwise_base;

/* Supported bitwise operation. `not` is unary on `a`; the binary ops take `a`
 * and `b`; for the shift/rotate ops `b` is the count. */
typedef enum {
    BITWISE_AND = 0,
    BITWISE_OR,
    BITWISE_XOR,
    BITWISE_NOT,
    BITWISE_SHL,
    BITWISE_SHR,
    BITWISE_ROL,
    BITWISE_ROR
} bitwise_op;

/* Operating field width, in bits: one of 8, 16, 32, 64. */
typedef uint32_t bitwise_width;

/* Positional radix (2/8/10/16) consumed by the parser/formatter. */
static uint32_t bitwise_radix(bitwise_base base) {
    switch (base) {
    case BITWISE_BIN: return 2;
    case BITWISE_OCT: return 8;
    case BITWISE_DEC: return 10;
    case BITWISE_HEX: return 16;
    }
    return 10;
}

/* Lowercase digit alphabet valid for each base — drives validation and the
 * per-character digit-value lookup. */
static const char *bitwise_digits(bitwise_base base) {
    switch (base) {
    case BITWISE_BIN: return "01";
    case BITWISE_OCT: return "01234567";
    case BITWISE_DEC: return "0123456789";
    case BITWISE_HEX: return "0123456789abcdef";
    }
    return "0123456789";
}

/* Lowercase name, for error messages that match the TS lib. */
static const char *bitwise_base_lower(bitwise_base base) {
    switch (base) {
    case BITWISE_BIN: return "bin";
    case BITWISE_OCT: return "oct";
    case BITWISE_DEC: return "dec";
    case BITWISE_HEX: return "hex";
    }
    return "dec";
}

/* Store a diagnostic message into the caller's error buffer (best-effort). */
static void bitwise_set_err(char *err, size_t err_len, const char *fmt, ...) {
    if (err == NULL || err_len == 0)
        return;
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(err, err_len, fmt, ap);
    va_end(ap);
}

/*
 * Parse a numeric string in `base` into a signed 64-bit integer, storing it in
 * *out. Accepts an optional leading sign and a single optional base prefix
 * (0x/0b/0o, case-insensitive). Returns 0 on success, or -1 with a message in
 * `err` (never NULL; may be NULL to skip diagnostics) on empty input, any digit
 * invalid for the requested base, 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 bitwise_normalize() / bitwise_bitwise().
 *
 * Magnitudes are accumulated as uint64_t and negated 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.
 */
int bitwise_parse(const char *value, bitwise_base base, int64_t *out,
                  char *err, size_t err_len) {
    *out = 0;

    /* Trim ASCII whitespace from both ends. */
    while (*value == ' ' || *value == '\t' || *value == '\n' || *value == '\r')
        value++;
    size_t len = strlen(value);
    while (len > 0 && (value[len - 1] == ' ' || value[len - 1] == '\t' ||
                       value[len - 1] == '\n' || value[len - 1] == '\r'))
        len--;

    /* Peel off an optional leading '-' so negative literals parse correctly. */
    int negative = 0;
    if (len > 0 && value[0] == '-') {
        negative = 1;
        value++;
        len--;
    }
    if (len == 0) {
        bitwise_set_err(err, err_len, "Empty %s value", bitwise_base_lower(base));
        return -1;
    }

    /* Lowercase copy: keeps an uppercase mantissa like "0xFF" valid, mirroring
     * the reference's chained prefix removal over a lowered string. Any string
     * longer than this buffer cannot fit in 64 bits anyway. */
    char buf[128];
    if (len >= sizeof buf) {
        bitwise_set_err(err, err_len, "Value out of range for base %s",
                        bitwise_base_lower(base));
        return -1;
    }
    for (size_t i = 0; i < len; i++)
        buf[i] = (char)tolower((unsigned char)value[i]);
    buf[len] = '\0';

    /* Strip each base prefix in turn (0x, then 0b, then 0o) — mirrors the
     * reference's chained leading-prefix removal. */
    static const char *const prefixes[] = {"0x", "0b", "0o"};
    for (size_t i = 0; i < sizeof prefixes / sizeof prefixes[0]; i++) {
        size_t plen = strlen(prefixes[i]);
        if (strncmp(buf, prefixes[i], plen) == 0)
            memmove(buf, buf + plen, strlen(buf) + 1 - plen);
    }
    if (buf[0] == '\0') {
        bitwise_set_err(err, err_len, "Empty %s value", bitwise_base_lower(base));
        return -1;
    }

    /* 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 = bitwise_digits(base);
    uint32_t radix = bitwise_radix(base);
    uint64_t acc = 0;
    for (const char *p = buf; *p != '\0'; p++) {
        const char *hit = strchr(alphabet, *p);
        if (hit == NULL) {
            bitwise_set_err(err, err_len, "Invalid digit '%c' for base %s",
                            *p, bitwise_base_lower(base));
            return -1;
        }
        uint64_t digit = (uint64_t)(hit - alphabet);
        if (acc > (UINT64_MAX - digit) / radix) {
            bitwise_set_err(err, err_len, "Value out of range for base %s",
                            bitwise_base_lower(base));
            return -1;
        }
        acc = acc * radix + digit;
    }

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

/*
 * Bitmask for a `width`-bit field: 2^width - 1. Width 64 is spelled directly
 * because 1 << 64 would overflow uint64_t.
 */
uint64_t bitwise_mask(bitwise_width width) {
    return width == 64 ? UINT64_MAX : ((UINT64_C(1) << 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) — e.g. -1 at width 8 yields 255.
 * The reference computes ((n % m) + m) % m with m = 2^width on arbitrary-
 * precision ints; casting to 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^width — the same canonical value, without materializing 2^64 itself.
 */
uint64_t bitwise_normalize(int64_t n, bitwise_width width) {
    return (uint64_t)n & bitwise_mask(width);
}

/*
 * Render an unsigned value in `base`, zero-padded to at least `min_digits`
 * places, into `buf`. Returns 0 on success, -1 if the buffer is too small
 * (a width-64 binary value needs 65 bytes including the NUL).
 * Manual base conversion keeps the port stdlib-only and yields the lowercase
 * digits of the TS `bigint.toString(radix)` output.
 */
static int bitwise_format_u64(uint64_t v, bitwise_base base, size_t min_digits,
                              char *buf, size_t buf_size) {
    static const char alphabet[] = "0123456789abcdef";
    uint32_t radix = bitwise_radix(base);

    /* Extract digits LSB-first; the do/while renders "0" for zero naturally. */
    char tmp[64];
    size_t nd = 0;
    do {
        tmp[nd++] = alphabet[v % radix];
        v /= radix;
    } while (v != 0);

    size_t digits = nd > min_digits ? nd : min_digits;
    if (digits + 1 > buf_size)
        return -1;

    char *w = buf;
    for (size_t i = nd; i < digits; i++)
        *w++ = '0'; /* zero-pad up to the minimum width */
    while (nd > 0)
        *w++ = tmp[--nd];
    *w = '\0';
    return 0;
}

/*
 * Render `n` in `base`, zero-padded to at least `min_digits` places, into
 * `buf`. 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).
 */
int bitwise_format(int64_t n, bitwise_base base, size_t min_digits,
                   char *buf, size_t buf_size) {
    if (n < 0) {
        uint64_t mag = (uint64_t)0 - (uint64_t)n;
        char tmp[65]; /* 64 digits + NUL */
        if (bitwise_format_u64(mag, base, min_digits, tmp, sizeof tmp) != 0)
            return -1;
        size_t len = strlen(tmp);
        if (len + 2 > buf_size) /* '-' + digits + NUL */
            return -1;
        buf[0] = '-';
        memcpy(buf + 1, tmp, len + 1);
        return 0;
    }
    return bitwise_format_u64((uint64_t)n, base, min_digits, buf, buf_size);
}

/*
 * 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 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
 * `width` bits of the true bigint shift are identical, but the intermediate
 * can no longer exceed 64 bits.
 */
uint64_t bitwise_bitwise(bitwise_op op, int64_t a, int64_t b, bitwise_width width) {
    uint64_t m = bitwise_mask(width);
    uint64_t x = bitwise_normalize(a, width);
    uint64_t y = bitwise_normalize(b, width);

    switch (op) {
    case BITWISE_AND:
        return x & y;
    case BITWISE_OR:
        return x | y;
    case BITWISE_XOR:
        return x ^ y;
    case BITWISE_NOT:
        return ~x & m;
    case BITWISE_SHL:
        if (y >= width)
            return 0;
        return (x & (m >> y)) << y; /* == (x << y) & m, without overflow */
    case BITWISE_SHR:
        /* x is normalized non-negative → logical (zero-filling) shift. */
        return y >= width ? 0 : x >> y;
    case BITWISE_ROL:
    case BITWISE_ROR: {
        uint64_t shift = y % width; /* rotate amount wraps within width */
        if (shift == 0)
            return x;
        /* A right-rotate by `shift` is a left-rotate by (width - shift). */
        uint64_t s = (op == BITWISE_ROL) ? shift : (uint64_t)width - shift;
        return ((x & (m >> s)) << s) | (x >> (width - s));
    }
    }
    return 0; /* unreachable with a valid op */
}

/*
 * Fixed-width binary string of `width` bits (MSB first), into `buf`.
 * Formats the unsigned normalized pattern directly: a width-64 pattern can
 * exceed INT64_MAX and must not round-trip through the signed path.
 */
int bitwise_to_bits(int64_t n, bitwise_width width, char *buf, size_t buf_size) {
    return bitwise_format_u64(bitwise_normalize(n, width), BITWISE_BIN, width,
                              buf, buf_size);
}

/*
 * Indices of set bits (LSB = index 0), normalized to `width`, written to `out`
 * in ascending order. `out` must have room for `width` entries; returns the
 * number of indices written (the loop stops at the highest set bit, so at
 * most `width` — and usually far fewer — are written).
 */
size_t bitwise_flags(int64_t n, bitwise_width width, uint32_t *out, size_t cap) {
    uint64_t v = bitwise_normalize(n, width);
    size_t count = 0;
    uint32_t i = 0;
    while (v != 0) {
        if (v & 1) {
            if (count < cap)
                out[count] = i;
            count++;
        }
        v >>= 1;
        i++;
    }
    return count;
}

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 →