Six Percentage Questions and the Formula Behind Each One
A receipt says 114 pounds, VAT included, and you want the price before VAT. Most people take 14% off and get 98.04. The real answer is 100. That gap is why I built the percentage calculator the way I did, with six questions side by side and the formula printed under every answer.
The arithmetic in each question is one line. Picking the right one is where people go wrong, and so is typing the number in a way a calculator understands. This article covers both.
The six questions
Most everyday percentage questions fit one of these. The page shows them all at once, filled with examples, so you can spot the one that sounds like yours.
| Question | Example | Formula |
|---|---|---|
| What is p% of y? | 15% of 200 | y × p ÷ 100 = 30 |
| x is what percent of y? | 425 out of 500 | x ÷ y × 100 = 85% |
| Change from a to b | 80 to 100 | (b − a) ÷ a × 100 = +25% |
| x is p% of what? | 30 is 15% of … | x ÷ p × 100 = 200 |
| y plus or minus p% | 250 plus 14% | y × (1 + p ÷ 100) = 285 |
| Amount before p% was added | 114 includes 14% | total ÷ (1 + p ÷ 100) = 100 |
Each one is a small function in utils/percent.ts that returns either a value or an error message. The errors are the cases with no answer. “x is what percent of 0” divides by zero, and so does a percentage change that starts at 0. For that one the message says why: any change from 0 is infinite.
One detail in the change formula: it divides by the absolute value of the starting number. Going from −50 to −25 is a rise, and the tests expect +50%, not −50%.
Percent and percentage points
When a rate goes from 10% to 12%, two different numbers describe it:
- It rose by 2 percentage points. That’s the plain difference, 12 − 10.
- It rose by 20 percent. That’s the change formula applied to the rate itself, (12 − 10) ÷ 10 × 100.
Both are correct. They answer different questions, and they’re easy to mix up. If you put 10 and 12 in the “change” row, the calculator gives 20%, because that’s the question the row asks. For percentage points you only need subtraction.
Two more traps belong here because they come from the same idea. A percentage always applies to some base, and the base moves:
- 20% off and then another 10% off is 28% off, not 30%. The second discount applies to the reduced price.
- A price that goes up 50% and then down 50% ends at 75% of where it started. The tests check this one.
Taking VAT back out
This is the question from the receipt. When VAT was added, the price before VAT was multiplied by 1.14. To undo it, you divide by 1.14:
export const removePercent = (total: number, p: number): Result => (p === -100 ? fail('The percentage can’t be −100.') : ok(total / (1 + p / 100)))
114 ÷ 1.14 = 100. Subtracting 14% instead means multiplying by 0.86, which gives 98.04. The two methods disagree because the 14% was calculated on 100, and subtracting 14% calculates it on 114.
The row also shows the VAT part on its own (“The 14% part: 14”), which is what you need for an expense claim. The only percentage it refuses is −100, which would divide by zero.
The same function works for any amount that had a percentage added on top: a service charge, a tip, a markup. The page’s FAQ lists some standard VAT rates (14% in Egypt, 15% in Saudi Arabia, 5% in the UAE, 20% in the UK). Reduced rates exist for some goods, so check your invoice when it matters.
Reading numbers the way people type them
People don’t all type numbers the same way. Some write 1,250 for one thousand two hundred fifty. Some write 12,5 for twelve and a half, because a comma is their decimal separator. On an Arabic keyboard, the same number may arrive as ١٢٥٠٫٥. A calculator that only accepts 1250.5 turns all of these into errors.
parseNumber handles them in a fixed order. First it replaces Arabic-Indic and Persian digits with Latin ones, turns the Arabic decimal separator ٫ into a dot, and removes the separators that only group thousands (the Arabic ٬, spaces and apostrophes). The digit conversion uses a small trick: the code points for ٠ to ٩ (U+0660 to U+0669) and for ۰ to ۹ (U+06F0 to U+06F9) both end in the digit’s value, so keeping the low four bits gives the digit.
Then comes the hard part, the comma:
const thousands = /^-?\d{1,3}(,\d{3})+%?$/.test(s)
s = !s.includes('.') && !thousands && (s.match(/,/g) ?? []).length === 1 ? s.replace(',', '.') : s.replace(/,/g, '')
A comma is read as a thousands separator when the number looks like one: one to three digits, then groups of exactly three (1,250 or 1,250,000). It’s read as a decimal comma when it’s the only comma, there’s no dot, and the number doesn’t fit the thousands pattern (12,5 or 0,75). In every other case the commas are dropped.
That rule has one honest blind spot. “1,250” could be one and a quarter in a country that writes decimals with a comma, and the parser reads it as 1250. I chose that because three digits after a single comma is much more likely to be a thousands group in the prices and marks people type here. The formula under each answer prints the numbers as they were read, so 12,5 shows up as 12.5, and an answer a thousand times too big is hard to miss. In the same spirit, a dot is always a decimal point, so “1.250” is 1.25.
After the comma, the parser drops a trailing %, a leading +, and turns the Unicode minus sign − into a hyphen. Whatever is left must look like a plain number (scientific notation like 1e3 is allowed), or the row shows “Enter numbers only, like 12.5 or 1,250.”
Hiding floating-point noise
JavaScript numbers are binary floating point, so some simple decimals can’t be stored exactly. In the browser console:
0.1 + 0.2is0.300000000000000041.005 * 100is100.49999999999999114 / 1.14is100.00000000000001
That last one is the VAT example. Showing “100.00000000000001” on a receipt question would make the calculator look broken, even though the error is in the 17th significant digit. The fix is one line:
export const clean = (n: number) => Number(n.toPrecision(12))
Rounding to 12 significant digits throws away the noise at the end and keeps far more precision than any price, mark or salary needs. Every result goes through clean before it’s returned, and the final grade and GPA calculators on the site reuse the same function. For display, format adds thousands separators and limits the decimals (up to 6, or 4 in the rows that answer with a percentage or an adjusted amount), so 1,234,567.891 reads as a number.
I didn’t use a decimal library. For six formulas with one or two operations each, rounding the result was enough, and the tests pin the cases above.
Try it
The percentage calculator has all six questions on one page, each with its own formula, and accepts commas, decimal commas and Arabic digits. More tools like it are in calculators.