Skill: trig-ratios
Right Triangle Solver
Find a missing side from an angle, find an angle from two sides, or get all three trig ratios — with every step shown.
Find a missing side
Give an acute angle and one known side; pick which side to solve for. Sides are labeled relative to the angle.
Find an angle
Give two sides (labeled relative to the angle you want). The answer comes from inverse trig, in degrees.
All three ratios
Enter all three sides of a right triangle to get sin, cos, and tan as fractions and decimals.
'use strict'; /* === PURE FUNCTIONS (extracted by tests/test_tool.js; keep DOM-free) === */ function trD2r(d){ return d * Math.PI / 180; } function trR2d(r){ return r * 180 / Math.PI; } function trRound2(v){ return Math.round(v * 100) / 100; } function trPos(v){ return !(!(typeof v === 'number') || !isFinite(v) || !(v > 0)); } function trGcd(a, b){ a = Math.abs(Math.round(a)); b = Math.abs(Math.round(b)); while (b){ var t = a % b; a = b; b = t; } return a || 1; } function trFracText(num, den){ var g = trGcd(num, den); return Math.round(num / g) + '/' + Math.round(den / g); } /* exact trig value at a special angle, or null */ function trExact(fn, deg){ var table = { 30: { sin: '1/2', cos: '\u221A3/2', tan: '\u221A3/3' }, 45: { sin: '\u221A2/2', cos: '\u221A2/2', tan: '1' }, 60: { sin: '\u221A3/2', cos: '1/2', tan: '\u221A3' } }; var row = table[Math.round(deg)]; if (!row) return null; if (Math.abs(deg - Math.round(deg)) > 1e-9) return null; return row[fn] || null; } /* Solve for target side. knownWhich/targetWhich in {'opp','adj','hyp'}. Returns {value, steps:[...], exact} or {error}. */ function trSolveSide(deg, knownWhich, knownVal, targetWhich){ if (!(deg > 0) || !(deg < 90)) return { error: 'Angle must be between 0\u00B0 and 90\u00B0 (exclusive).' }; if (!trPos(knownVal)) return { error: 'Side lengths must be positive numbers.' }; if (knownWhich === targetWhich) return { error: 'Pick a different side to solve for.' }; var r = trD2r(deg), s = Math.sin(r), c = Math.cos(r), t = Math.tan(r); /* express every side via the hypotenuse: opp = H s, adj = H c */ var H; if (knownWhich === 'hyp') H = knownVal; else if (knownWhich === 'opp') H = knownVal / s; else H = knownVal / c; var vals = { hyp: H, opp: H * s, adj: H * c }; var value = vals[targetWhich]; var kn = knownWhich === 'opp' ? 'opposite' : knownWhich === 'adj' ? 'adjacent' : 'hypotenuse'; var tg = targetWhich === 'opp' ? 'opposite' : targetWhich === 'adj' ? 'adjacent' : 'hypotenuse'; var steps = [ 'Write the ratio that links the ' + kn + ' (' + knownVal + ') to the ' + tg + '.', 'Solve for the ' + tg + ': ' + tg + ' = ' + trRound2(value) + '.' ]; var exact = null; var soPair = !(!(knownWhich === 'opp' || knownWhich === 'hyp') || !(targetWhich === 'opp' || targetWhich === 'hyp') || knownWhich === targetWhich); var caPair = !(!(knownWhich === 'adj' || knownWhich === 'hyp') || !(targetWhich === 'adj' || targetWhich === 'hyp') || knownWhich === targetWhich); var fnUsed = soPair ? 'sin' : caPair ? 'cos' : 'tan'; var ex = trExact(fnUsed, deg); if (ex) exact = 'Exact form uses ' + fnUsed + ' ' + Math.round(deg) + '\u00B0 = ' + ex + '.'; return { value: trRound2(value), steps: steps, exact: exact }; } /* Solve for the angle (degrees) from two labeled sides. Returns {value, steps:[...]} or {error}. */ function trSolveAngle(v1, w1, v2, w2){ if (!trPos(v1) || !trPos(v2)) return { error: 'Side lengths must be positive numbers.' }; if (w1 === w2) return { error: 'The two sides must be different (opposite, adjacent, or hypotenuse).' }; var o = w1 === 'opp' ? v1 : w2 === 'opp' ? v2 : null; var a = w1 === 'adj' ? v1 : w2 === 'adj' ? v2 : null; var h = w1 === 'hyp' ? v1 : w2 === 'hyp' ? v2 : null; if (h !== null){ var leg = o !== null ? o : a; if (!(leg < h)) return { error: 'The hypotenuse must be the longest side.' }; } var ratio, inv, deg; if (!(!(o !== null) || !(h !== null))){ ratio = o / h; if (!(ratio < 1)) return { error: 'Opposite cannot exceed the hypotenuse.' }; deg = trR2d(Math.asin(ratio)); inv = 'sin\u207B\u00B9'; } else if (!(!(a !== null) || !(h !== null))){ ratio = a / h; if (!(ratio < 1)) return { error: 'Adjacent cannot exceed the hypotenuse.' }; deg = trR2d(Math.acos(ratio)); inv = 'cos\u207B\u00B9'; } else { ratio = o / a; deg = trR2d(Math.atan(ratio)); inv = 'tan\u207B\u00B9'; } var desc = !(!(o !== null) || !(h !== null)) ? 'sin \u03B8 = opposite/hypotenuse = ' + v1 + '/' + v2 : !(!(a !== null) || !(h !== null)) ? 'cos \u03B8 = adjacent/hypotenuse' : 'tan \u03B8 = opposite/adjacent'; return { value: trRound2(deg), steps: ['Identify the ratio: ' + desc + ' = ' + trRound2(ratio) + '.', 'Undo it with inverse trig: \u03B8 = ' + inv + '(' + trRound2(ratio) + ') \u2248 ' + trRound2(deg) + '\u00B0.'] }; } /* All three ratios from three sides. Returns {sin:{frac,dec},...} or {error}. */ function trAllRatios(o, a, h){ if (!trPos(o) || !trPos(a) || !trPos(h)) return { error: 'All three sides must be positive numbers.' }; if (!(o < h) || !(a < h)) return { error: 'The hypotenuse must be the longest side.' }; if (Math.abs(o * o + a * a - h * h) > 0.01 * h * h + 1e-9) return { error: 'These sides do not form a right triangle (a\u00B2 + b\u00B2 \u2260 c\u00B2).' }; function entry(num, den){ return { frac: trFracText(num, den), dec: trRound2(num / den) }; } return { sin: entry(o, h), cos: entry(a, h), tan: entry(o, a) }; } /* === END PURE FUNCTIONS === */
/* ---------- DOM wiring ---------- */ function showResult(id, html, okClass){ var box = document.getElementById(id); box.className = 'result show ' + okClass; box.innerHTML = html; } function esc(s){ return String(s).replace(/[<>&"]/g, function (c){ return { '<': '<', '>': '>', '&': '&', '"': '"' }[c]; }); } function stepsHTML(steps){ return '
- ' + steps.map(function (st){ return '
- ' + esc(st) + '
'; }).join('') + '
'; } function num(id){ return parseFloat(document.getElementById(id).value); } function sel(id){ return document.getElementById(id).value; }
document.getElementById('sGo').addEventListener('click', function(){ var r = trSolveSide(num('sAngle'), sel('sKnownWhich'), num('sKnownVal'), sel('sTarget')); if (r.error){ showResult('sRes', 'Check your inputs: ' + esc(r.error), 'err'); return; } var html = 'The missing side is:
' + stepsHTML(r.steps); if (r.exact) html += '
'; showResult('sRes', html, 'ok'); }); document.getElementById('aGo').addEventListener('click', function(){ var r = trSolveAngle(num('aSide1'), sel('aWhich1'), num('aSide2'), sel('aWhich2')); if (r.error){ showResult('aRes', 'Check your inputs: ' + esc(r.error), 'err'); return; } showResult('aRes', 'The angle is:
' + stepsHTML(r.steps), 'ok'); }); document.getElementById('rGo').addEventListener('click', function(){ var r = trAllRatios(num('rOpp'), num('rAdj'), num('rHyp')); if (r.error){ showResult('rRes', 'Check your inputs: ' + esc(r.error), 'err'); return; } var html = '
' + 'cos θ = ' + r.cos.frac + ' ≈ ' + r.cos.dec + '
' + 'tan θ = ' + r.tan.frac + ' ≈ ' + r.tan.dec + '
'; showResult('rRes', html, 'ok'); });
/* tabs */ var tabs = [document.getElementById('tab-side'), document.getElementById('tab-angle'), document.getElementById('tab-ratios')]; var panels = [document.getElementById('panel-side'), document.getElementById('panel-angle'), document.getElementById('panel-ratios')]; tabs.forEach(function (tab, i){ tab.addEventListener('click', function(){ tabs.forEach(function (t2, j){ t2.classList.toggle('active', i === j); t2.setAttribute('aria-selected', i === j ? 'true' : 'false'); panels[j].classList.toggle('active', i === j); }); }); });