Number Base Converter — Zig source
Convert numbers between binary, octal, decimal and hexadecimal. BigInt-powered, so it handles arbitrarily large values without precision loss.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
// number-base — Zig port: arbitrary-precision base conversion (bases 2-36).
const std = @import("std");
const Big = std.math.big.int.Managed;
const digits = "0123456789abcdefghijklmnopqrstuvwxyz";
/// Map a lowercase byte to its value; null when not a digit.
fn digitValue(c: u8) ?u8 {
return switch (c) {
'0'...'9' => c - '0',
'a'...'z' => c - 'a' + 10,
else => null,
};
}
fn parseInner(a: std.mem.Allocator, t_in: []const u8, base: u8, neg: bool) !Big {
var v = try Big.initSet(a, 0);
errdefer v.deinit();
var b = try Big.initSet(a, base);
defer b.deinit();
for (t_in) |raw| {
const d = digitValue(std.ascii.toLower(raw)) orelse return error.InvalidDigit;
if (d >= base) return error.InvalidDigit;
try v.mul(&v, &b); // Horner's method
try v.addScalar(&v, d);
}
if (neg) try v.negate(&v);
return v;
}
/// Parse `s` as an integer written in `base` (2-36); the caller owns the result.
/// null on bad base, empty input, or invalid digit; a radix prefix is stripped
/// only when it agrees with the base.
pub fn parse(a: std.mem.Allocator, s: []const u8, base: u8) !?Big {
if (base < 2 or base > 36) return null;
var t = std.mem.trim(u8, s, " \t\r\n");
var neg = false;
if (t.len > 0 and (t[0] == '-' or t[0] == '+')) {
neg = t[0] == '-';
t = t[1..];
}
if (t.len >= 2 and t[0] == '0') {
const p = std.ascii.toLower(t[1]);
if ((p == 'x' and base == 16) or (p == 'b' and base == 2) or (p == 'o' and base == 8))
t = t[2..];
}
if (t.len == 0) return null;
return parseInner(a, t, base, neg) catch null;
}
/// Render `v` as a lowercase string in `base` (2-36); "" for an unsupported
/// base. The sign of zero is never emitted.
pub fn format(a: std.mem.Allocator, v_in: Big, base: u8) ![]u8 {
if (base < 2 or base > 36) return a.dupe(u8, "");
var v = try v_in.clone();
defer v.deinit();
if (v.toConst().eqlZero()) return a.dupe(u8, "0");
const zero = try Big.initSet(a, 0);
defer zero.deinit();
const neg = v.toConst().order(zero.toConst()) == .lt;
if (neg) try v.negate(&v);
var b = try Big.initSet(a, base);
defer b.deinit();
var q = try Big.init(a);
defer q.deinit();
var r = try Big.init(a);
defer r.deinit();
var out = std.ArrayList(u8).init(a);
errdefer out.deinit();
while (!v.toConst().eqlZero()) {
try v.divFloor(&q, &r, &v, &b); // peel the least-significant digit
std.mem.swap(Big, &v, &q);
const d = r.toConst().to(u8) catch unreachable; // r < base <= 36
try out.append(digits[d]);
}
if (neg) try out.append('-');
std.mem.reverse(u8, out.items);
return out.toOwnedSlice();
}
/// value(from_base) -> to_base; null when it does not parse.
pub fn convertBase(a: std.mem.Allocator, value: []const u8, from: u8, to: u8) !?[]u8 {
var n = (try parse(a, value, from)) orelse return null;
defer n.deinit();
return try format(a, n, to);
}
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 →