Skip to content

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

<?php
/*
 * percentage-calculator — PHP 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.
 */

namespace CosmoDev\Percentage;

const DEFAULT_MAX_DECIMALS = 2;

/**
 * Options governing result precision. `maxDecimals` defaults to 2 when null,
 * mirroring the TypeScript PercentOptions optional field — which (unlike an
 * int) distinguishes "unset" from an explicit 0.
 */
final class PercentOptions
{
    public function __construct(
        public readonly ?int $maxDecimals = null,
    ) {}
}

/**
 * Reports whether every value in $vals is finite. NAN and ±INF are treated as
 * invalid inputs throughout this namespace: callers receive null rather than a
 * propagated NAN.
 */
function every_finite(float ...$vals): bool
{
    foreach ($vals as $v) {
        if (!is_finite($v)) {
            return false;
        }
    }
    return true;
}

/** Resolve the effective precision, falling back to the default when unset. */
function resolve_decimals(PercentOptions $opts): int
{
    return $opts->maxDecimals ?? DEFAULT_MAX_DECIMALS;
}

/**
 * Round $n to at most $maxDecimals places.
 *
 * Adding PHP_FLOAT_EPSILON before scaling absorbs the tiny errors that arise
 * from representing decimal fractions in binary floating point (the classic
 * `0.1 + 0.2 != 0.3` problem); it also nudges exact `.5` ties onto a side that
 * agrees with JavaScript's Math.round, keeping this port byte-for-byte
 * consistent with the TypeScript source. Non-finite values are returned
 * unchanged so this function is total.
 *
 * Named `round_value` to avoid shadowing PHP's built-in `round()`, which is
 * used internally to do the scaled rounding.
 */
function round_value(float $n, int $maxDecimals): float
{
    if (!is_finite($n)) {
        return $n;
    }
    $factor = 10 ** $maxDecimals;
    return round(($n + PHP_FLOAT_EPSILON) * $factor) / $factor;
}

/**
 * X% of $value: ($pct / 100) * $value. Returns null when either input is
 * non-finite.
 */
function percent_of(float $pct, float $value, ?PercentOptions $opts = null): ?float
{
    $opts ??= new PercentOptions();
    if (!every_finite($pct, $value)) {
        return null;
    }
    return round_value(($pct / 100) * $value, resolve_decimals($opts));
}

/**
 * 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.
 */
function what_percent(float $part, float $total, ?PercentOptions $opts = null): ?float
{
    $opts ??= new PercentOptions();
    if (!every_finite($part, $total)) {
        return null;
    }
    if ($total == 0.0) {
        return null;
    }
    return round_value(($part / $total) * 100, resolve_decimals($opts));
}

/**
 * Percentage change from $from to $to: (($to - $from) / abs($from)) * 100.
 *
 * The denominator is absolute so the result's sign reflects only the
 * direction of change (positive for an increase, negative for a decrease).
 * Returns null when $from is zero (no meaningful base to compare against) or
 * either input is non-finite.
 */
function percent_change(float $from, float $to, ?PercentOptions $opts = null): ?float
{
    $opts ??= new PercentOptions();
    if (!every_finite($from, $to)) {
        return null;
    }
    if ($from == 0.0) {
        return null;
    }
    return round_value((($to - $from) / abs($from)) * 100, resolve_decimals($opts));
}

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 →