Newer
Older
ddhx / src / utils / format.d
/// Bit utilities.
/// Copyright: dd86k <dd@dax.moe>
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module utils.format;
    
private enum : float {
    // SI (base-10)
    SI = 1000,    /// SI base
    kB = SI,    /// Represents one KiloByte
    MB = kB * SI,    /// Represents one MegaByte
    GB = MB * SI,    /// Represents one GigaByte
    TB = GB * SI,    /// Represents one TeraByte
    PB = TB * SI,    /// Represents one PetaByte
    EB = PB * SI,    /// Represents one ExaByte
    // IEC (base-2)
    IEC = 1024,    /// IEC base
    KiB = IEC,    /// Represents one KibiByte (1024)
    MiB = KiB * IEC,    /// Represents one MebiByte (1024²)
    GiB = MiB * IEC,    /// Represents one GibiByte (1024³)
    TiB = GiB * IEC,    /// Represents one TebiByte (1024⁴)
    PiB = TiB * IEC,    /// Represents one PebiByte (1024⁵)
    EiB = PiB * IEC,    /// Represents one PebiByte (1024⁶)
}

/// Format byte size.
/// Params:
///   size = Binary number.
///   b10  = Use SI suffixes instead of IEC suffixes.
/// Returns: Character slice using sformat
const(char)[] formatBin(long size, bool b10 = false)
{
    import std.format : format;
    
    // NOTE: ulong.max = (2^64)-1 Bytes = 16 EiB - 1 = 16 * 1024⁵
    
    //TODO: Pretty this up with some clever math
    //      Not great, not terrible...
    
    if (b10) // base 1000
    {
        if (size >= EB)
            return format("%0.2f EB", size / EB);
        if (size >= PB)
            return format("%0.2f TB", size / PB);
        if (size >= TB)
            return format("%0.2f TB", size / TB);
        if (size >= GB)
            return format("%0.2f GB", size / GB);
        if (size >= MB)
            return format("%0.1f MB", size / MB);
        if (size >= kB)
            return format("%0.1f kB", size / kB);
    }
    else // base 1024
    {
        if (size >= EiB)
            return format("%0.2f EiB", size / EiB);
        if (size >= PiB)
            return format("%0.2f TiB", size / PiB);
        if (size >= TiB)
            return format("%0.2f TiB", size / TiB);
        if (size >= GiB)
            return format("%0.2f GiB", size / GiB);
        if (size >= MiB)
            return format("%0.1f MiB", size / MiB);
        if (size >= KiB)
            return format("%0.1f KiB", size / KiB);
    }
    
    return format("%u B", size);
}

unittest {
    assert(formatBin(0) == "0 B");
    assert(formatBin(1) == "1 B");
    assert(formatBin(1023) == "1023 B");
    assert(formatBin(1024) == "1.0 KiB");
    // Wouldn't this more exactly 7.99 EiB? Precision limitation?
    assert(formatBin(long.max) == "8.00 EiB");
    assert(formatBin(999,  true) == "999 B");
    assert(formatBin(1000, true) == "1.0 kB");
}