Percentage Calculator — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// percentage-calculator — Swift port: three-way percentage math (X% of Y, X as % of Y, % change).
import Foundation
/// Rounding precision when an option is omitted, matching
/// DEFAULT_MAX_DECIMALS in src/lib/percentage.ts.
let defaultMaxDecimals = 2
/// 2^-52 — identical to JavaScript's Number.EPSILON (Double.ulpOfOne; NOT
/// Double.leastNonzeroMagnitude, which is the smallest subnormal).
let jsEpsilon = Double.ulpOfOne // 2.2204460492503131e-16
/// Options governing result precision. nil means "omitted" → the default,
/// keeping an explicit 0 distinct from an unset value (the TS optional field).
struct PercentOptions {
var maxDecimals: Int? = nil
/// Effective precision: the explicit option when set, otherwise the default.
var resolved: Int { maxDecimals ?? defaultMaxDecimals }
}
/// 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().
func roundValue(_ n: Double, maxDecimals: Int = defaultMaxDecimals) -> Double {
guard n.isFinite else { return n }
let factor = pow(10, Double(maxDecimals))
// floor(x + 0.5) replicates JavaScript's Math.round (half toward +inf) —
// NOT .rounded(), which sends .5 away from zero, and not
// .rounded(.toNearestOrEven), which is Swift's banker's variant.
return floor((n + jsEpsilon) * factor + 0.5) / factor
}
/// X% of `value`: pct / 100 * value. nil (the Swift analogue of the TS null)
/// when either input is non-finite.
func percentOf(_ pct: Double, _ value: Double, opts: PercentOptions = PercentOptions()) -> Double? {
guard pct.isFinite, value.isFinite else { return nil }
return roundValue((pct / 100) * value, maxDecimals: opts.resolved)
}
/// What percentage `part` is of `total`: part / total * 100. nil when total
/// is 0 (undefined ratio) or either input is non-finite.
func whatPercent(_ part: Double, _ total: Double, opts: PercentOptions = PercentOptions()) -> Double? {
guard part.isFinite, total.isFinite, total != 0 else { return nil }
return roundValue((part / total) * 100, maxDecimals: opts.resolved)
}
/// Percentage change from `from` to `to`, measured against |from| so the
/// result's sign reflects only the direction of change. nil when from is 0
/// (no meaningful base) or either input is non-finite.
func percentChange(_ from: Double, _ to: Double, opts: PercentOptions = PercentOptions()) -> Double? {
guard from.isFinite, to.isFinite, from != 0 else { return nil }
return roundValue(((to - from) / abs(from)) * 100, maxDecimals: opts.resolved)
}
/// Demo helper: print "null" for the undefined result, integers without a
/// trailing ".0" (%g keeps the minimal representation).
func show(_ label: String, _ v: Double?) {
let text = v.map { String(format: "%g", $0) } ?? "null"
// Padding via NSString padding, not "%-24s" — String(format:) treats %s
// as a C char* and mangles a Swift String argument.
print(label.padding(toLength: 24, withPad: " ", startingAt: 0) + " = " + text)
}
let omitted = PercentOptions() // default → 2 decimals
let zero = PercentOptions(maxDecimals: 0) // explicit 0 ≠ omitted
show("15% of 200", percentOf(15, 200, opts: omitted))
show("30 is what % of 200", whatPercent(30, 200, opts: omitted))
show("change 40 -> 50", percentChange(40, 50, opts: omitted))
show("change 50 -> 40", percentChange(50, 40, opts: omitted))
show("15% of 37, 0 decimals", percentOf(15, 37, opts: zero))
show("30 is what % of 0", whatPercent(30, 0, opts: omitted))
show("change 0 -> 50", percentChange(0, 50, opts: omitted))
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 →