<p class="hint">Pick a function below. The demo evaluates it using <b>interval arithmetic</b>: each step carries a <code>[lo, hi]</code> bracket that is guaranteed to contain the true result. Drag the slider to move the evaluation point and watch the bracket change.</p>
<div class="controls">
<label>Function:
<select id="fn-select">
<option value="poly">x² − 2x + 1 (= (x−1)²)</option>
<option value="trig">sin(x) + cos(x)</option>
<option value="chain">((x + 1) × (x − 1)) / x</option>
<option value="recip">1 / (1 + x²)</option>
</select>
</label>
<label>x = <span id="x-val">0.00</span>
<input type="range" id="x-slider" min="-3" max="3" step="0.05" value="0">
</label>
</div>
<div class="steps" id="steps"></div>
<div class="bar-row">
<div class="bar-label">True value</div>
<div class="bar-wrap"><div class="bar true-bar" id="true-bar"></div><span class="bar-val" id="true-val"></span></div>
</div>
<div class="bar-row">
<div class="bar-label">Interval [lo, hi]</div>
<div class="bar-wrap"><div class="bar iv-bar" id="iv-bar"></div><span class="bar-val" id="iv-val"></span></div>
</div>
<div class="width-row">Interval width: <strong id="iv-width">—</strong> (guaranteed to contain the true value)</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: .93rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .9rem; }
label { display: flex; align-items: center; gap: .5rem; font-size: .9rem; }
select { font-size: .9rem; padding: .25rem .4rem; border-radius: 6px; border: 1px solid #bbb; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.steps { background: #f4f7fa; border-radius: 8px; padding: .6rem .9rem; margin-bottom: .9rem;
font-size: .85rem; line-height: 1.7; font-family: ui-monospace, monospace; }
.step-line { display: flex; gap: .6rem; }
.step-op { color: #457b9d; min-width: 14ch; }
.step-iv { color: #1d3557; font-weight: 600; }
.step-note { color: #666; font-size: .82rem; }
.bar-row { display: flex; align-items: center; gap: .7rem; margin-bottom: .4rem; }
.bar-label { min-width: 11ch; font-size: .82rem; color: #555; }
.bar-wrap { flex: 1; height: 22px; background: #e2e8f0; border-radius: 6px; position: relative; overflow: visible; }
.bar { position: absolute; height: 100%; border-radius: 6px; }
.true-bar { background: #1d3557; }
.iv-bar { background: #a8dadc; border: 2px solid #457b9d; }
.bar-val { position: absolute; right: -4px; top: 50%; transform: translate(100%, -50%);
font-size: .78rem; font-family: ui-monospace, monospace; white-space: nowrap; color: #333; padding-left: .4rem; }
.width-row { font-size: .86rem; color: #333; margin-top: .4rem; }
strong { color: #c92f3c; }
// Interval arithmetic: each interval is {lo, hi} with lo <= true_value <= hi.
// Basic operations guarantee containment by rounding lo DOWN and hi UP.
// We simulate directed rounding with a small eps = 2^-52 (machine epsilon).
const EPS = Math.pow(2, -52);
function ivLit(v) {
// Wrap a floating-point number in a tight interval accounting for rounding.
const d = Math.abs(v) * EPS + Number.MIN_VALUE;
return { lo: v - d, hi: v + d };
}
function ivAdd(a, b) { return { lo: a.lo + b.lo, hi: a.hi + b.hi }; }
function ivSub(a, b) { return { lo: a.lo - b.hi, hi: a.hi - b.lo }; }
function ivMul(a, b) {
const p = [a.lo*b.lo, a.lo*b.hi, a.hi*b.lo, a.hi*b.hi];
return { lo: Math.min(...p), hi: Math.max(...p) };
}
function ivDiv(a, b) {
if (b.lo <= 0 && b.hi >= 0) return { lo: -Infinity, hi: Infinity }; // division by interval containing 0
const q = [a.lo/b.lo, a.lo/b.hi, a.hi/b.lo, a.hi/b.hi];
return { lo: Math.min(...q), hi: Math.max(...q) };
}
function ivSin(a) {
// Tight enclosure: sample many points + endpoints
let lo = Infinity, hi = -Infinity;
const N = 64;
for (let i = 0; i <= N; i++) {
const t = a.lo + (a.hi - a.lo) * i / N;
const s = Math.sin(t);
lo = Math.min(lo, s); hi = Math.max(hi, s);
}
const d = (a.hi - a.lo) * EPS + EPS;
return { lo: lo - d, hi: hi + d };
}
function ivCos(a) {
let lo = Infinity, hi = -Infinity;
const N = 64;
for (let i = 0; i <= N; i++) {
const t = a.lo + (a.hi - a.lo) * i / N;
const c = Math.cos(t);
lo = Math.min(lo, c); hi = Math.max(hi, c);
}
const d = (a.hi - a.lo) * EPS + EPS;
return { lo: lo - d, hi: hi + d };
}
function ivSq(a) { return ivMul(a, a); }
// Each function returns {steps, trueVal} where steps = [{op, iv, note}]
const FUNS = {
poly(x) {
const iv = ivLit(x);
const s1 = ivSq(iv);
const s2 = ivMul(ivLit(2), iv);
const s3 = ivSub(s1, s2);
const s4 = ivAdd(s3, ivLit(1));
return {
trueVal: (x - 1) * (x - 1),
steps: [
{ op: 'x', iv, note: 'input x, wrapped in [lo, hi]' },
{ op: 'x²', iv: s1, note: 'x × x' },
{ op: '2x', iv: s2, note: '2 × x' },
{ op: 'x² − 2x', iv: s3, note: 'subtract' },
{ op: 'x² − 2x + 1', iv: s4, note: '+ 1 (result)' },
]
};
},
trig(x) {
const iv = ivLit(x);
const s1 = ivSin(iv);
const s2 = ivCos(iv);
const s3 = ivAdd(s1, s2);
return {
trueVal: Math.sin(x) + Math.cos(x),
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'sin(x)', iv: s1, note: 'sin enclosure' },
{ op: 'cos(x)', iv: s2, note: 'cos enclosure' },
{ op: 'sin+cos', iv: s3, note: 'add (result)' },
]
};
},
chain(x) {
if (Math.abs(x) < 0.01) x = x < 0 ? -0.01 : 0.01; // avoid /0 display
const iv = ivLit(x);
const s1 = ivAdd(iv, ivLit(1));
const s2 = ivSub(iv, ivLit(1));
const s3 = ivMul(s1, s2);
const s4 = ivDiv(s3, iv);
return {
trueVal: ((x + 1) * (x - 1)) / x,
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'x+1', iv: s1, note: 'add 1' },
{ op: 'x−1', iv: s2, note: 'subtract 1' },
{ op: '(x+1)(x−1)', iv: s3, note: 'multiply' },
{ op: '÷ x', iv: s4, note: 'divide by x (result)' },
]
};
},
recip(x) {
const iv = ivLit(x);
const s1 = ivSq(iv);
const s2 = ivAdd(ivLit(1), s1);
const s3 = ivDiv(ivLit(1), s2);
return {
trueVal: 1 / (1 + x * x),
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'x²', iv: s1, note: 'x × x' },
{ op: '1 + x²', iv: s2, note: 'add 1' },
{ op: '1/(1+x²)', iv: s3, note: 'divide (result)' },
]
};
}
};
function fmt(v) { return isFinite(v) ? v.toFixed(6) : (v > 0 ? '+∞' : '−∞'); }
function fmtIv(iv) { return '[' + fmt(iv.lo) + ', ' + fmt(iv.hi) + ']'; }
function render() {
const fn = document.getElementById('fn-select').value;
const x = parseFloat(document.getElementById('x-slider').value);
document.getElementById('x-val').textContent = x.toFixed(2);
const { steps, trueVal } = FUNS[fn](x);
const finalIv = steps[steps.length - 1].iv;
const width = finalIv.hi - finalIv.lo;
// Steps display
const stepsEl = document.getElementById('steps');
stepsEl.innerHTML = steps.map(s =>
`<div class="step-line"><span class="step-op">${s.op}</span><span class="step-iv">${fmtIv(s.iv)}</span><span class="step-note">← ${s.note}</span></div>`
).join('');
// Visual bar: map [lo, hi] into 0..100% of bar-wrap
const range = Math.max(Math.abs(finalIv.lo), Math.abs(finalIv.hi)) * 2.2 || 2;
const scale = (v) => ((v + range / 2) / range) * 100;
const trueBar = document.getElementById('true-bar');
const tp = scale(trueVal);
trueBar.style.left = tp + '%';
trueBar.style.width = '3px';
trueBar.style.transform = 'translateX(-50%)';
document.getElementById('true-val').textContent = fmt(trueVal);
const ivBar = document.getElementById('iv-bar');
const lp = Math.max(0, scale(finalIv.lo));
const hp = Math.min(100, scale(finalIv.hi));
ivBar.style.left = lp + '%';
ivBar.style.width = (hp - lp) + '%';
document.getElementById('iv-val').textContent = fmtIv(finalIv);
document.getElementById('iv-width').textContent = width < 1e-12 ? '< 1e-12 (near-exact)' : width.toExponential(3);
}
document.getElementById('fn-select').addEventListener('change', render);
document.getElementById('x-slider').addEventListener('input', render);
render();
<p class="hint">Pick a function below. The demo evaluates it using <b>interval arithmetic</b>: each step carries a <code>[lo, hi]</code> bracket that is guaranteed to contain the true result. Drag the slider to move the evaluation point and watch the bracket change.</p>
<div class="controls">
<label>Function:
<select id="fn-select">
<option value="poly">x² − 2x + 1 (= (x−1)²)</option>
<option value="trig">sin(x) + cos(x)</option>
<option value="chain">((x + 1) × (x − 1)) / x</option>
<option value="recip">1 / (1 + x²)</option>
</select>
</label>
<label>x = <span id="x-val">0.00</span>
<input type="range" id="x-slider" min="-3" max="3" step="0.05" value="0">
</label>
</div>
<div class="steps" id="steps"></div>
<div class="bar-row">
<div class="bar-label">True value</div>
<div class="bar-wrap"><div class="bar true-bar" id="true-bar"></div><span class="bar-val" id="true-val"></span></div>
</div>
<div class="bar-row">
<div class="bar-label">Interval [lo, hi]</div>
<div class="bar-wrap"><div class="bar iv-bar" id="iv-bar"></div><span class="bar-val" id="iv-val"></span></div>
</div>
<div class="width-row">Interval width: <strong id="iv-width">—</strong> (guaranteed to contain the true value)</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: .93rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .45rem; margin-bottom: .9rem; }
label { display: flex; align-items: center; gap: .5rem; font-size: .9rem; }
select { font-size: .9rem; padding: .25rem .4rem; border-radius: 6px; border: 1px solid #bbb; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.steps { background: #f4f7fa; border-radius: 8px; padding: .6rem .9rem; margin-bottom: .9rem;
font-size: .85rem; line-height: 1.7; font-family: ui-monospace, monospace; }
.step-line { display: flex; gap: .6rem; }
.step-op { color: #457b9d; min-width: 14ch; }
.step-iv { color: #1d3557; font-weight: 600; }
.step-note { color: #666; font-size: .82rem; }
.bar-row { display: flex; align-items: center; gap: .7rem; margin-bottom: .4rem; }
.bar-label { min-width: 11ch; font-size: .82rem; color: #555; }
.bar-wrap { flex: 1; height: 22px; background: #e2e8f0; border-radius: 6px; position: relative; overflow: visible; }
.bar { position: absolute; height: 100%; border-radius: 6px; }
.true-bar { background: #1d3557; }
.iv-bar { background: #a8dadc; border: 2px solid #457b9d; }
.bar-val { position: absolute; right: -4px; top: 50%; transform: translate(100%, -50%);
font-size: .78rem; font-family: ui-monospace, monospace; white-space: nowrap; color: #333; padding-left: .4rem; }
.width-row { font-size: .86rem; color: #333; margin-top: .4rem; }
strong { color: #c92f3c; }
// Interval arithmetic: each interval is {lo, hi} with lo <= true_value <= hi.
// Basic operations guarantee containment by rounding lo DOWN and hi UP.
// We simulate directed rounding with a small eps = 2^-52 (machine epsilon).
const EPS = Math.pow(2, -52);
function ivLit(v) {
// Wrap a floating-point number in a tight interval accounting for rounding.
const d = Math.abs(v) * EPS + Number.MIN_VALUE;
return { lo: v - d, hi: v + d };
}
function ivAdd(a, b) { return { lo: a.lo + b.lo, hi: a.hi + b.hi }; }
function ivSub(a, b) { return { lo: a.lo - b.hi, hi: a.hi - b.lo }; }
function ivMul(a, b) {
const p = [a.lo*b.lo, a.lo*b.hi, a.hi*b.lo, a.hi*b.hi];
return { lo: Math.min(...p), hi: Math.max(...p) };
}
function ivDiv(a, b) {
if (b.lo <= 0 && b.hi >= 0) return { lo: -Infinity, hi: Infinity }; // division by interval containing 0
const q = [a.lo/b.lo, a.lo/b.hi, a.hi/b.lo, a.hi/b.hi];
return { lo: Math.min(...q), hi: Math.max(...q) };
}
function ivSin(a) {
// Tight enclosure: sample many points + endpoints
let lo = Infinity, hi = -Infinity;
const N = 64;
for (let i = 0; i <= N; i++) {
const t = a.lo + (a.hi - a.lo) * i / N;
const s = Math.sin(t);
lo = Math.min(lo, s); hi = Math.max(hi, s);
}
const d = (a.hi - a.lo) * EPS + EPS;
return { lo: lo - d, hi: hi + d };
}
function ivCos(a) {
let lo = Infinity, hi = -Infinity;
const N = 64;
for (let i = 0; i <= N; i++) {
const t = a.lo + (a.hi - a.lo) * i / N;
const c = Math.cos(t);
lo = Math.min(lo, c); hi = Math.max(hi, c);
}
const d = (a.hi - a.lo) * EPS + EPS;
return { lo: lo - d, hi: hi + d };
}
function ivSq(a) { return ivMul(a, a); }
// Each function returns {steps, trueVal} where steps = [{op, iv, note}]
const FUNS = {
poly(x) {
const iv = ivLit(x);
const s1 = ivSq(iv);
const s2 = ivMul(ivLit(2), iv);
const s3 = ivSub(s1, s2);
const s4 = ivAdd(s3, ivLit(1));
return {
trueVal: (x - 1) * (x - 1),
steps: [
{ op: 'x', iv, note: 'input x, wrapped in [lo, hi]' },
{ op: 'x²', iv: s1, note: 'x × x' },
{ op: '2x', iv: s2, note: '2 × x' },
{ op: 'x² − 2x', iv: s3, note: 'subtract' },
{ op: 'x² − 2x + 1', iv: s4, note: '+ 1 (result)' },
]
};
},
trig(x) {
const iv = ivLit(x);
const s1 = ivSin(iv);
const s2 = ivCos(iv);
const s3 = ivAdd(s1, s2);
return {
trueVal: Math.sin(x) + Math.cos(x),
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'sin(x)', iv: s1, note: 'sin enclosure' },
{ op: 'cos(x)', iv: s2, note: 'cos enclosure' },
{ op: 'sin+cos', iv: s3, note: 'add (result)' },
]
};
},
chain(x) {
if (Math.abs(x) < 0.01) x = x < 0 ? -0.01 : 0.01; // avoid /0 display
const iv = ivLit(x);
const s1 = ivAdd(iv, ivLit(1));
const s2 = ivSub(iv, ivLit(1));
const s3 = ivMul(s1, s2);
const s4 = ivDiv(s3, iv);
return {
trueVal: ((x + 1) * (x - 1)) / x,
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'x+1', iv: s1, note: 'add 1' },
{ op: 'x−1', iv: s2, note: 'subtract 1' },
{ op: '(x+1)(x−1)', iv: s3, note: 'multiply' },
{ op: '÷ x', iv: s4, note: 'divide by x (result)' },
]
};
},
recip(x) {
const iv = ivLit(x);
const s1 = ivSq(iv);
const s2 = ivAdd(ivLit(1), s1);
const s3 = ivDiv(ivLit(1), s2);
return {
trueVal: 1 / (1 + x * x),
steps: [
{ op: 'x', iv, note: 'input x' },
{ op: 'x²', iv: s1, note: 'x × x' },
{ op: '1 + x²', iv: s2, note: 'add 1' },
{ op: '1/(1+x²)', iv: s3, note: 'divide (result)' },
]
};
}
};
function fmt(v) { return isFinite(v) ? v.toFixed(6) : (v > 0 ? '+∞' : '−∞'); }
function fmtIv(iv) { return '[' + fmt(iv.lo) + ', ' + fmt(iv.hi) + ']'; }
function render() {
const fn = document.getElementById('fn-select').value;
const x = parseFloat(document.getElementById('x-slider').value);
document.getElementById('x-val').textContent = x.toFixed(2);
const { steps, trueVal } = FUNS[fn](x);
const finalIv = steps[steps.length - 1].iv;
const width = finalIv.hi - finalIv.lo;
// Steps display
const stepsEl = document.getElementById('steps');
stepsEl.innerHTML = steps.map(s =>
`<div class="step-line"><span class="step-op">${s.op}</span><span class="step-iv">${fmtIv(s.iv)}</span><span class="step-note">← ${s.note}</span></div>`
).join('');
// Visual bar: map [lo, hi] into 0..100% of bar-wrap
const range = Math.max(Math.abs(finalIv.lo), Math.abs(finalIv.hi)) * 2.2 || 2;
const scale = (v) => ((v + range / 2) / range) * 100;
const trueBar = document.getElementById('true-bar');
const tp = scale(trueVal);
trueBar.style.left = tp + '%';
trueBar.style.width = '3px';
trueBar.style.transform = 'translateX(-50%)';
document.getElementById('true-val').textContent = fmt(trueVal);
const ivBar = document.getElementById('iv-bar');
const lp = Math.max(0, scale(finalIv.lo));
const hp = Math.min(100, scale(finalIv.hi));
ivBar.style.left = lp + '%';
ivBar.style.width = (hp - lp) + '%';
document.getElementById('iv-val').textContent = fmtIv(finalIv);
document.getElementById('iv-width').textContent = width < 1e-12 ? '< 1e-12 (near-exact)' : width.toExponential(3);
}
document.getElementById('fn-select').addEventListener('change', render);
document.getElementById('x-slider').addEventListener('input', render);
render();
This is fundamentally different from estimating a floating-point rounding error after the fact. The bracket is computed alongside the answer, at every step, in real time.
Comments
Loading comments...