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 →