Percentage change

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html><html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Percentage change</title><style>*{box-sizing:border-box} :root{color-scheme:light;--ink:#20303b;--muted:#536570;--line:#d3dce1;--accent:#245c76;--paper:#fff;--soft:#edf3f6}body{margin:0;background:#f3f5f5;color:var(--ink);font:16px/1.5 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{max-width:1080px;margin:auto;padding:30px 32px 64px}header{margin-bottom:20px}h1{font-size:clamp(1.5rem,3vw,2.15rem);line-height:1.18;letter-spacing:-.035em;margin:0 0 8px}h2{font-size:1.22rem;line-height:1.3;margin:0 0 16px}h3{font-size:1rem;margin:0 0 8px}p{margin:8px 0 16px}small,.muted{color:var(--muted)}.eyebrow{font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:750;color:var(--accent);margin-bottom:8px}.panel{background:var(--paper);border:1px solid var(--line);padding:24px;border-radius:16px}.grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:24px}.stack{display:grid;gap:18px}.fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}label{display:block;font-size:.9rem;font-weight:650}input,select,textarea,button{font:inherit}input,select,textarea{display:block;width:100%;min-width:0;margin-top:6px;border:1px solid #8b9ca6;border-radius:7px;padding:10px 12px;background:white;color:var(--ink)}input,select{min-height:46px}textarea{resize:vertical}button{border:1px solid var(--accent);border-radius:7px;padding:10px 16px;min-height:44px;background:var(--accent);color:white;cursor:pointer;font-weight:650}button.secondary{background:white;color:var(--accent)}button:hover{filter:brightness(.94)}:focus-visible{outline:3px solid #d06e1c;outline-offset:3px}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}.answer{background:var(--ink);color:white;padding:26px;border-radius:14px}.answer .muted{color:#d9e5eb}.big{font-size:clamp(2.1rem,5vw,3.8rem);font-weight:750;line-height:1.16;letter-spacing:-.045em;margin:8px 0;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.subresult{font-size:1.2rem;font-weight:650} .formula{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere}.note{border-left:3px solid #b47029;padding:10px 14px;background:#fff5e5;margin:14px 0;color:#66451b}.error{color:#a22621;font-weight:650;min-height:24px;margin:8px 0}.below{margin-top:28px}table{border-collapse:collapse;width:100%;font-size:.94rem}th{text-align:left;color:var(--muted);font-weight:650;font-size:.84rem}th,td{padding:11px 10px;border-bottom:1px solid var(--line);vertical-align:top}th:first-child,td:first-child{padding-left:0}th:last-child,td:last-child{padding-right:0}details{border-top:1px solid var(--line);padding:16px 0}summary{cursor:pointer;font-weight:650}svg{display:block;width:100%;height:auto}.tag{display:inline-block;font-size:.78rem;font-weight:650;padding:3px 8px;border-radius:30px;background:var(--soft);color:var(--accent)}.rule{height:1px;background:var(--line);margin:20px 0}.checklabel{display:flex;gap:10px;align-items:flex-start;font-weight:500}.checklabel input{width:20px;min-height:20px;flex:none;margin:2px 0}pre{white-space:pre-wrap;word-break:break-word;font: .86rem/1.6 ui-monospace,Consolas,monospace;background:#edf1f3;padding:20px;border-radius:8px}a{color:var(--accent)}[hidden]{display:none!important}@media(max-width:720px){main{padding:22px 20px 40px}.grid{grid-template-columns:1fr;gap:18px}.panel{padding:20px}.answer{padding:22px}.big{font-size:2.65rem}}@media(max-width:400px){main{padding:18px 16px 36px}.fields{gap:12px}th,td{padding:9px 6px}.panel{padding:16px}.big{font-size:2.25rem}}@media print{body{background:white}main{max-width:none;padding:0}.actions,button{display:none}.panel{break-inside:avoid}details>*{display:block}}.change-result{display:grid;grid-template-columns:1.2fr 1fr;background:#fff;border-top:6px solid #a45d36;border-bottom:1px solid #d3dce1;padding:26px 24px;gap:24px}.change-result .big{color:#944b26}.change-breakdown{display:grid;align-content:center;gap:16px;border-left:1px solid var(--line);padding-left:24px}.change-breakdown b{display:block;font-size:1.4rem}@media(max-width:520px){.change-result{grid-template-columns:1fr;padding:20px;gap:16px}.change-breakdown{grid-template-columns:1fr 1fr;border-left:0;border-top:1px solid var(--line);padding:16px 0 0}.change-breakdown b{font-size:1.1rem}}</style></head><body><main><header><div class="eyebrow">Original → new</div><h1>Percentage change</h1></header>
<p>Measure the signed change against the original value.</p>
<section class="change-result" aria-live="polite"><div><div class="muted">Relative change</div><div id="result" class="big">+25%</div><div id="direction">Increase relative to 20 pages</div></div><div class="change-breakdown"><div><span class="muted">Signed difference</span><b id="gap">+5 pages</b></div><div><span class="muted">Original denominator</span><b id="denom">20 pages</b></div></div></section>
<section class="panel below"><div class="fields"><label for="old">Original value<input id="old" inputmode="decimal" value="20" ></label><label for="next">New value<input id="next" inputmode="decimal" value="25" ></label><label for="mode">Comparison mode<select id="mode"><option value="quantity">Quantity</option><option value="rate">Percentage values</option></select></label><label for="unit">Common unit / metric<input id="unit" value="pages"></label></div><p id="modeHint" class="muted">Use the same unit and counting rule for both values.</p><label for="basis">Comparison basis<input id="basis" value="Same fictional guide; same definition of a counted page"></label><div class="actions"><button type="button" id="calculate">Calculate</button><button type="button" id="reverse" class="secondary">Reverse values</button><button type="button" id="reset" class="secondary">Reset example</button></div><p id="error" class="error" role="status"></p></section>
<section class="below panel"><h2>Worked comparison record</h2><p id="record" class="formula"></p><p id="basisRecord"></p><p id="rounding" class="muted"></p></section>
<details class="below"><summary>Zero baselines and input limits</summary><p>An original value of zero gives an undefined relative change, while its signed difference remains valid. In percentage-values mode enter 50 for 50%. A 50% → 60% comparison gives +10 percentage points and +20% relative change.</p><p>Accepts 0–1,000,000, up to six decimal places. No commas, exponents, negative inputs, or blank values. Calculations use exact decimal fractions; final displays use up to six places with ties away from zero. The fictional page-count example says nothing about quality. Entries are not saved.</p></details></main><script>
const $=id=>document.getElementById(id);const put=(id,text)=>{$(id).textContent=text};const fmt=(x,n=6)=>{if(!Number.isFinite(x))throw Error('Result exceeds the numeric range.');let v=(Math.round(Math.abs(x)*10**n)/10**n)*(x<0?-1:1);return Object.is(v,-0)?'0':v.toFixed(n).replace(/\.?0+$/,'')||'0'};const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function decimal(s,{max=1000000,signed=false,places=6}={}){s=String(s).trim();if(!s)throw Error('Enter a value; a blank field is not zero.');const r=new RegExp('^'+(signed?'[+-]?':'')+'(?:[0-9]+(?:\\.[0-9]{1,'+places+'})?|\\.[0-9]{1,'+places+'})$');if(!r.test(s))throw Error('Use a '+(signed?'signed':'nonnegative')+' US decimal with up to '+places+' decimal places; no commas or exponents.');const v=Number(s);if(!Number.isFinite(v)||Math.abs(v)>max)throw Error('Enter a value within '+(signed?'-':'0 to ')+max+'.');return v;}
function scaled(s){decimal(s);const [a,b='']=String(s).trim().split('.');return BigInt(a||'0')*1000000n+BigInt(b.padEnd(6,'0'));}
function rational(n,d,places=6){if(d===0n)throw Error('Zero denominator');let neg=(n<0n)!=(d<0n);n=n<0n?-n:n;d=d<0n?-d:d;const scale=10n**BigInt(places),v=n*scale,q=v/d,r=v%d;let rounded=q+(r*2n>=d?1n:0n);let s=(rounded/scale).toString();const f=(rounded%scale).toString().padStart(places,'0').replace(/0+$/,'');if(f)s+='.'+f;return {text:(neg&&rounded!==0n?'-':'')+s,approx:r!==0n};}
function bind(ids,fn){ids.forEach(id=>{ $(id).addEventListener('input',fn);$(id).addEventListener('change',fn); });}

function change(a,b){const old=scaled(a),next=scaled(b),delta=next-old;return {gap:rational(delta,1000000n),relative:old===0n?null:rational(delta*100n,old),denominator:String(a).trim()};}
const signed=s=>s==='0'?'0':s[0]==='-'?s:'+'+s;
function update(){put('modeHint',$('mode').value==='rate'?'Enter 50 for 50%. The direct gap is in percentage points.':'Use the same unit and counting rule for both values.');try{const r=change($('old').value,$('next').value),rate=$('mode').value==='rate',unit=rate?'percentage points':$('unit').value.trim()||'units',denUnit=rate?'%':' '+unit;put('result',r.relative?signed(r.relative.text)+'%':'Undefined');put('direction',r.relative?(Number(r.gap.text)>0?'Increase':Number(r.gap.text)<0?'Decrease':'No change')+' relative to '+r.denominator+denUnit:'Original value is zero; relative change has no denominator.');put('gap',signed(r.gap.text)+' '+unit);put('denom',r.denominator+denUnit);put('record',$('next').value+' − '+$('old').value+' = '+signed(r.gap.text)+' '+unit+'. '+(r.relative?'100 × ('+$('next').value+' − '+$('old').value+') / '+$('old').value+' = '+signed(r.relative.text)+'%':'Relative change undefined: division by zero.'));put('basisRecord','Basis: '+($('basis').value.trim()||'Not supplied; comparability needs confirmation.'));put('rounding',r.relative&&r.relative.approx?'Relative result rounded to six decimal places.':'Exact decimal result'+(r.relative?'':' for the signed difference')+'.');put('error','');}catch(e){['result','gap','denom'].forEach(id=>put(id,'—'));['direction','record','basisRecord','rounding'].forEach(id=>put(id,''));put('error',e.message);}}
bind(['old','next','mode','unit','basis'],update);$('calculate').addEventListener('click',update);$('reverse').addEventListener('click',()=>{const v=$('old').value;$('old').value=$('next').value;$('next').value=v;update()});$('reset').addEventListener('click',()=>{$('old').value='20';$('next').value='25';$('mode').value='quantity';$('unit').value='pages';$('basis').value='Same fictional guide; same definition of a counted page';update()});globalThis.Tool={change,update,rational};update();
</script></body></html>