Skip to content

Instantly share code, notes, and snippets.

@chris-hatton
Last active December 31, 2023 11:37
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chris-hatton/a3fca4d064e7466982a8151d81715ca1 to your computer and use it in GitHub Desktop.
Save chris-hatton/a3fca4d064e7466982a8151d81715ca1 to your computer and use it in GitHub Desktop.
Human Readable Data Size Formatter for Kotlin Multiplatform (uses Common API only)
package datasizeformatter
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.roundToLong
/**
* Format a human-readable representation of data size, in binary base form.
* e.g. 1024 -> 1 KiB
* @param byteCount The number of bytes to represent in human-readable form. `Long.MIN_VALUE` is unsupported.
* @param decimalPlaces The number of digits to round the resulting scale to
* @param zeroPadFraction whether to add trailing zeroes if they could otherwise be numerically truncated.
* e.g. for the value `3.1` if [decimalPlaces] is set to 3 and [zeroPadFraction] is `true` would be shown as `3.100`.
*/
fun formatBinarySize(
byteCount: Long,
decimalPlaces: Int = 2,
zeroPadFraction: Boolean = false
): String {
require(byteCount>Long.MIN_VALUE) { "Out of range" }
require(decimalPlaces>=0) { "Negative decimal places unsupported" }
val isNegative = byteCount < 0
val absByteCount = abs(byteCount)
return if (absByteCount < 1024) {
"$byteCount B"
} else {
val zeroBitCount: Int = (63 - absByteCount.countLeadingZeroBits()) / 10
val absNumber: Double = absByteCount.toDouble() / (1L shl zeroBitCount * 10)
val roundingFactor: Int = 10.0.pow(decimalPlaces).toInt()
val absRoundedNumberString = with((absNumber * roundingFactor).roundToLong().toString()) {
val splitIndex = length - decimalPlaces - 1
val wholeString = substring(0..splitIndex)
val fractionString = with(substring(splitIndex + 1)) {
if (zeroPadFraction) this else dropLastWhile { digit -> digit == '0' }
}
if (fractionString.isEmpty()) wholeString else "$wholeString.$fractionString"
}
val roundedNumberString = if(isNegative) "-$absRoundedNumberString" else absRoundedNumberString
"$roundedNumberString ${"KMGTPE"[zeroBitCount - 1]}iB"
}
}
@chris-hatton
Copy link
Author

chris-hatton commented Jan 3, 2022

Tested OK on JVM, JS and Native targets with:

assertEquals(expected = "0 B", actual = formatBinarySize(byteCount = -0))
assertEquals(expected = "1 KiB", actual = formatBinarySize(byteCount = 1024))
assertEquals(expected = "1.00 KiB", actual = formatBinarySize(byteCount = 1024, zeroPadFraction = true))
assertEquals(expected = "4 KiB", actual = formatBinarySize(byteCount = 4096))
assertEquals(expected = "1.00 MiB", actual = formatBinarySize(1048576, zeroPadFraction = true))
assertEquals(expected = "1.500 MiB", actual = formatBinarySize(1572864, decimalPlaces = 3, zeroPadFraction = true))
assertEquals(expected = "1.00009 PiB", actual = formatBinarySize(1126000000000000, decimalPlaces = 5, zeroPadFraction = false))
assertEquals(expected = "-1.00009 PiB", actual = formatBinarySize(-1126000000000000, decimalPlaces = 5, zeroPadFraction = false))
assertEquals(expected = "-47 B", actual = formatBinarySize(-47))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment