Skip to content

Data Unit Converter — Rust 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 Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.

// data-unit-converter — polyglot showcase port (Rust).
//
// Pure digital-data unit conversion for the Data Unit Converter tool on
// CosmoDev (dev.cosmolabs.org). Ported from the canonical TypeScript source at
// src/lib/dataUnits.ts so the tool page can display the same logic across six
// languages.
//
// Supports B, KB/KiB, MB/MiB, GB/GiB, TB/TiB. Decimal base = 1000 (KB, MB, GB,
// TB); binary base = 1024 (KiB, MiB, GiB, TiB). Pure + deterministic.
// convert_data returns f64::NAN for non-finite input, unknown units, or an
// invalid base.
//
// Self-contained: standard library only, no external dependencies (no serde,
// no external crates).
//
// License/usage: display source — part of CosmoDev's polyglot tool pages.

/// Supported conversion radix: decimal (`Decimal = 1000`, SI units) or binary
/// (`Binary = 1024`, IEC units).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Base {
    Decimal = 1000,
    Binary = 1024,
}

impl Base {
    /// Numeric radix as an f64, ready for `powi`.
    fn radix(self) -> f64 {
        self as i64 as f64
    }
}

/// Recognized digital-data units. `Kilo/Mega/Giga/Tera` are decimal (1000);
/// `Kibi/Mebi/Gibi/Tebi` are binary (1024); `Byte` is shared by both.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unit {
    Byte,
    Kilobyte,
    Megabyte,
    Gigabyte,
    Terabyte,
    Kibibyte,
    Mebibyte,
    Gibibyte,
    Tebibyte,
}

impl Unit {
    /// Symbol string used in the UI and persisted in shareable URLs.
    pub fn symbol(self) -> &'static str {
        match self {
            Unit::Byte => "B",
            Unit::Kilobyte => "KB",
            Unit::Megabyte => "MB",
            Unit::Gigabyte => "GB",
            Unit::Terabyte => "TB",
            Unit::Kibibyte => "KiB",
            Unit::Mebibyte => "MiB",
            Unit::Gibibyte => "GiB",
            Unit::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.
    fn exponent(self) -> i32 {
        match self {
            Unit::Byte => 0,
            Unit::Kilobyte | Unit::Kibibyte => 1,
            Unit::Megabyte | Unit::Mebibyte => 2,
            Unit::Gigabyte | Unit::Gibibyte => 3,
            Unit::Terabyte | Unit::Tebibyte => 4,
        }
    }

    /// Parse a symbol back into a Unit, mirroring the TS dictionary lookup.
    /// Returns None for unknown symbols (the TS side returns NaN downstream).
    pub fn from_symbol(s: &str) -> Option<Unit> {
        match s {
            "B" => Some(Unit::Byte),
            "KB" => Some(Unit::Kilobyte),
            "MB" => Some(Unit::Megabyte),
            "GB" => Some(Unit::Gigabyte),
            "TB" => Some(Unit::Terabyte),
            "KiB" => Some(Unit::Kibibyte),
            "MiB" => Some(Unit::Mebibyte),
            "GiB" => Some(Unit::Gibibyte),
            "TiB" => Some(Unit::Tebibyte),
            _ => None,
        }
    }
}

/// Units available under each base, ordered smallest (Byte) to largest. The UI
/// uses these to populate the per-base unit pickers.
pub fn units_for(base: Base) -> &'static [Unit] {
    match base {
        Base::Decimal => &[
            Unit::Byte,
            Unit::Kilobyte,
            Unit::Megabyte,
            Unit::Gigabyte,
            Unit::Terabyte,
        ],
        Base::Binary => &[
            Unit::Byte,
            Unit::Kibibyte,
            Unit::Mebibyte,
            Unit::Gibibyte,
            Unit::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) and unknown symbols return
/// `f64::NAN`, mirroring the TypeScript contract. `Base` and `Unit` are already
/// constrained by their enum types, but the symbol-based entry point below
/// preserves the original "unknown unit → NaN" behavior for string inputs.
pub fn convert_data(value: f64, from: Unit, to: Unit, base: Base) -> f64 {
    // Reject Inf/NaN early so they don't propagate through the multiplication.
    if !value.is_finite() {
        return f64::NAN;
    }

    let radix = base.radix();
    (value * radix.powi(from.exponent())) / radix.powi(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 convert_data_symbols(value: f64, from: &str, to: &str, base: i64) -> f64 {
    if !value.is_finite() {
        return f64::NAN;
    }
    // Only the two canonical bases are valid; anything else is a caller bug.
    let base = match base {
        1000 => Base::Decimal,
        1024 => Base::Binary,
        _ => return f64::NAN,
    };
    // Unknown unit symbols short-circuit to NaN, like the TS dictionary miss.
    let from = match Unit::from_symbol(from) {
        Some(u) => u,
        None => return f64::NAN,
    };
    let to = match Unit::from_symbol(to) {
        Some(u) => u,
        None => return f64::NAN,
    };
    convert_data(value, from, to, base)
}

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 →