Skip to content

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

/**
 * percentage-calculator - JavaScript port.
 *
 * CosmoDev polyglot showcase port of the `percentage-calculator` tool. Pure
 * percentage logic ported from src/lib/percentage.ts. Deterministic and
 * side-effect free: every function returns null for non-finite input or an
 * undefined result (a zero divisor) instead of throwing, and rounds to a
 * configurable maximum number of decimal places.
 *
 * Display source - part of CosmoDev's polyglot tool pages.
 */

/**
 * @typedef {Object} PercentOptions
 * @property {number} [maxDecimals] - Maximum decimal places in the result
 *   (defaults to 2 when omitted, mirroring the TypeScript optional field).
 */

const DEFAULT_MAX_DECIMALS = 2;

/**
 * Reports whether every value in `vals` is a finite number. NaN and ±Infinity
 * are treated as invalid inputs throughout this module (they propagate nowhere
 * - callers receive null instead of a garbage result).
 *
 * @param {...number} vals
 * @returns {boolean}
 */
function everyFinite(...vals) {
  return vals.every(Number.isFinite);
}

/**
 * Round to at most `maxDecimals` places (default 2).
 *
 * Adding Number.EPSILON before scaling absorbs the tiny errors that arise from
 * representing decimal fractions in binary floating point - the classic
 * `0.1 + 0.2 === 0.30000000000000004`. Non-finite values are returned
 * unchanged so this helper is total and safe to use before a null check.
 *
 * @param {number} n
 * @param {number} [maxDecimals=DEFAULT_MAX_DECIMALS]
 * @returns {number}
 */
export function round(n, maxDecimals = DEFAULT_MAX_DECIMALS) {
  if (!Number.isFinite(n)) return n;
  const factor = 10 ** maxDecimals;
  return Math.round((n + Number.EPSILON) * factor) / factor;
}

/**
 * X% of `value`: `(pct / 100) * value`.
 *
 * Returns null when either input is non-finite.
 *
 * @param {number} pct
 * @param {number} value
 * @param {PercentOptions} [opts={}]
 * @returns {number | null}
 */
export function percentOf(pct, value, opts = {}) {
  if (!everyFinite(pct, value)) return null;
  return round((pct / 100) * value, opts.maxDecimals);
}

/**
 * What percentage `part` is of `total`: `(part / total) * 100`.
 *
 * Returns null when `total` is zero (the ratio is undefined) or either input
 * is non-finite.
 *
 * @param {number} part
 * @param {number} total
 * @param {PercentOptions} [opts={}]
 * @returns {number | null}
 */
export function whatPercent(part, total, opts = {}) {
  if (!everyFinite(part, total)) return null;
  if (total === 0) return null;
  return round((part / total) * 100, opts.maxDecimals);
}

/**
 * Percentage change going from `from` to `to`: `((to - from) / |from|) * 100`.
 *
 * The denominator is absolute so the result's sign reflects only the
 * direction of change (positive for increase, negative for decrease).
 * Returns null when `from` is zero (no meaningful base to compare against) or
 * either input is non-finite.
 *
 * @param {number} from
 * @param {number} to
 * @param {PercentOptions} [opts={}]
 * @returns {number | null}
 */
export function percentChange(from, to, opts = {}) {
  if (!everyFinite(from, to)) return null;
  if (from === 0) return null;
  return round(((to - from) / Math.abs(from)) * 100, opts.maxDecimals);
}

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 →