Percentage Calculator — C# 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 C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// percentage-calculator — C# port: three-way percentage math (X% of Y, X as % of Y, % change).
using System;
namespace PercentageCalculator;
/// <summary>Pure percentage logic, ported from the canonical TS lib.
/// 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 decimals.</summary>
public static class Percentage
{
/// <summary>Rounding precision when an option is omitted, matching
/// DEFAULT_MAX_DECIMALS in src/lib/percentage.ts.</summary>
public const int DefaultMaxDecimals = 2;
/// <summary>2^-52 — identical to JavaScript's Number.EPSILON.
/// NOT double.Epsilon, which in .NET is the smallest subnormal
/// (~4.9e-324) — a common porting trap.</summary>
private const double JsEpsilon = 2.2204460492503131e-16;
/// <summary>Options governing result precision. null means "omitted" →
/// the default, keeping an explicit 0 distinct from an unset value
/// (the TS optional-field semantics).</summary>
public sealed record PercentOptions(int? MaxDecimals = null);
/// <summary>Round to at most <paramref name="maxDecimals"/> places,
/// absorbing binary float noise via the epsilon nudge (the classic
/// 0.005 → 0.01 case). Non-finite values pass through unchanged.</summary>
public static double Round(double n, int maxDecimals = DefaultMaxDecimals)
{
if (!double.IsFinite(n)) return n;
double factor = Math.Pow(10, maxDecimals);
// Math.Floor(x + 0.5) replicates JavaScript's Math.round (half toward
// +infinity) — NOT Math.Round, whose default is banker's rounding and
// whose MidpointRounding.AwayFromZero sends negative .5 ties down.
return Math.Floor((n + JsEpsilon) * factor + 0.5) / factor;
}
/// <summary>X% of <paramref name="value"/>: pct / 100 * value.
/// null when either input is non-finite.</summary>
public static double? PercentOf(double pct, double value, PercentOptions? opts = null)
{
if (!double.IsFinite(pct) || !double.IsFinite(value)) return null;
return Round((pct / 100) * value, opts?.MaxDecimals ?? DefaultMaxDecimals);
}
/// <summary>What percentage <paramref name="part"/> is of
/// <paramref name="total"/>: part / total * 100. null when total is 0
/// (undefined ratio) or either input is non-finite.</summary>
public static double? WhatPercent(double part, double total, PercentOptions? opts = null)
{
if (!double.IsFinite(part) || !double.IsFinite(total) || total == 0) return null;
return Round((part / total) * 100, opts?.MaxDecimals ?? DefaultMaxDecimals);
}
/// <summary>Percentage change from <paramref name="from"/> to
/// <paramref name="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.</summary>
public static double? PercentChange(double from, double to, PercentOptions? opts = null)
{
if (!double.IsFinite(from) || !double.IsFinite(to) || from == 0) return null;
return Round(((to - from) / Math.Abs(from)) * 100, opts?.MaxDecimals ?? DefaultMaxDecimals);
}
}
public static class Demo
{
public static void Main()
{
var omitted = new Percentage.PercentOptions(); // default → 2 decimals
var zero = new Percentage.PercentOptions(MaxDecimals: 0); // explicit 0 ≠ omitted
Show("15% of 200", Percentage.PercentOf(15, 200, omitted));
Show("30 is what % of 200", Percentage.WhatPercent(30, 200, omitted));
Show("change 40 -> 50", Percentage.PercentChange(40, 50, omitted));
Show("change 50 -> 40", Percentage.PercentChange(50, 40, omitted));
Show("15% of 37, 0 decimals", Percentage.PercentOf(15, 37, zero));
Show("30 is what % of 0", Percentage.WhatPercent(30, 0, omitted));
Show("change 0 -> 50", Percentage.PercentChange(0, 50, omitted));
}
private static void Show(string label, double? v) =>
Console.WriteLine($"{label,-24} = {(v is null ? "null" : v.Value.ToString("0.############"))}");
}
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 →