{
  "schemaVersion": 1,
  "slug": "unit-price-calculator",
  "category": "everyday",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "4ff1885a5818563b9835d2403595d85948c92f5be16aa69f12b7dd0501f58940",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/unit-price-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": "b_A = S_A × k_A; b_B = S_B × k_B; u_A = P_A / b_A; u_B = P_B / b_B; save% = (u_max − u_min) / u_max × 100; saving = (u_max − u_min) × b_A × q",
    "vars": [
      [
        "P_A",
        "Price A"
      ],
      [
        "P_B",
        "Price B"
      ],
      [
        "S_A",
        "Size A"
      ],
      [
        "S_B",
        "Size B"
      ],
      [
        "u_A",
        "Unit price A"
      ],
      [
        "u_B",
        "Unit price B"
      ]
    ],
    "note": "Convert each quantity to grams, millilitres or its own count type, then divide price by that amount.  Saving percent = (higher unit price − lower unit price) ÷ higher unit price × 100; it is undefined if both prices are zero. Total saving compares equal quantities of the products, using one A package’s size times the selected count; it assumes fractional packages can be bought and does not round purchases to whole B packages. Equivalent B quantity uses B’s own unit and is undefined when B is free. Weight outputs for A use exactly 28.349523125 g per avoirdupois ounce and 453.59237 g per pound. Fluid ounces and mass-to-volume conversions are unsupported. Floating-point rounding differences within four machine epsilons are treated as ties. Choose the selected package unit or the base unit (g, ml, or the same count type) as the price basis; both displayed unit prices use that basis."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-31",
    "sources": [
      {
        "name": "Federal Trade Commission — How unit prices help compare products",
        "url": "https://consumer.ftc.gov/consumer-alerts/2024/10/case-shrinking-packaging"
      },
      {
        "name": "NIST Handbook 44 — exact mass conversion factors",
        "url": "https://nvlpubs.nist.gov/nistpubs/hb/2026/NIST.HB.44-2026.pdf"
      }
    ],
    "implementationHash": "4ff1885a5818563b9835d2403595d85948c92f5be16aa69f12b7dd0501f58940",
    "basis": "Convert each quantity to grams, millilitres or its own count type, then divide price by that amount.  Saving percent = (higher unit price − lower unit price) ÷ higher unit price × 100; it is undefined if both prices are zero. Total saving compares equal quantities of the products, using one A package’s size times the selected count; it assumes fractional packages can be bought and does not round purchases to whole B packages. Equivalent B quantity uses B’s own unit and is undefined when B is free. Weight outputs for A use exactly 28.349523125 g per avoirdupois ounce and 453.59237 g per pound. Fluid ounces and mass-to-volume conversions are unsupported. Floating-point rounding differences within four machine epsilons are treated as ties. Choose the selected package unit or the base unit (g, ml, or the same count type) as the price basis; both displayed unit prices use that basis.",
    "limitations": "Choose each package’s unit separately to compare prices for the same amount. Mass converts between oz, lb, g and kg; volume converts between ml and l. Counts, sheets and washes compare only with their own kind. Use one currency and include any taxes or shipping you want to compare in the entered prices."
  },
  "contract": {
    "compute": "const keys=['unitPriceA','unitPriceB','cheaperOption','savingsPercent','savingsPerUnit','priceGapPer100','equivalentSizeB','totalSaving','pricePerOz','pricePerLb','pricePerGram','pricePerKg'];\nconst bad=(key)=>Object.assign(Object.fromEntries(keys.map(k=>[k,null])),{__error:key});\nconst units={oz:['mass',28.349523125],lb:['mass',453.59237],g:['mass',1],kg:['mass',1000],ml:['volume',1],l:['volume',1000],ct:['ct',1],sheet:['sheet',1],wash:['wash',1]};\nconst uB=unitB==='same'?unit:unitB;\nif(!['entered','base'].includes(priceBasis))return bad('inputError');\nif(![priceA,priceB,sizeA,sizeB,quantity].every(Number.isFinite)||priceA<0||priceA>100000||priceB<0||priceB>100000||sizeA<0.001||sizeA>1000000||sizeB<0.001||sizeB>1000000||quantity<1||quantity>1000||!Number.isInteger(quantity)||!Object.prototype.hasOwnProperty.call(units,unit)||!Object.prototype.hasOwnProperty.call(units,uB))return bad('inputError');\nif(units[unit][0]!==units[uB][0])return bad('unitMismatchError');\nconst baseA=sizeA*units[unit][1],baseB=sizeB*units[uB][1],basis=priceBasis==='base'?1:units[unit][1];\nconst pA=priceA/baseA,pB=priceB/baseB,hi=Math.max(pA,pB),lo=Math.min(pA,pB),equal=Math.abs(pA-pB)<=4*Number.EPSILON*hi,gap=equal?0:hi-lo;\nif((priceA>0&&pA===0)||(priceB>0&&pB===0))return bad('inputError');\nconst mass=units[unit][0]==='mass';\nreturn {unitPriceA:pA*basis,unitPriceB:pB*basis,cheaperOption:equal?'Both cost the same per unit':pA<pB?'Option A':'Option B',savingsPercent:hi>0?gap/hi*100:null,savingsPerUnit:gap*basis,priceGapPer100:gap*basis*100,equivalentSizeB:pB>0?priceA/pB/units[uB][1]:null,totalSaving:gap*baseA*quantity,pricePerOz:mass?pA*28.349523125:null,pricePerLb:mass?pA*453.59237:null,pricePerGram:mass?pA:null,pricePerKg:mass?pA*1000:null,__priceUnit:'$/'+(priceBasis==='base'?(mass?'g':units[unit][0]==='volume'?'ml':unit):unit),__sizeBUnit:uB,__moneyUnit:'$'};",
    "inputs": [
      {
        "name": "priceA",
        "type": "number",
        "default": 4.99,
        "min": 0,
        "max": 100000,
        "step": 0.01,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "sizeA",
        "type": "number",
        "default": 16,
        "min": 0.001,
        "max": 1000000,
        "step": 0.001,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "unit",
        "type": "select",
        "default": "oz",
        "options": [
          {
            "value": "oz"
          },
          {
            "value": "lb"
          },
          {
            "value": "g"
          },
          {
            "value": "kg"
          },
          {
            "value": "ml"
          },
          {
            "value": "l"
          },
          {
            "value": "ct"
          },
          {
            "value": "sheet"
          },
          {
            "value": "wash"
          }
        ]
      },
      {
        "name": "priceB",
        "type": "number",
        "default": 8.49,
        "min": 0,
        "max": 100000,
        "step": 0.01,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "sizeB",
        "type": "number",
        "default": 32,
        "min": 0.001,
        "max": 1000000,
        "step": 0.001,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "unitB",
        "type": "select",
        "default": "same",
        "options": [
          {
            "value": "same"
          },
          {
            "value": "oz"
          },
          {
            "value": "lb"
          },
          {
            "value": "g"
          },
          {
            "value": "kg"
          },
          {
            "value": "ml"
          },
          {
            "value": "l"
          },
          {
            "value": "ct"
          },
          {
            "value": "sheet"
          },
          {
            "value": "wash"
          }
        ]
      },
      {
        "name": "quantity",
        "type": "number",
        "default": 1,
        "min": 1,
        "max": 1000,
        "step": 1,
        "requiredValue": true,
        "strictRange": true,
        "advanced": true
      },
      {
        "name": "resultKind",
        "type": "select",
        "default": "cheaperOption",
        "options": [
          {
            "value": "cheaperOption"
          },
          {
            "value": "unitPriceA"
          },
          {
            "value": "pricePerOz"
          }
        ],
        "advanced": true
      },
      {
        "name": "priceBasis",
        "type": "select",
        "default": "entered",
        "options": [
          {
            "value": "entered"
          },
          {
            "value": "base"
          }
        ],
        "advanced": true
      }
    ],
    "outputs": [
      {
        "name": "unitPriceA",
        "precision": 4,
        "unit": "",
        "notation": "auto",
        "unitField": "__priceUnit"
      },
      {
        "name": "unitPriceB",
        "precision": 4,
        "unit": "",
        "notation": "auto",
        "unitField": "__priceUnit"
      },
      {
        "name": "cheaperOption",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "savingsPercent",
        "precision": 1,
        "unit": "%",
        "optional": true,
        "notation": "auto"
      },
      {
        "name": "savingsPerUnit",
        "precision": 4,
        "unit": "",
        "notation": "auto",
        "unitField": "__priceUnit"
      },
      {
        "name": "priceGapPer100",
        "precision": 2,
        "unit": "",
        "notation": "auto",
        "unitField": "__moneyUnit"
      },
      {
        "name": "equivalentSizeB",
        "precision": 2,
        "unit": "",
        "optional": true,
        "notation": "auto",
        "unitField": "__sizeBUnit"
      },
      {
        "name": "totalSaving",
        "precision": 2,
        "unit": "$",
        "notation": "auto"
      },
      {
        "name": "pricePerOz",
        "precision": 4,
        "unit": "$/oz",
        "optional": true,
        "notation": "auto"
      },
      {
        "name": "pricePerLb",
        "precision": 4,
        "unit": "$/lb",
        "optional": true,
        "notation": "auto"
      },
      {
        "name": "pricePerGram",
        "precision": 5,
        "unit": "$/g",
        "optional": true,
        "notation": "auto"
      },
      {
        "name": "pricePerKg",
        "precision": 4,
        "unit": "$/kg",
        "optional": true,
        "notation": "auto"
      }
    ],
    "interpret": null
  }
}
