Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A discount calculator needs three pieces of arithmetic: the sale price is the original price multiplied by (1 − d/100), the savings are the original price minus the sale price, and the original price can be recovered by dividing the sale price by (1 − d/100). Stacked discounts multiply rather than add. The JavaScript below implements those rules, validates inputs before calculating, and formats the output for a locale and currency.

The headline’s $2,400 is the author’s own savings estimate. It is a personal claim, not a measured figure, and the code can only compute prices; it cannot show how much anyone actually saved.

The core formula

Sale price

For an original price P and a discount percentage d, the sale price is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
salePrice = P × (1 − d / 100)

A 25% discount on a $80 item gives 80 × (1 − 0.25) = 60. The percentage is divided by 100 first, so you never multiply by the raw number 25.

Savings

The amount saved is the difference between the two prices:

savings = P − salePrice

For the same $80 item at 25% off, savings are $20. Because savings is computed from the sale price, the two results always agree and you only need to validate the inputs once.

Working backwards from a sale price

When a shelf or checkout shows only the discounted price and the percentage, divide by the fraction that remains:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
originalPrice = salePrice / (1 − d / 100)

If $60 is the sale price after 25% off, the original is 60 ÷ 0.75 = 80. This formula only works when the discount is below 100%. At exactly 100% the divisor is zero, and above 100% the result is meaningless, so the code below rejects both.

Stacked discounts: multiply, don’t add

Two percentage discounts applied one after another act on the running price. The combined formula is:

final = price × (1 − discount1) × (1 − discount2)

Take a $100 item with 20% off and then an additional 10% off. The table shows how each step works.

Step Discount applied Multiplier Running price
Start none 1.00 100.00
First discount 20% 0.80 80.00
Second discount 10% (applied to 80.00) 0.90 72.00

The final price is 72, so the combined reduction is 28%, not 30%. Adding the two percentages would wrongly suggest a $70 price. The gap grows with larger discounts: 50% followed by 50% leaves 25% of the original, a 75% reduction rather than 100%.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Implementing the calculator

Keep three stages separate: parse and validate input, calculate with plain numbers, and format the result only when it is shown. Mixing them is the usual source of bugs, because form fields deliver text and text does not behave like a number in arithmetic.

Parse and validate input

Form values are strings. Converting an empty field with Number("") returns 0, which would silently produce a free item, so check for blank input before converting. Then confirm the result is a finite number. MDN documents Number.isFinite() as returning true only for finite values of type number, and false for NaN, positive or negative infinity, and non-number inputs. It does not coerce strings, so the parsing step must come first.

function parseNumber(text) {
  const trimmed = String(text).trim();
  if (trimmed === "") return NaN;
  return Number(trimmed);
}

Calculate with numbers only

function salePrice(original, percentOff) {
  if (!Number.isFinite(original) || !Number.isFinite(percentOff)) {
    throw new RangeError("Inputs must be finite numbers.");
  }
  if (original < 0 || percentOff < 0 || percentOff > 100) {
    throw new RangeError("Price must be zero or more; discount must be 0 to 100.");
  }
  return original * (1 - percentOff / 100);
}

function savings(original, percentOff) {
  return original - salePrice(original, percentOff);
}

function originalFromSale(sale, percentOff) {
  if (!Number.isFinite(sale) || !Number.isFinite(percentOff)) {
    throw new RangeError("Inputs must be finite numbers.");
  }
  if (percentOff <= 0 || percentOff >= 100) {
    throw new RangeError("Discount must be above 0 and below 100 to reverse.");
  }
  return sale / (1 - percentOff / 100);
}

function stackDiscounts(price, ...percents) {
  return percents.reduce((current, d) => salePrice(current, d), price);
}

Calling stackDiscounts(100, 20, 10) returns 72. Each discount is validated against the running price, so the same range checks apply at every step.

Format for display

Use Intl.NumberFormat with an explicit locale and currency code. MDN documents it as the locale-sensitive way to produce number and currency output, and it is preferable to concatenating a symbol onto a number, because separators, symbol placement, and fraction digits vary by locale.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function formatMoney(amount, locale, currency) {
  return new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount);
}

formatMoney(72, "en-US", "USD"); // "$72.00"

When no fraction-digit options are given, the default follows the currency’s minor-unit count from ISO 4217. Two digits is typical for USD and EUR; a currency with zero minor units, such as JPY, displays without decimals. Choose the locale based on where the shopper is, not on the code’s default.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Rounding is a policy decision

Formatting to two decimal places controls what the screen shows; it does not define how a store must round. Whether a merchant rounds each line item, the total, or tax, and in which direction, is set by its own checkout logic and by local rules. The MDN references above establish how the formatter behaves, not what any retailer or jurisdiction requires. If your calculator is meant to match a real bill, confirm the rounding method with the merchant or the relevant tax authority before relying on it.

Common failure modes

  • Blank field treated as zero. Handle empty strings before calling Number(), as shown above.
  • Discount above 100%. The sale price would be negative, and the reverse formula would divide by zero or a negative number. The validation rejects it.
  • Adding stacked discounts. 20% plus 10% is 28% of the original, not 30%.
  • Formatting before calculating. A string like “$72.00” cannot be used in further arithmetic, so keep the raw number until the last step.
  • Hard-coded currency symbol. Pass the locale and currency code to Intl.NumberFormat instead.

Where to read the primary sources

The arithmetic here is standard percentage math, and the worked examples can be checked against the online discount calculator at mathcheck.net, which covers sale price, stacked discounts, and reverse calculation. For the JavaScript behaviour, read the MDN reference for Number.isFinite() and the Intl.NumberFormat() constructor. Percentage-discount price examples also appear in the CBSE Academic Class XI JavaScript chapter, available at cbseacademic.nic.in.

Keep the savings claim in perspective

The calculator tells you what a discount is worth on a given price. Whether it saved $2,400 over a year depends on which purchases the author would otherwise have made, and no independent measurement of that figure is available. Treat the number as the author’s own estimate and use the functions above to check any individual deal.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.