Memory Unit Converter: From 1024 to Petabytes - Implementation Deep Dive#

Building a file upload feature recently, I needed to display file sizes. The backend returns byte counts in the millions - users stare at 15728640 and wonder, “How big is that?” So I built a memory unit converter.

The Core Question: Why 1024?#

Computers store data in bits, with 8 bits forming 1 Byte. After that, each unit scales by 1024 (that’s 2^10):

1 KB = 1024 Bytes
1 MB = 1024 KB = 1,048,576 Bytes
1 GB = 1024 MB = 1,073,741,824 Bytes
1 TB = 1024 GB = 1,099,511,627,776 Bytes
1 PB = 1024 TB = 1,125,899,906,842,624 Bytes

Why 1024 instead of 1000?

Computers use binary. 1024 = 2^10 is a binary “round number.” Hard drive manufacturers use decimal (1 KB = 1000 Bytes), which is why your “1TB” drive shows as 931 GB on your computer - not a defect, just different standards.

Conversion Algorithm#

The Core Concept#

Converting between units is simple: convert to bytes first, then to the target unit:

interface Unit {
  value: string       // 'B', 'KB', 'MB'...
  label: string       // 'Bytes (B)', 'Kilobytes (KB)'...
  factor: number      // 1024^n
}

const units: Unit[] = [
  { value: "B",  label: "Bytes (B)",     factor: 1 },
  { value: "KB", label: "Kilobytes (KB)", factor: 1024 },
  { value: "MB", label: "Megabytes (MB)", factor: 1024 ** 2 },
  { value: "GB", label: "Gigabytes (GB)", factor: 1024 ** 3 },
  { value: "TB", label: "Terabytes (TB)", factor: 1024 ** 4 },
  { value: "PB", label: "Petabytes (PB)", factor: 1024 ** 5 },
]

function convert(value: number, from: string, to: string): number {
  const fromUnit = units.find(u => u.value === from)
  const toUnit = units.find(u => u.value === to)
  if (!fromUnit || !toUnit) return 0

  const bytes = value * fromUnit.factor  // To bytes first
  return bytes / toUnit.factor           // To target unit
}

// Example: 2 GB to MB
convert(2, 'GB', 'MB')  // 2048

Auto-selecting the Best Unit#

When users input 15728640 bytes, we want to display 15 MB automatically:

function formatBytes(bytes: number, decimals: number = 2): string {
  if (bytes === 0) return '0 Bytes'

  const k = 1024
  const dm = decimals < 0 ? 0 : decimals
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']

  // Calculate the appropriate unit index
  const i = Math.floor(Math.log(bytes) / Math.log(k))

  // Convert and format
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}

formatBytes(15728640)      // "15 MB"
formatBytes(1073741824)    // "1 GB"
formatBytes(1234567890)    // "1.15 GB"
formatBytes(1023)          // "1023 Bytes"

Algorithm explained:

Math.log(bytes) / Math.log(1024) computes the logarithm base 1024. The result’s integer part indicates how many unit boundaries we’ve crossed:

  • Math.log(15728640) / Math.log(1024) ≈ 2.91 → index 2, which is MB
  • Math.log(1073741824) / Math.log(1024) = 3 → index 3, which is GB

Calculating All Units at Once#

To show conversions to all units simultaneously:

function calculateAllConversions(value: number, from: string) {
  const fromUnit = units.find(u => u.value === from)
  if (!fromUnit) return []

  const bytes = value * fromUnit.factor

  return units.map(u => ({
    ...u,
    result: bytes / u.factor
  }))
}

// Example: 1 GB in all units
calculateAllConversions(1, 'GB')
/*
[
  { value: 'B',  result: 1073741824 },
  { value: 'KB', result: 1048576 },
  { value: 'MB', result: 1024 },
  { value: 'GB', result: 1 },
  { value: 'TB', result: 0.0009765625 },
  { value: 'PB', result: 9.5367431640625e-7 }
]
*/

Cache results with useMemo to avoid recalculating:

const result = useMemo(() => {
  const numValue = parseFloat(value) || 0
  return convert(numValue, fromUnit, toUnit)
}, [value, fromUnit, toUnit])

const allConversions = useMemo(() => {
  const numValue = parseFloat(value) || 0
  return calculateAllConversions(numValue, fromUnit)
}, [value, fromUnit])

Number Formatting & Precision#

Large Number Display#

1073741824 isn’t readable. Use toLocaleString() for thousands separators:

// Default formatting
result.toLocaleString()  // "1,073,741,824"

// Control decimal places
result.toLocaleString(undefined, {
  minimumFractionDigits: 2,
  maximumFractionDigits: 4
})
// "1,073,741,824.0000" or "1.1525" (auto-adjusted)

Floating Point Precision Pitfalls#

JavaScript’s floating-point math has precision issues:

0.1 + 0.2  // 0.30000000000000004
1024 * 0.0009765625  // 0.9999999999999999 (should be 1)

Solution: use toFixed() or toPrecision():

function formatResult(value: number, maxDecimals: number = 6): string {
  // Round first, then remove trailing zeros
  const fixed = value.toFixed(maxDecimals)
  return parseFloat(fixed).toString()
}

formatResult(0.9999999999999999)  // "1"
formatResult(15.123456789)        // "15.123457"

Practical Applications#

1. File Upload Size Limits#

function checkFileSize(file: File, maxSizeMB: number): boolean {
  const maxSizeBytes = maxSizeMB * 1024 * 1024
  return file.size <= maxSizeBytes
}

// Check if file exceeds 10MB
checkFileSize(file, 10)

2. Storage Space Statistics#

interface StorageInfo {
  total: number
  used: number
  available: number
}

function formatStorageInfo(info: StorageInfo) {
  return {
    total: formatBytes(info.total),
    used: formatBytes(info.used),
    available: formatBytes(info.available),
    usagePercent: ((info.used / info.total) * 100).toFixed(1) + '%'
  }
}

formatStorageInfo({
  total: 107374182400,  // 100 GB
  used: 75161927680,    // 70 GB
  available: 32212254720 // 30 GB
})
// { total: "100 GB", used: "70 GB", available: "30 GB", usagePercent: "70.0%" }

3. Bandwidth Calculations#

Network bandwidth is typically in Mbps (megabits per second), but download speeds display as MB/s:

function bandwidthToSpeed(mbps: number): string {
  const bytesPerSecond = (mbps * 1000000) / 8  // Mbps → MB/s
  return formatBytes(bytesPerSecond) + '/s'
}

bandwidthToSpeed(100)   // "12.5 MB/s" (100Mbps actual download speed)
bandwidthToSpeed(1000)  // "125 MB/s" (1Gbps)

Edge Cases#

1. Very Large Values#

JavaScript’s Number.MAX_SAFE_INTEGER is 2^53 - 1 (about 9 PB). Larger integers may lose precision:

function safeConvert(value: number, from: string, to: string): number {
  const fromUnit = units.find(u => u.value === from)
  const toUnit = units.find(u => u.value === to)

  if (!fromUnit || !toUnit) return 0

  // Use BigInt for very large values
  if (value > Number.MAX_SAFE_INTEGER) {
    const bytes = BigInt(value) * BigInt(fromUnit.factor)
    return Number(bytes / BigInt(toUnit.factor))
  }

  return (value * fromUnit.factor) / toUnit.factor
}

2. Negative Numbers and Zero#

function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 Bytes'
  if (bytes < 0) return '-' + formatBytes(-bytes)

  // Normal conversion logic...
}

3. Non-numeric Input#

function parseInput(value: string): number {
  const parsed = parseFloat(value)
  return isNaN(parsed) ? 0 : parsed
}

The Result#

Based on these principles, I built: Memory Unit Converter

Features:

  • B / KB / MB / GB / TB / PB conversions
  • Real-time calculation for all units
  • Decimal and negative number support
  • Thousands separator formatting

The implementation isn’t complex, but involves many details: base selection, precision control, formatting, and edge cases. Hope this helps!


Related tools: Unit Converter | Timestamp Converter