(Kotlin/코틀린) 실무에서 바로 쓰는 Number 확장함수 모음

개요


1. 범위 체크 / 검증

/** 양수인지 (0 제외) */
fun Int.isPositive(): Boolean = this > 0
fun Long.isPositive(): Boolean = this > 0L
fun Double.isPositive(): Boolean = this > 0.0

/** 음수인지 */
fun Int.isNegative(): Boolean = this < 0
fun Long.isNegative(): Boolean = this < 0L
fun Double.isNegative(): Boolean = this < 0.0

/** 0 이상인지 */
fun Int.isZeroOrPositive(): Boolean = this >= 0

/** 특정 범위 안에 있는지 (양 끝 포함) */
fun Int.isBetween(min: Int, max: Int): Boolean = this in min..max
fun Long.isBetween(min: Long, max: Long): Boolean = this in min..max
fun Double.isBetween(min: Double, max: Double): Boolean = this in min..max

/** 짝수/홀수 */
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.isOdd(): Boolean = this % 2 != 0

사용 예시

println(5.isPositive())          // true
println((-3).isNegative())       // true
println(7.isBetween(1, 10))      // true
println(4.isEven())              // true
println(3.isOdd())               // true

2. 안전 파싱

/** 문자열 → Int, 실패 시 null */
fun String?.toIntOrNull2(): Int? = this?.trim()?.toIntOrNull()

/** 문자열 → Int, 실패 시 기본값 */
fun String?.toIntOrDefault(default: Int = 0): Int =
    this?.trim()?.toIntOrNull() ?: default

/** 문자열 → Double, 실패 시 기본값 */
fun String?.toDoubleOrDefault(default: Double = 0.0): Double =
    this?.trim()?.toDoubleOrNull() ?: default

/** 문자열 → Long, 실패 시 기본값 */
fun String?.toLongOrDefault(default: Long = 0L): Long =
    this?.trim()?.toLongOrNull() ?: default

/** 퍼센트 문자열 → Double ("85%" → 85.0) */
fun String?.percentToDouble(): Double =
    this?.trim()?.removeSuffix("%")?.toDoubleOrNull() ?: 0.0

사용 예시

println("42".toIntOrDefault())           // 42
println("abc".toIntOrDefault(-1))        // -1
println(null.toDoubleOrDefault(9.9))     // 9.9
println("85%".percentToDouble())         // 85.0
println("  123  ".toIntOrDefault())      // 123

3. 소수점 / 반올림 포맷

/** 소수점 n자리로 반올림한 Double 반환 */
fun Double.roundTo(decimals: Int): Double {
    val factor = Math.pow(10.0, decimals.toDouble())
    return Math.round(this * factor) / factor
}

/** 소수점 n자리 문자열로 포맷 */
fun Double.format(decimals: Int): String = "%.${decimals}f".format(this)
fun Float.format(decimals: Int): String = "%.${decimals}f".format(this)

/** 소수점 제거 — 정수면 정수처럼, 아니면 소수점 표시 */
fun Double.toCleanString(): String =
    if (this == Math.floor(this) && !this.isInfinite()) this.toLong().toString()
    else this.toString()

사용 예시

println(3.14159.roundTo(2))      // 3.14
println(3.14159.format(3))       // "3.142"
println(2.0.toCleanString())     // "2"
println(2.5.toCleanString())     // "2.5"

4. 금액 / 숫자 표시 포맷

import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Locale

/** 천 단위 콤마 포맷 (1234567 → "1,234,567") */
fun Int.toCommaString(): String = DecimalFormat("#,###").format(this)
fun Long.toCommaString(): String = DecimalFormat("#,###").format(this)
fun Double.toCommaString(): String = DecimalFormat("#,###.##").format(this)

/** 원화 표시 (1234567 → "₩1,234,567") */
fun Int.toWon(): String = "₩${this.toCommaString()}"
fun Long.toWon(): String = "₩${this.toCommaString()}"

/** 달러 표시 */
fun Double.toDollar(): String =
    NumberFormat.getCurrencyInstance(Locale.US).format(this)

/** K/M 단위 축약 (1500 → "1.5K", 2000000 → "2M") */
fun Long.toAbbreviatedString(): String = when {
    this >= 1_000_000L -> "${(this / 100_000L) / 10.0}M"
    this >= 1_000L     -> "${(this / 100L) / 10.0}K"
    else               -> this.toString()
}
fun Int.toAbbreviatedString(): String = this.toLong().toAbbreviatedString()

사용 예시

println(1234567.toCommaString())         // "1,234,567"
println(50000.toWon())                   // "₩50,000"
println(1299.99.toDollar())              // "$1,299.99"
println(1500.toAbbreviatedString())      // "1.5K"
println(2_000_000.toAbbreviatedString()) // "2.0M"

5. 안전 나눗셈 / 수학 연산

/** 0 나누기 방지 나눗셈, 0으로 나눌 경우 0.0 반환 */
fun Int.safeDivide(other: Int): Double =
    if (other == 0) 0.0 else this.toDouble() / other

fun Double.safeDivide(other: Double): Double =
    if (other == 0.0) 0.0 else this / other

/** 퍼센트 계산 (part / total * 100) */
fun Int.percentOf(total: Int): Double =
    if (total == 0) 0.0 else this.toDouble() / total * 100.0

/** 절댓값 */
fun Int.abs(): Int = kotlin.math.abs(this)
fun Double.abs(): Double = kotlin.math.abs(this)

/** 두 값 사이의 차이 */
fun Int.diff(other: Int): Int = kotlin.math.abs(this - other)
fun Double.diff(other: Double): Double = kotlin.math.abs(this - other)

/** n의 배수로 올림 */
fun Int.ceilToMultipleOf(multiple: Int): Int =
    if (multiple <= 0) this
    else if (this % multiple == 0) this
    else this + (multiple - this % multiple)

/** n의 배수로 내림 */
fun Int.floorToMultipleOf(multiple: Int): Int =
    if (multiple <= 0) this else (this / multiple) * multiple

사용 예시

println(10.safeDivide(0))            // 0.0
println(10.safeDivide(3))            // 3.3333...
println(3.percentOf(10))             // 30.0
println((-5).abs())                  // 5
println(7.diff(12))                  // 5
println(13.ceilToMultipleOf(5))      // 15
println(13.floorToMultipleOf(5))     // 10

6. 단위 변환

/** dp → px 변환 (Android Context 없이 density 직접 전달) */
fun Int.dpToPx(density: Float): Int = (this * density + 0.5f).toInt()
fun Float.dpToPx(density: Float): Float = this * density

/** px → dp 변환 */
fun Int.pxToDp(density: Float): Float = this / density
fun Float.pxToDp(density: Float): Float = this / density

/** 초 → 분:초 문자열 (90 → "1:30") */
fun Int.toMinuteSecond(): String {
    val m = this / 60
    val s = this % 60
    return "%d:%02d".format(m, s)
}

/** 밀리초 → 시:분:초 문자열 */
fun Long.toHourMinuteSecond(): String {
    val total = this / 1000
    val h = total / 3600
    val m = (total % 3600) / 60
    val s = total % 60
    return if (h > 0) "%d:%02d:%02d".format(h, m, s)
    else "%d:%02d".format(m, s)
}

/** 바이트 → 사람이 읽기 좋은 파일 크기 */
fun Long.toReadableFileSize(): String = when {
    this >= 1_073_741_824L -> "%.1f GB".format(this / 1_073_741_824.0)
    this >= 1_048_576L     -> "%.1f MB".format(this / 1_048_576.0)
    this >= 1_024L         -> "%.1f KB".format(this / 1_024.0)
    else                   -> "$this B"
}

사용 예시

val density = 2.0f
println(16.dpToPx(density))              // 32
println(90.toMinuteSecond())             // "1:30"
println(3661_000L.toHourMinuteSecond())  // "1:01:01"
println(1_500_000L.toReadableFileSize()) // "1.4 MB"
println(2_147_483_648L.toReadableFileSize()) // "2.0 GB"

7. 범위 제한 / 클램프

/** min~max 범위 안으로 강제 제한 */
fun Int.clamp(min: Int, max: Int): Int = this.coerceIn(min, max)
fun Long.clamp(min: Long, max: Long): Long = this.coerceIn(min, max)
fun Double.clamp(min: Double, max: Double): Double = this.coerceIn(min, max)
fun Float.clamp(min: Float, max: Float): Float = this.coerceIn(min, max)

/** 0.0 ~ 1.0 사이로 제한 (Progress, Alpha 등에 사용) */
fun Double.clamp01(): Double = this.coerceIn(0.0, 1.0)
fun Float.clamp01(): Float = this.coerceIn(0f, 1f)

/** 최솟값 보장 */
fun Int.atLeast(min: Int): Int = maxOf(this, min)
fun Double.atLeast(min: Double): Double = maxOf(this, min)

/** 최댓값 보장 */
fun Int.atMost(max: Int): Int = minOf(this, max)
fun Double.atMost(max: Double): Double = minOf(this, max)

사용 예시

println(150.clamp(0, 100))      // 100
println((-5).clamp(0, 100))     // 0
println(1.5.clamp01())          // 1.0
println((-0.3).clamp01())       // 0.0
println(3.atLeast(5))           // 5
println(10.atMost(7))           // 7

8. 실전 조합 예제

예제 1 — 할인율 표시

fun calculateDiscountLabel(original: Int, discounted: Int): String {
    val rate = discounted.percentOf(original)
    val saved = (original - discounted).toWon()
    return "할인율 ${(100 - rate).roundTo(1)}% (${saved} 절약)"
}

println(calculateDiscountLabel(50000, 35000))
// 할인율 30.0% (₩15,000 절약)

예제 2 — 진행률 바 계산

fun getProgressFraction(current: Int, total: Int): Float =
    current.percentOf(total).toFloat().div(100f).clamp01()

println(getProgressFraction(3, 10))   // 0.3
println(getProgressFraction(12, 10))  // 1.0 (최대 제한)

예제 3 — 동영상 재생 시간 표시

fun formatDuration(currentMs: Long, totalMs: Long): String =
    "${currentMs.toHourMinuteSecond()} / ${totalMs.toHourMinuteSecond()}"

println(formatDuration(65_000L, 3_600_000L))
// "1:05 / 1:00:00"

예제 4 — 파일 업로드 진행률

fun uploadStatus(uploadedBytes: Long, totalBytes: Long): String {
    val percent = uploadedBytes.toDouble().div(totalBytes.toDouble())
        .times(100).clamp(0.0, 100.0).roundTo(1)
    return "${uploadedBytes.toReadableFileSize()} / ${totalBytes.toReadableFileSize()} ($percent%)"
}

println(uploadStatus(512_000L, 2_048_000L))
// "500.0 KB / 2.0 MB (25.0%)"

9. 정리

카테고리 주요 함수
범위 체크 isPositive, isBetween, isEven, isOdd
안전 파싱 toIntOrDefault, toDoubleOrDefault, percentToDouble
소수점 포맷 roundTo, format, toCleanString
금액 포맷 toCommaString, toWon, toDollar, toAbbreviatedString
수학 연산 safeDivide, percentOf, abs, diff, ceilToMultipleOf
단위 변환 dpToPx, toMinuteSecond, toHourMinuteSecond, toReadableFileSize
범위 제한 clamp, clamp01, atLeast, atMost


Related Posts