Shopping maths looks trivial until you try to code it. "20% off, then an extra 10% with a voucher" is not 30% off. Removing VAT is not "minus 20%". And "free delivery over £50" can make you spend more than you save.
I recently helped build a set of free UK shopping calculators for PrimeVoucher, and here are the formulas behind each one, in plain JavaScript you can drop into your own project.
1. Basic discount
const salePrice = (price, pct) => price * (1 - pct / 100);
const saving = (price, pct) => price * (pct / 100);
salePrice(80, 25); // 60
Try it live: Discount Calculator
2. Stacked discounts (sale + voucher code)
The classic bug: adding percentages. Discounts multiply, they don't add.
const stacked = (price, ...pcts) =>
pcts.reduce((p, pct) => p * (1 - pct / 100), price);
const realPercentOff = (...pcts) =>
100 * (1 - pcts.reduce((m, pct) => m * (1 - pct / 100), 1));
realPercentOff(20, 10); // 28, not 30
Live version: Stacked Discount Calculator
3. "% off" vs "£ off" - which voucher wins?
A percentage code beats a fixed-amount code once the basket passes a break-even point:
const breakEven = (poundsOff, pctOff) => poundsOff / (pctOff / 100);
breakEven(10, 15); // £66.67 - above this, 15% off wins
Compare two codes: Voucher Comparison Calculator
4. Free delivery threshold
Is it worth adding an item to hit free shipping? Only if what you add is something you'd buy anyway, or costs less than delivery.
const shortfall = (basket, threshold) => Math.max(0, threshold - basket);
const worthTopUp = (basket, threshold, deliveryFee) =>
shortfall(basket, threshold) <= deliveryFee;
worthTopUp(42, 50, 4.99); // false - £8 top-up costs more than £4.99 delivery
5. UK VAT (add and remove)
Removing VAT means dividing by 1.2, not subtracting 20%.
const addVat = (net, rate = 20) => net * (1 + rate / 100);
const removeVat = (gross, rate = 20) => gross / (1 + rate / 100);
removeVat(120); // 100 (not 96!)
6. Price per unit
Normalise everything to the same unit before comparing multipacks.
const perUnit = (price, qty, unitSize = 1) => price / (qty * unitSize);
perUnit(3.5, 6, 330) * 100; // pence per 100ml for a 6x330ml pack
7. Voucher + cashback + gift card
Order matters: the voucher comes off first, and cashback is usually paid on the post-voucher amount (excluding delivery).
const realCost = ({ price, voucherPct = 0, cashbackPct = 0, giftCardDiscountPct = 0 }) => {
const afterVoucher = price * (1 - voucherPct / 100);
const cashback = afterVoucher * (cashbackPct / 100);
const paid = afterVoucher * (1 - giftCardDiscountPct / 100);
return paid - cashback;
};
8. Pay in 3 / Buy Now Pay Later
Split evenly, but put the rounding remainder on the first instalment so the total always adds up to the penny.
const instalments = (total, n) => {
const pence = Math.round(total * 100);
const base = Math.floor(pence / n);
const first = pence - base * (n - 1);
return [first, ...Array(n - 1).fill(base)].map(p => p / 100);
};
instalments(100, 3); // [33.34, 33.33, 33.33]
9. Sale savings tracker
const totalSaved = items =>
items.reduce((s, { was, now }) => s + (was - now), 0);
10. Annual savings
const yearly = (avgSavingPerOrder, ordersPerMonth) =>
avgSavingPerOrder * ordersPerMonth * 12;
A note on money and floating point
0.1 + 0.2 !== 0.3 in JavaScript. For anything shown to users, either work in integer pence (as in the Pay in 3 example) or round at the end with Math.round(x * 100) / 100. Display with Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).
All ten calculators are free to use at primevoucher.co.uk/calculators. If you spot an edge case I've missed, drop it in the comments.
Top comments (0)