Skip to content

Data Unit Converter — Zig source

Convert between digital data units - B, KB/KiB, MB/MiB, GB/GiB, TB/TiB. Switch between decimal (1000) and binary (1024) bases, runs entirely in your browser.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// =============================================================================
//  data-unit-converter.zig — CosmoDev polyglot showcase port of the
//  `data-unit-converter` tool
//  -----------------------------------------------------------------------------
//  Language : Zig (0.13, standard library only)
//  Source:   ported from src/lib/dataUnits.ts (the canonical, live TypeScript
//             lib); mirrors src/tool-sources/data-unit-converter/{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 digital-data unit conversion. Convert between B, KB/KiB, MB/MiB,
//  GB/GiB, TB/TiB: the decimal base 1000 pairs with KB/MB/GB/TB (SI) and the
//  binary base 1024 with KiB/MiB/GiB/TiB (IEC). Deterministic and
//  side-effect free; convertData returns NaN for non-finite input, unknown
//  units, or an invalid base.
// =============================================================================

const std = @import("std");

/// Supported conversion radix: decimal (`.decimal = 1000`, SI units) or binary
/// (`.binary = 1024`, IEC units).
pub const Base = enum(u32) {
    decimal = 1000,
    binary = 1024,

    /// Numeric radix as an f64, ready for `std.math.pow`.
    pub fn radix(self: Base) f64 {
        return @floatFromInt(@intFromEnum(self));
    }
};

/// Recognized digital-data units. `kilo/mega/giga/tera` are decimal (1000);
/// `kibi/mebi/gibi/tebi` are binary (1024); `byte` is shared by both.
pub const Unit = enum {
    byte,
    kilobyte,
    megabyte,
    gigabyte,
    terabyte,
    kibibyte,
    mebibyte,
    gibibyte,
    tebibyte,

    /// Symbol string used in the UI and persisted in shareable URLs.
    pub fn symbol(self: Unit) []const u8 {
        return switch (self) {
            .byte => "B",
            .kilobyte => "KB",
            .megabyte => "MB",
            .gigabyte => "GB",
            .terabyte => "TB",
            .kibibyte => "KiB",
            .mebibyte => "MiB",
            .gibibyte => "GiB",
            .tebibyte => "TiB",
        };
    }

    /// Power-of-base rank: `byte` is rank 0 and each prefix step adds 1. KB
    /// and KiB are both rank 1 — the unit picks the rank, the base decides
    /// whether a rank means ×1000 or ×1024.
    pub fn exponent(self: Unit) i32 {
        return switch (self) {
            .byte => 0,
            .kilobyte, .kibibyte => 1,
            .megabyte, .mebibyte => 2,
            .gigabyte, .gibibyte => 3,
            .terabyte, .tebibyte => 4,
        };
    }

    /// Parse a symbol back into a Unit, mirroring the TS dictionary lookup.
    /// Returns null for unknown symbols (the TS side returns NaN downstream).
    pub fn fromSymbol(s: []const u8) ?Unit {
        if (std.mem.eql(u8, s, "B")) return .byte;
        if (std.mem.eql(u8, s, "KB")) return .kilobyte;
        if (std.mem.eql(u8, s, "MB")) return .megabyte;
        if (std.mem.eql(u8, s, "GB")) return .gigabyte;
        if (std.mem.eql(u8, s, "TB")) return .terabyte;
        if (std.mem.eql(u8, s, "KiB")) return .kibibyte;
        if (std.mem.eql(u8, s, "MiB")) return .mebibyte;
        if (std.mem.eql(u8, s, "GiB")) return .gibibyte;
        if (std.mem.eql(u8, s, "TiB")) return .tebibyte;
        return null;
    }
};

/// Units available under each base, ordered smallest (byte) to largest. The UI
/// uses these to populate the per-base unit pickers.
pub fn unitsFor(base: Base) *const [5]Unit {
    return switch (base) {
        .decimal => &.{ .byte, .kilobyte, .megabyte, .gigabyte, .terabyte },
        .binary => &.{ .byte, .kibibyte, .mebibyte, .gibibyte, .tebibyte },
    };
}

/// Convert a value between two digital-data units under the given base.
///
/// Strategy: reduce to bytes via `value × base^fromExp`, then divide by
/// `base^toExp`. Non-finite values (Inf/NaN) return NaN, mirroring the
/// TypeScript contract. `Base` and `Unit` are already constrained by their
/// enum types; the symbol-based entry point below preserves the original
/// "unknown unit → NaN" behavior for string inputs.
pub fn convertData(value: f64, from: Unit, to: Unit, base: Base) f64 {
    // Reject Inf/NaN early so they don't propagate through the multiplication.
    if (!std.math.isFinite(value)) return std.math.nan(f64);

    const radix = base.radix();
    return (value * std.math.pow(f64, radix, @floatFromInt(from.exponent()))) /
        std.math.pow(f64, radix, @floatFromInt(to.exponent()));
}

/// String-in / NaN-out entry point that mirrors the TypeScript signature
/// (`convertData(value, fromUnit, toUnit, base)`) for unknown-symbol handling.
/// Returns NaN for non-finite input, unrecognized symbols, or a non-canonical
/// base value.
pub fn convertDataSymbols(value: f64, from: []const u8, to: []const u8, base: i64) f64 {
    if (!std.math.isFinite(value)) return std.math.nan(f64);
    // Only the two canonical bases are valid; anything else is a caller bug.
    const radix: Base = switch (base) {
        1000 => .decimal,
        1024 => .binary,
        else => return std.math.nan(f64),
    };
    // Unknown unit symbols short-circuit to NaN, like the TS dictionary miss.
    const from_unit = Unit.fromSymbol(from) orelse return std.math.nan(f64);
    const to_unit = Unit.fromSymbol(to) orelse return std.math.nan(f64);
    return convertData(value, from_unit, to_unit, radix);
}

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 →