4

Failed to find the similar topic in StackOverflow, the question is similar to How to convert byte size into human readable format in java?

How to convert byte size into human-readable format in Java? Like 1024 should become "1 Kb" and 1024*1024 should become "1 Mb".

I am kind of sick of writing this utility method for each project. Are there any static methods in Apache Commons for this?

But for Kotlin, had prepared something based on the accepted answer there and wanted to share it but thought should posting it in a separate thread is better to not distract people on that thread so others also can comment or post other idiomatic Kotlin answers here

Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81

6 Answers6

10

As my usecase was for Android use turned out I could use this https://stackoverflow.com/a/26502430

android.text.format.Formatter.formatShortFileSize(activityContext, bytes)

and

android.text.format.Formatter.formatFileSize(activityContext, bytes)
Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
5

Based on this Java code by @aioobe:

fun humanReadableByteCountBin(bytes: Long) = when {
    bytes == Long.MIN_VALUE || bytes < 0 -> "N/A"
    bytes < 1024L -> "$bytes B"
    bytes <= 0xfffccccccccccccL shr 40 -> "%.1f KiB".format(bytes.toDouble() / (0x1 shl 10))
    bytes <= 0xfffccccccccccccL shr 30 -> "%.1f MiB".format(bytes.toDouble() / (0x1 shl 20))
    bytes <= 0xfffccccccccccccL shr 20 -> "%.1f GiB".format(bytes.toDouble() / (0x1 shl 30))
    bytes <= 0xfffccccccccccccL shr 10 -> "%.1f TiB".format(bytes.toDouble() / (0x1 shl 40))
    bytes <= 0xfffccccccccccccL -> "%.1f PiB".format((bytes shr 10).toDouble() / (0x1 shl 40))
    else -> "%.1f EiB".format((bytes shr 20).toDouble() / (0x1 shl 40))
}

Can be improved by using ULong to drop the first condition but the type, currently (2019), is marked as experimental by the language. Prepend Locale.ENGLISH to .format( to ensure digits won't be converted in locales with different digits.

Let me know what can be improved to make it a more idiomatic and readable Kotlin code.

Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
4

There's a more concise solution:

fun bytesToHumanReadableSize(bytes: Double) = when {
        bytes >= 1 shl 30 -> "%.1f GB".format(bytes / (1 shl 30))
        bytes >= 1 shl 20 -> "%.1f MB".format(bytes / (1 shl 20))
        bytes >= 1 shl 10 -> "%.0f kB".format(bytes / (1 shl 10))
        else -> "$bytes bytes"
    }
John Doe
  • 1,003
  • 12
  • 14
1

Another concise solution with locale-sensitive formatting and correct binary prefixes:

import java.util.*

object Bytes {
  fun format(value: Long, locale: Locale = Locale.getDefault()): String = when {
    value < 1024 -> "$value B"
    else -> {
      val z = (63 - java.lang.Long.numberOfLeadingZeros(value)) / 10
      String.format(locale, "%.1f %siB", value.toDouble() / (1L shl z * 10), " KMGTPE"[z])
    }
  }
}

Test:

val locale = Locale.getDefault()
println(Bytes.format(1L, locale))
println(Bytes.format(2L * 1024, locale))
println(Bytes.format(3L * 1024 * 1024, locale))
println(Bytes.format(4L * 1024 * 1024 * 1024, locale))
println(Bytes.format(5L * 1024 * 1024 * 1024 * 1024, locale))
println(Bytes.format(6L * 1024 * 1024 * 1024 * 1024 * 1024, locale))
println(Bytes.format(Long.MAX_VALUE, locale))

Output:

1 B
2.0 KiB
3.0 MiB
4.0 GiB
5.0 GiB
6.0 PiB
8.0 EiB
Michel Jung
  • 2,966
  • 6
  • 31
  • 51
1

Binary -> 1 Kilobyte = 1024 Byte

Non-Binary -> 1 Kilobyte = 1000 Byte

Native kotlin method below, if your API level is less than or equal to 24(Nougat), it uses binary format. If greater, it uses non-binary format.

Formatter.formatFileSize(context, yourValueAsByte)

So i suggest to add your custom method. Here is the mine:

fun Long.formatBinarySize(): String {
    val kiloByteAsByte = 1.0 * 1024.0
    val megaByteAsByte = 1.0 * 1024.0 * 1024.0
    val gigaByteAsByte = 1.0 * 1024.0 * 1024.0 * 1024.0
    val teraByteAsByte = 1.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0
    val petaByteAsByte = 1.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0

    return when {
        this < kiloByteAsByte -> "${this.toDouble()} B"
        this >= kiloByteAsByte && this < megaByteAsByte -> "${String.format("%.2f", (this / kiloByteAsByte))} KB"
        this >= megaByteAsByte && this < gigaByteAsByte -> "${String.format("%.2f", (this / megaByteAsByte))} MB"
        this >= gigaByteAsByte && this < teraByteAsByte -> "${String.format("%.2f", (this / gigaByteAsByte))} GB"
        this >= teraByteAsByte && this < petaByteAsByte -> "${String.format("%.2f", (this / teraByteAsByte))} TB"
        else -> "Bigger than 1024 TB"
}}
Atakan Akar
  • 133
  • 1
  • 7
1

The method below will generate a formatter for any set of units in any base.

An example and a unit test follow.

/**
 * This generates formatting functions for applying aggregating units (e.g. Kilo, Mega)
 * to values.  If you run out of units it gives up and shows the unit value.
 * NB This method is usable verbatim in Kotlin/JS.
 *
 * @param base The unit base, e.g. 1024 for bytes, 1000 for metric units of measure.
 * @param units the names of the units starting with the base unit (e.g. byte, meter).
 */
fun formatUnits(base: Int, units: List<String>): (Double, Int) -> String {
    check(1 < base)
    check(units.isNotEmpty())
    return { value, precision ->
        check(0 <= precision)
        tailrec fun haveAtIt(unitsTail: List<String>, adj: Double): String {
            if (unitsTail.isEmpty()) {
                return "$value${units.first()}"
            }
            if (adj < base) {
                val formatter: Double = precision.let {
                    var i = 1.0
                    for (q in 0 until precision)
                        i *= 10.0
                    i
                }
                val mag = ((adj * formatter).roundToInt() / formatter).toString().let {
                    if (it.endsWith(".0")) it.substring(0 .. it.length - 3)
                    else it
                }
                return "$mag${unitsTail.first()}"
            }
            return haveAtIt(unitsTail.drop(1), adj / base)
        }
        haveAtIt(units, value)
    }
}

private val formatBytesImpl = formatUnits(1024, listOf("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"))
fun formatBytes(bytes: Long): String = formatBytesImpl(bytes.toDouble(), 2)

    @Test
    fun testFormatBytes() {
        assertEquals("1B", formatBytes(1))
        assertEquals("1023B", formatBytes(1023))
        assertEquals("1KB", formatBytes(1024))
        assertEquals("1.5KB", formatBytes(1536))
        assertEquals("1.51KB", formatBytes(1547))
        assertEquals("1.51KB", formatBytes(1548))
        assertEquals("1MB", formatBytes((1024 * 1024) + 1024))
        assertEquals("1.01MB", formatBytes((1024 * 1024) + 7 * 1024))
        assertEquals("1.01MB", formatBytes((1024 * 1024) + 10 * 1024))
    }
F. P. Freely
  • 1,026
  • 14
  • 24