{
  "schemaVersion": 1,
  "slug": "math-calculator",
  "category": "math",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "c20d1b722078ee6240bcd19d956d4dfb11db0b4db71bd69405d700f09baf0823",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/math-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": "v = term {(+ | −) term}; term = unary {(× | ÷ | %) unary}; unary = (−) atom ^ unary",
    "vars": [
      [
        "v",
        "evaluated result"
      ],
      [
        "term",
        "product-level subexpression"
      ],
      [
        "unary",
        "signed power"
      ],
      [
        "k",
        "rounding decimals, 0–12"
      ],
      [
        "atom",
        "number or parenthesized expression"
      ]
    ],
    "note": "Recursive-descent parser; precedence: parentheses → ^ (right-associative) → unary sign → × ÷ % → + −. Only decimal numbers are supported; rounded uses k fixed decimals."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const src=String(expression);let i=0;const skip=()=>{while(/\\s/.test(src[i]||''))i++};const eat=(ch)=>{skip();if(src[i]===ch){i++;return true}return false};const atom=()=>{skip();if(eat('(')){const v=expr();if(!eat(')'))throw Error('paren');return v}const m=src.slice(i).match(/^(?:\\d+\\.?\\d*|\\.\\d+)/);if(!m)throw Error('number');i+=m[0].length;return Number(m[0])};const power=()=>{const a=atom();return eat('^')?Math.pow(a,unary()):a};const unary=()=>eat('+')?unary():eat('-')?-unary():power();const term=()=>{let v=unary();for(;;){if(eat('*'))v*=unary();else if(eat('/'))v/=unary();else if(eat('%'))v%=unary();else break}return v};const expr=()=>{let v=term();for(;;){if(eat('+'))v+=term();else if(eat('-'))v-=term();else break}return v};try{if(!src.trim())throw Error('empty');const value=expr();skip();if(i!==src.length||!Number.isFinite(value))throw Error('invalid');const places=Math.max(0,Math.min(12,Math.round(roundTo)));return{result:value,rounded:value.toFixed(places),expressionUsed:src,operatorCount:(src.match(/[+\\-*/^%]/g)||[]).length,parenthesesDepth:(src.match(/\\(/g)||[]).length,isExact:Number.isInteger(value)?'Yes — a whole number':'No — has a fractional part'}}catch(e){return{result:null,rounded:'—',expressionUsed:src,operatorCount:0,parenthesesDepth:0,isExact:'—',__error:'inputError'}}",
    "inputs": [
      {
        "name": "expression",
        "type": "text",
        "default": "(12 + 8) * 3 - 10 / 2",
        "step": 1
      },
      {
        "name": "roundTo",
        "type": "number",
        "default": 2,
        "min": 0,
        "max": 12,
        "step": 1,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "result",
        "precision": 6,
        "unit": ""
      },
      {
        "name": "rounded",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "expressionUsed",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "operatorCount",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "parenthesesDepth",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "isExact",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
