{
  "schemaVersion": 1,
  "slug": "scientific-calculator",
  "category": "math",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "ed408a9116e584ecd5befde94c62ff22192da6baacc6f7cfc510d9f09d7466ab",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/scientific-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "y = f( ), ( ) → ^ → (−)_u → × ÷ % → + −",
    "vars": [
      [
        "E",
        "Expression text"
      ],
      [
        "y",
        "Computed numeric result"
      ],
      [
        "f",
        "Function: sqrt, ln, log, sin, exp…"
      ],
      [
        "→",
        "Precedence order, tightest binding first"
      ],
      [
        "^",
        "Exponent operator"
      ],
      [
        "(−)_u",
        "Unary minus, applied before its operand"
      ],
      [
        "%",
        "Remainder operator"
      ]
    ],
    "note": "A bounded 200-character recursive-descent parser accepts arithmetic, powers, parentheses, pi, e and the listed Math functions; it does not evaluate arbitrary JavaScript. Trigonometric input and inverse output use the selected angle mode."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-12",
    "sources": [
      {
        "name": "OpenStax — Arithmetic operations",
        "url": "https://openstax.org/books/prealgebra-2e/pages/1-5-divide-whole-numbers"
      }
    ],
    "referenceRole": "definition",
    "implementationHash": "ed408a9116e584ecd5befde94c62ff22192da6baacc6f7cfc510d9f09d7466ab",
    "basis": "y = f( ), ( ) → ^ → (−)_u → × ÷ % → + −",
    "limitations": "The references explain definitions and methods; any extra preset must be checked against the assumptions on this page."
  },
  "contract": {
    "compute": "const src=String(expression).replace(/\\s+/g,''); const toRad=x=>angleMode==='degrees'?x*Math.PI/180:x; const fromRad=x=>angleMode==='degrees'?x*180/Math.PI:x; const F={sqrt:Math.sqrt,abs:Math.abs,exp:Math.exp,round:Math.round,floor:Math.floor,ceil:Math.ceil,ln:Math.log,log:Math.log10,sin:x=>Math.sin(toRad(x)),cos:x=>Math.cos(toRad(x)),tan:x=>Math.tan(toRad(x)),asin:x=>fromRad(Math.asin(x)),acos:x=>fromRad(Math.acos(x)),atan:x=>fromRad(Math.atan(x))}; const used=[]; let i=0; const eat=ch=>{if(src[i]===ch){i++;return true;}return false;}; const parseExpr=()=>{let v=parseTerm();for(;;){if(eat('+'))v+=parseTerm();else if(eat('-'))v-=parseTerm();else break;}return v;}; const parseTerm=()=>{let v=parseUnary();for(;;){if(eat('*'))v*=parseUnary();else if(eat('/'))v/=parseUnary();else if(eat('%'))v%=parseUnary();else break;}return v;}; const parseUnary=()=>{if(eat('-'))return-parseUnary();if(eat('+'))return parseUnary();return parsePower();}; const parsePower=()=>{const b=parseAtom();return eat('^')?Math.pow(b,parseUnary()):b;}; const parseAtom=()=>{if(eat('(')){const v=parseExpr();if(!eat(')'))throw Error('paren');return v;} if(src.startsWith('pi',i)){i+=2;return Math.PI;} if(src[i]==='e'&&!/[a-z0-9]/.test(src[i+1]||'')){i++;return Math.E;} for(const name of Object.keys(F)){if(src.startsWith(name,i)&&src[i+name.length]==='('){i+=name.length+1;used.push(name);const arg=parseExpr();if(!eat(')'))throw Error('function paren');return F[name](arg);}} let j=i;while(j<src.length&&/[0-9.]/.test(src[j]))j++;if(j>i){const token=src.slice(i,j);if(!/^(?:\\d+(?:\\.\\d*)?|\\.\\d+)$/.test(token))throw Error('number');i=j;return Number(token);}throw Error('token');}; try{if(!src||src.length>200)throw Error('empty');const value=parseExpr();if(i!==src.length||!Number.isFinite(value))throw Error('invalid');return{result:value,rounded:value.toFixed(Math.max(0,Math.min(12,Math.round(roundTo)))),notation:value===0?'0':value.toExponential(4),functionsUsed:used.length?used.join(', '):'None — arithmetic only',angleModeUsed:angleMode,magnitude:value!==0?Math.floor(Math.log10(Math.abs(value))):0};}catch(_){return{result:null,rounded:null,notation:null,functionsUsed:null,angleModeUsed:null,magnitude:null,__error:'inputError'};}",
    "inputs": [
      {
        "name": "expression",
        "type": "text",
        "default": "sqrt(16) + sin(30) * 2",
        "step": 1
      },
      {
        "name": "angleMode",
        "type": "select",
        "default": "degrees",
        "options": [
          {
            "value": "degrees"
          },
          {
            "value": "radians"
          }
        ]
      },
      {
        "name": "roundTo",
        "type": "number",
        "default": 6,
        "min": 0,
        "max": 12,
        "step": 1,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "result",
        "precision": 8,
        "unit": ""
      },
      {
        "name": "rounded",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "notation",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "functionsUsed",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "angleModeUsed",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "magnitude",
        "precision": 0,
        "unit": ""
      }
    ],
    "interpret": null
  }
}
