Percentage Calculator — Kotlin source
Calculate percentages three ways - X% of Y, X is what percent of Y, and the percentage change between two values. Runs entirely in your browser, with a shareable link.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// percentage-calculator — Kotlin port: three-way percentage math (X% of Y, X as % of Y, % change).
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.pow
/** Rounding precision when an option is omitted, matching
* DEFAULT_MAX_DECIMALS in src/lib/percentage.ts. */
const val DEFAULT_MAX_DECIMALS: Int = 2
/** 2^-52 — identical to JavaScript's Number.EPSILON. */
const val JS_EPSILON: Double = 2.2204460492503131e-16
/** Options governing result precision. null means "omitted" → the default,
* keeping an explicit 0 distinct from an unset value (the TS optional field). */
data class PercentOptions(val maxDecimals: Int? = null) {
/** Effective precision: the explicit option when set, otherwise the default. */
fun resolve(): Int = maxDecimals ?: DEFAULT_MAX_DECIMALS
}
/** Round to at most [maxDecimals] places, absorbing binary float noise via
* the epsilon nudge (the classic 0.005 → 0.01 case). Non-finite values pass
* through unchanged so this function is total, exactly like the TS round(). */
fun roundValue(n: Double, maxDecimals: Int = DEFAULT_MAX_DECIMALS): Double {
if (!n.isFinite()) return n
val factor = 10.0.pow(maxDecimals)
// floor(x + 0.5) replicates JavaScript's Math.round (half toward +inf) —
// NOT kotlin.math.round, which sends .5 ties to even, and not
// roundToLong(), which sends them away from zero.
return floor((n + JS_EPSILON) * factor + 0.5) / factor
}
/** X% of [value]: pct / 100 * value. null (the Kotlin analogue of the TS
* null) when either input is non-finite. */
fun percentOf(pct: Double, value: Double, opts: PercentOptions = PercentOptions()): Double? {
if (!pct.isFinite() || !value.isFinite()) return null
return roundValue((pct / 100) * value, opts.resolve())
}
/** What percentage [part] is of [total]: part / total * 100. null when total
* is 0 (undefined ratio) or either input is non-finite. */
fun whatPercent(part: Double, total: Double, opts: PercentOptions = PercentOptions()): Double? {
if (!part.isFinite() || !total.isFinite() || total == 0.0) return null
return roundValue((part / total) * 100, opts.resolve())
}
/** Percentage change from [from] to [to], measured against |from| so the
* result's sign reflects only the direction of change. null when from is 0
* (no meaningful base) or either input is non-finite. */
fun percentChange(from: Double, to: Double, opts: PercentOptions = PercentOptions()): Double? {
if (!from.isFinite() || !to.isFinite() || from == 0.0) return null
return roundValue(((to - from) / abs(from)) * 100, opts.resolve())
}
fun main() {
val omitted = PercentOptions() // default → 2 decimals
val zero = PercentOptions(maxDecimals = 0) // explicit 0 ≠ omitted
show("15% of 200", percentOf(15.0, 200.0, omitted))
show("30 is what % of 200", whatPercent(30.0, 200.0, omitted))
show("change 40 -> 50", percentChange(40.0, 50.0, omitted))
show("change 50 -> 40", percentChange(50.0, 40.0, omitted))
show("15% of 37, 0 decimals", percentOf(15.0, 37.0, zero))
show("30 is what % of 0", whatPercent(30.0, 0.0, omitted))
show("change 0 -> 50", percentChange(0.0, 50.0, omitted))
}
/** Demo helper: print "null" for the undefined result, integers without a
* trailing ".0" (Kotlin's default Double toString keeps it minimal). */
fun show(label: String, v: Double?) {
println("%-24s = %s".format(label, v?.toString() ?: "null"))
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →