Search code examples
scalabytestring-formatting

Bytes in human-readable format with idiomatic Scala


I'm looking for idiomatic Scala code that formats a number of bytes into a human-readable string which is useful when displaying file sizes to users.

For example, 1000 bytes should be formatted to 1.0 kB and 49134421234 bytes to 49.1 GB.

Some requirements for the formatting function:

  • As readable and idiomatic as possible
  • Works both with SI (e.g., megabyte) and IEC units (e.g., mebibyte)
  • No dependencies on external libraries
  • Executable in a browser via Scala.js

Solution

  • My version:

    /**
      * Converts a number of bytes into a human-readable string
      * such as `2.2 MB` or `8.0 EiB`.
      *
      * @param bytes the number of bytes we want to convert
      * @param si    if true, we use base 10 SI units where 1000 bytes are 1 kB.
      *              If false, we use base 2 IEC units where 1024 bytes are 1 KiB.
      * @return the bytes as a human-readable string
      */
    def humanReadableSize(bytes: Long, si: Boolean): String = {
    
      // See https://en.wikipedia.org/wiki/Byte
      val (baseValue, unitStrings) =
        if (si)
          (1000, Vector("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"))
        else
          (1024, Vector("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"))
    
      def getExponent(curBytes: Long, baseValue: Int, curExponent: Int = 0): Int =
        if (curBytes < baseValue) curExponent
        else {
          val newExponent = 1 + curExponent
          getExponent(curBytes / (baseValue * newExponent), baseValue, newExponent)
        }
    
      val exponent = getExponent(bytes, baseValue)
      val divisor = Math.pow(baseValue, exponent)
      val unitString = unitStrings(exponent)
    
      // Divide the bytes and show one digit after the decimal point
      f"${bytes / divisor}%.1f $unitString"
    }
    

    Usage examples:

    // Result: 1.0 kB
    humanReadableSize(1000, si = true)
    
    // Result: 1000.0 B
    humanReadableSize(1000, si = false)
    
    // Result: 10.0 kB
    humanReadableSize(10000, si = true)
    
    // Result: 9.8 KiB
    humanReadableSize(10000, si = false)
    
    // Result: 49.1 GB
    humanReadableSize(49134421234L, si = true)
    
    // Result: 45.8 GiB
    humanReadableSize(49134421234L, si = false)