Skip to content

Percentage Calculator — Go 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 Go implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Package percentage is the Go twin of CosmoDev's src/lib/percentage.ts (dual
// source: the web lib is TypeScript, the CLI lib is Go — kept in lock-step).
// Pure + deterministic, never panics. The table-driven tests in
// percentage-calculator_test.go share vectors with src/lib/percentage.test.ts
// so the two implementations are held to the same contract.
//
// The TS lib models an undefined result (non-finite input or a zero divisor)
// as null. The Go analogue is the (float64, bool) return: when the bool is
// false the result is undefined and the float64 is a meaningless zero value;
// when it is true the float64 is the rounded result. Round passes non-finite
// values through unchanged, exactly like the TS round().
package percentage

import "math"

// DefaultMaxDecimals is the rounding precision used when an option is omitted,
// matching DEFAULT_MAX_DECIMALS in src/lib/percentage.ts.
const DefaultMaxDecimals = 2

// epsilon is 2^-52, identical to JavaScript's Number.EPSILON. Adding it before
// rounding absorbs binary floating-point noise (so e.g. 0.005 rounds up to
// 0.01 instead of down to 0), mirroring the TS lib's `n + Number.EPSILON`.
var epsilon = math.Ldexp(1, -52)

// Options configures the percentage functions. The zero value (Options{})
// matches the TS default (PercentOptions.maxDecimals omitted → 2 decimals).
//
// MaxDecimals is a *int so the zero value means "default 2" while a non-nil
// pointer — including a pointer to 0 (whole-number rounding) — is used
// verbatim. This mirrors the TS lib's distinction between an omitted
// maxDecimals and an explicitly set 0.
type Options struct {
	MaxDecimals *int
}

// resolveMaxDecimals returns the effective precision: the explicit option when
// set, otherwise DefaultMaxDecimals. Mirrors the TS default-parameter behavior
// of round().
func resolveMaxDecimals(opts Options) int {
	if opts.MaxDecimals != nil {
		return *opts.MaxDecimals
	}
	return DefaultMaxDecimals
}

// everyFinite reports whether every value is finite (not NaN, not ±Inf). It is
// the Go twin of everyFinite() in the TS lib.
func everyFinite(vals ...float64) bool {
	for _, v := range vals {
		if math.IsNaN(v) || math.IsInf(v, 0) {
			return false
		}
	}
	return true
}

// Round rounds n to at most maxDecimals places, absorbing binary float noise
// via epsilon (Number.EPSILON). It is the Go twin of round() in
// src/lib/percentage.ts. Non-finite values (NaN, ±Inf) are returned unchanged.
func Round(n float64, maxDecimals int) float64 {
	if math.IsNaN(n) || math.IsInf(n, 0) {
		return n
	}
	factor := math.Pow(10, float64(maxDecimals))
	return math.Round((n+epsilon)*factor) / factor
}

// PercentOf returns pct% of value: pct/100*value. The bool is false (the Go
// analogue of the TS null) if either input is non-finite; otherwise it is true
// and the float64 holds the rounded result. It is the Go twin of percentOf().
func PercentOf(pct, value float64, opts Options) (float64, bool) {
	if !everyFinite(pct, value) {
		return 0, false
	}
	return Round((pct/100)*value, resolveMaxDecimals(opts)), true
}

// WhatPercent returns what percentage part is of total: part/total*100. The
// bool is false if either input is non-finite or if total is 0 (an undefined
// result). It is the Go twin of whatPercent().
func WhatPercent(part, total float64, opts Options) (float64, bool) {
	if !everyFinite(part, total) || total == 0 {
		return 0, false
	}
	return Round((part/total)*100, resolveMaxDecimals(opts)), true
}

// PercentChange returns the percentage change from from to to, measured
// relative to the magnitude of from: (to-from)/|from|*100. The bool is false
// if either input is non-finite or if from is 0 (an undefined result). It is
// the Go twin of percentChange().
func PercentChange(from, to float64, opts Options) (float64, bool) {
	if !everyFinite(from, to) || from == 0 {
		return 0, false
	}
	return Round(((to-from)/math.Abs(from))*100, resolveMaxDecimals(opts)), true
}

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 →