Skip to content

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

// =============================================================================
//  DataUnitConverter.swift — CosmoDev polyglot showcase port of the
//  `data-unit-converter` tool
//  -----------------------------------------------------------------------------
//  Language : Swift (5.9, standard library + Foundation for pow)
//  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.
// =============================================================================

import Foundation

/// Supported conversion radix: decimal (`.decimal = 1000`, SI units) or binary
/// (`.binary = 1024`, IEC units).
public enum Base: Int {
    case decimal = 1000
    case binary = 1024

    /// Numeric radix as a `Double`, ready for `pow`.
    var radix: Double { Double(rawValue) }
}

/// Recognized digital-data units. The raw values are the symbol strings used
/// in the UI and persisted in shareable URLs — a raw-value enum gives the
/// symbol→unit dictionary lookup (`DataUnit(rawValue:)`) for free.
/// `kilobyte`…`terabyte` are decimal (1000); `kibibyte`…`tebibyte` are binary
/// (1024); `byte` is shared by both bases.
public enum DataUnit: String {
    case byte = "B"
    case kilobyte = "KB"
    case megabyte = "MB"
    case gigabyte = "GB"
    case terabyte = "TB"
    case kibibyte = "KiB"
    case mebibyte = "MiB"
    case gibibyte = "GiB"
    case 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.
    var exponent: Int {
        switch self {
        case .byte: return 0
        case .kilobyte, .kibibyte: return 1
        case .megabyte, .mebibyte: return 2
        case .gigabyte, .gibibyte: return 3
        case .terabyte, .tebibyte: return 4
        }
    }

    /// Units available under each base, ordered smallest (byte) to largest.
    /// The UI uses these to populate the per-base unit pickers.
    public static func units(for base: Base) -> [DataUnit] {
        switch base {
        case .decimal:
            return [.byte, .kilobyte, .megabyte, .gigabyte, .terabyte]
        case .binary:
            return [.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 `DataUnit` are already constrained by their
/// types; the symbol-based entry point below preserves the original
/// "unknown unit → NaN" behavior for string inputs.
public func convertData(_ value: Double, from: DataUnit, to: DataUnit, base: Base) -> Double {
    // Reject Inf/NaN early so they don't propagate through the multiplication.
    guard value.isFinite else { return .nan }

    let radix = base.radix
    return (value * pow(radix, Double(from.exponent))) / pow(radix, Double(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.
public func convertData(_ value: Double, fromSymbol: String, toSymbol: String, base: Int) -> Double {
    guard value.isFinite else { return .nan }
    // Only the two canonical bases are valid; anything else is a caller bug.
    guard let radix = Base(rawValue: base) else { return .nan }
    // Unknown unit symbols short-circuit to NaN, like the TS dictionary miss.
    guard let from = DataUnit(rawValue: fromSymbol) else { return .nan }
    guard let to = DataUnit(rawValue: toSymbol) else { return .nan }
    return convertData(value, from: from, to: to, base: 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 →