Skip to content

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

"""data-unit-converter — polyglot showcase port (Python).

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 ``math.nan`` for non-finite input, unknown units, or
an invalid base — it never raises.

Self-contained: standard library only, no external dependencies (no numpy, no
pip packages).

License/usage: display source — part of CosmoDev's polyglot tool pages.
"""

from __future__ import annotations

import math
from typing import Dict, List

# Public aliases — keep close to the TS naming so the two sources read in
# parallel. ``Literal`` mirrors the TS ``DataBase = 1000 | 1024`` union.
DataBase = int  # 1000 (decimal/SI) or 1024 (binary/IEC)
DataUnit = str  # one of the symbols below

#: Units available under each base, ordered smallest (B) to largest. The UI
#: uses this to populate the per-base unit pickers.
UNITS: Dict[int, List[DataUnit]] = {
    1000: ["B", "KB", "MB", "GB", "TB"],
    1024: ["B", "KiB", "MiB", "GiB", "TiB"],
}

#: Power-of-base rank for each unit: ``B`` 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.
_EXPONENT: Dict[DataUnit, int] = {
    "B": 0,

    "KB": 1, "MB": 2, "GB": 3, "TB": 4,
    "KiB": 1, "MiB": 2, "GiB": 3, "TiB": 4,
}


def convert_data(value: float, from_unit: DataUnit, to_unit: DataUnit,
                 base: DataBase) -> float:
    """Convert a value between two digital-data units under the given base.

    Strategy: reduce to bytes via ``value × base ** from_exp``, then divide by
    ``base ** to_exp``. Returns ``math.nan`` for non-finite input, unknown
    units, or an invalid base — never raises (mirrors the TS contract).

    Using ``math.isnan``/``math.isinf`` rather than a bare truthiness check
    matters: ``0.0`` is falsy-ish through some idioms but is a perfectly valid
    input that must round-trip to ``0.0``.
    """
    # Reject Inf/NaN early so they don't propagate through the multiplication.
    if math.isnan(value) or math.isinf(value):
        return math.nan
    # Only the two canonical bases are valid; anything else is a caller bug.
    if base != 1000 and base != 1024:
        return math.nan

    # Unknown unit symbols miss the dict (.get -> None); treat as invalid.
    from_exp = _EXPONENT.get(from_unit)
    to_exp = _EXPONENT.get(to_unit)
    if from_exp is None or to_exp is None:
        return math.nan

    return (value * base ** from_exp) / base ** to_exp

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 →