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.cs — CosmoDev polyglot showcase port of the `bitwise` tool
// -----------------------------------------------------------------------------
// Language : C# 12 (.NET 8, 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`. .NET 8 ships
// Int128/UInt128, so signed parsing uses Int128 and masked results use
// UInt128 — mirroring the Rust port's i128/u128 approach. This is exact for
// every supported width (8/16/32/64) and all shift intermediates. Only
// literals beyond ~1.7e38 diverge from the TS lib (thrown as a clean
// out-of-range), far outside any realistic bitwise-calculator input.
//
// `BitwiseOps.Apply` is the port of the reference's `bitwise()` function (the
// name is unavailable here — the containing class holds it).
// =============================================================================
namespace CosmoDev.Polyglot.Bitwise;
/// <summary>Numeric radix used for parsing and formatting.</summary>
public enum Base
{
Bin,
Oct,
Dec,
Hex,
}
/// <summary>Bit-width of the virtual register (8/16/32/64).</summary>
public enum Width
{
W8 = 8,
W16 = 16,
W32 = 32,
W64 = 64,
}
/// <summary>
/// Supported bitwise operation. <see cref="Not"/> is unary on
/// <c>a</c>; the rest are binary, with <c>b</c> as the shift/rotate count for
/// the shift/rotate ops.
/// </summary>
public enum Op
{
And,
Or,
Xor,
Not,
Shl,
Shr,
Rol,
Ror,
}
public static class BitwiseOps
{
private static readonly string[] BaseNames = { "bin", "oct", "dec", "hex" };
private static readonly int[] BaseRadixes = { 2, 8, 10, 16 };
private static readonly string[] BaseDigits = { "01", "01234567", "0123456789", "0123456789abcdef" };
/// <summary>Positional radix (2/8/10/16).</summary>
private static int Radix(this Base b) => BaseRadixes[(int)b];
/// <summary>Lowercase digit alphabet valid for this base.</summary>
private static string Digits(this Base b) => BaseDigits[(int)b];
/// <summary>Lowercase name, for error messages that match the TS lib.</summary>
private static string Lower(this Base b) => BaseNames[(int)b];
private static int WidthOf(this Width w) => (int)w;
/// <summary>
/// Parse a numeric string in <paramref name="b"/> into a signed integer.
/// Strips 0x/0b/0o prefixes and an optional leading sign. Throws
/// <see cref="ArgumentException"/> on empty input, any out-of-base digit,
/// or a literal that overflows Int128. The raw signed value is returned
/// (no width normalization); callers fold it into a field via
/// <see cref="Normalize"/> / <see cref="Apply"/>.
/// </summary>
public static Int128 Parse(string value, Base b)
{
string trimmed = value.Trim();
if (trimmed.Length == 0 || trimmed == "-")
throw new ArgumentException($"Empty {b.Lower()} value", nameof(value));
// Peel off an optional leading '-' so negative literals parse correctly.
bool negative = trimmed[0] == '-';
string body = negative ? trimmed[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.ToLowerInvariant();
foreach (string prefix in new[] { "0x", "0b", "0o" })
if (lowered.StartsWith(prefix, StringComparison.Ordinal))
lowered = lowered[prefix.Length..];
if (lowered.Length == 0)
throw new ArgumentException($"Empty {b.Lower()} value", nameof(value));
string allowed = b.Digits();
int radix = b.Radix();
Int128 acc = 0;
foreach (char ch in lowered)
{
int digit = allowed.IndexOf(ch);
if (digit < 0)
throw new ArgumentException($"Invalid digit '{ch}' for base {b.Lower()}", nameof(value));
// Checked arithmetic: an over-wide literal reports a clean error
// instead of wrapping.
try
{
acc = checked(acc * radix + digit);
}
catch (OverflowException)
{
throw new ArgumentException($"Value out of range for base {b.Lower()}", nameof(value));
}
}
return negative ? -acc : acc;
}
/// <summary>Mask for a <paramref name="w"/>-bit field: 2^w - 1.</summary>
public static UInt128 Mask(Width w) => (UInt128.One << w.WidthOf()) - 1;
/// <summary>
/// Normalize any signed value to its unsigned w-bit two's-complement
/// value, i.e. into the half-open range [0, 2^w). The double-modulo form
/// <c>((n % m) + m) % m</c> maps negative dividends into the canonical
/// positive bucket (C#'s <c>%</c> is truncated, so the add + second mod
/// is required) — e.g. -1 at width 8 yields 255.
/// </summary>
public static UInt128 Normalize(Int128 n, Width w)
{
Int128 m = (Int128)1 << w.WidthOf(); // 2^w
return (UInt128)(((n % m) + m) % m);
}
/// <summary>
/// Render <paramref name="n"/> in <paramref name="b"/>, zero-padded to at
/// least <paramref name="minDigits"/> 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
/// <c>width</c> digits.)
/// </summary>
public static string Format(Int128 n, Base b, int minDigits)
{
if (n < 0)
{
// Format the magnitude and prepend the sign. The (-(n+1))+1 form
// negates safely even at Int128.MinValue.
UInt128 mag = (UInt128)(-(n + 1)) + 1;
return "-" + FormatUnsigned(mag, b, minDigits);
}
return FormatUnsigned((UInt128)n, b, minDigits);
}
// Manual base conversion keeps parity with the sibling ports and yields
// lowercase digits matching the TS `bigint.toString(radix)` output.
private static string FormatUnsigned(UInt128 v, Base b, int minDigits)
{
const string alphabet = "0123456789abcdef";
int radix = b.Radix();
// Extract digits LSB-first; the do/while renders "0" for zero naturally.
var buf = new List<char>();
do
{
buf.Add(alphabet[(int)(v % (UInt128)(uint)radix)]);
v /= (UInt128)(uint)radix;
} while (v != 0);
while (buf.Count < minDigits)
buf.Insert(0, '0'); // zero-pad up to the minimum
buf.Reverse();
return new string(buf.ToArray());
}
/// <summary>
/// Apply a w-bit operation. <paramref name="a"/> is the (unary) operand
/// for <see cref="Op.Not"/>; <paramref name="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 <paramref name="w"/> bits.
/// </summary>
/// <remarks>
/// 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 is the port of the reference's <c>bitwise()</c>.
/// </remarks>
public static UInt128 Apply(Op op, Int128 a, Int128 b, Width w)
{
UInt128 m = Mask(w);
UInt128 x = Normalize(a, w);
UInt128 y = Normalize(b, w);
UInt128 width = (UInt128)w.WidthOf();
switch (op)
{
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:
// Left shift grows; for shift >= width the masked result is 0.
return y >= width ? 0 : (x << (int)y) & m;
case Op.Shr:
// x is normalized non-negative → logical (zero-filling) shift.
return y >= width ? 0 : x >> (int)y;
case Op.Rol:
case Op.Ror:
{
UInt128 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).
UInt128 s = op == Op.Rol ? shift : width - shift;
return ((x << (int)s) | (x >> (int)(width - s))) & m;
}
default:
throw new ArgumentException($"Unknown bitwise operation: {op}");
}
}
/// <summary>Fixed-width binary string of <paramref name="w"/> bits (MSB first).</summary>
public static string ToBits(Int128 n, Width w) =>
Format((Int128)Normalize(n, w), Base.Bin, w.WidthOf());
/// <summary>
/// Indices of set bits (LSB = index 0), normalized to
/// <paramref name="w"/>. Stops at the highest set bit.
/// </summary>
public static List<int> Flags(Int128 n, Width w)
{
UInt128 v = Normalize(n, w);
var outList = new List<int>();
int i = 0;
while (v != 0)
{
if ((v & 1) != 0)
outList.Add(i);
v >>= 1;
i++;
}
return outList;
}
}
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 →