{
  "schemaVersion": 1,
  "slug": "mixed-number-calculator",
  "category": "math",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "9b119df2f977ec68e8cc29c4819ff32e9cd50da66d11299a27be95fa6cf217ac",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/mixed-number-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": "A = w₁D₁ + n₁, B = w₂D₂ + n₂; n = AD₂ ± BD₁, d = D₁D₂; n → n ÷ g, d → d ÷ g, g = gcd(n, d)",
    "vars": [
      [
        "A",
        "first number as improper numerator"
      ],
      [
        "B",
        "second improper numerator"
      ],
      [
        "w₁, w₂",
        "whole-number parts"
      ],
      [
        "n₁, n₂",
        "fractional numerators"
      ],
      [
        "D₁",
        "first denominator"
      ],
      [
        "D₂",
        "second denominator"
      ],
      [
        "n",
        "result numerator"
      ],
      [
        "d",
        "result denominator"
      ],
      [
        "g",
        "gcd for reduction"
      ]
    ],
    "note": "Sign follows the whole-number component. Improper negative results are split into a signed whole part plus a non-negative remainder; a negative proper fraction with whole part zero keeps its negative sign (for example, −1/2). Division by zero is rejected."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "if(![w1,n1,d1,w2,n2,d2].every(Number.isInteger)||d1<=0||d2<=0)return{mixedResult:'—',improper:'—',decimal:null,__error:'inputError'};const gcd=(x,y)=>{x=Math.abs(x);y=Math.abs(y);while(y){const t=y;y=x%y;x=t}return x||1};const s1=w1<0||(w1===0&&n1<0)?-1:1,s2=w2<0||(w2===0&&n2<0)?-1:1;const A=s1*(Math.abs(w1)*d1+Math.abs(n1)),B=s2*(Math.abs(w2)*d2+Math.abs(n2));let n,d;if(op==='add'){n=A*d2+B*d1;d=d1*d2}else if(op==='sub'){n=A*d2-B*d1;d=d1*d2}else if(op==='mul'){n=A*B;d=d1*d2}else{if(B===0)return{mixedResult:'—',improper:'—',decimal:null,__error:'inputError'};n=A*d2;d=d1*B}if(d<0){n=-n;d=-d}const g=gcd(n,d),sn=n/g,sd=d/g,whole=Math.trunc(sn/sd),rem=Math.abs(sn-whole*sd);return{mixedResult:rem===0?String(whole):whole===0?(sn<0?'-':'')+rem+'/'+sd:whole+' '+rem+'/'+sd,improper:sn+'/'+sd,decimal:sn/sd}",
    "inputs": [
      {
        "name": "w1",
        "type": "number",
        "default": 2,
        "min": -1000,
        "max": 1000,
        "step": 1,
        "slider": true
      },
      {
        "name": "n1",
        "type": "number",
        "default": 1,
        "min": -1000,
        "max": 1000,
        "step": 1,
        "slider": true
      },
      {
        "name": "d1",
        "type": "number",
        "default": 2,
        "min": 1,
        "max": 1000,
        "step": 1,
        "slider": true
      },
      {
        "name": "op",
        "type": "select",
        "default": "add",
        "options": [
          {
            "value": "add"
          },
          {
            "value": "sub"
          },
          {
            "value": "mul"
          },
          {
            "value": "div"
          }
        ]
      },
      {
        "name": "w2",
        "type": "number",
        "default": 1,
        "min": -1000,
        "max": 1000,
        "step": 1,
        "slider": true
      },
      {
        "name": "n2",
        "type": "number",
        "default": 3,
        "min": -1000,
        "max": 1000,
        "step": 1,
        "slider": true
      },
      {
        "name": "d2",
        "type": "number",
        "default": 4,
        "min": 1,
        "max": 1000,
        "step": 1,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "mixedResult",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "improper",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "decimal",
        "precision": 6,
        "unit": "",
        "trimTrailingZeros": true
      }
    ],
    "interpret": null
  }
}
