Command Palette

Search for a command to run...

Hectal
PHASE 4Beginner ~28 min· topic 4 of 6

Topic 4.4

Math and Rounding

In one line

The built-in Math object gives you rounding (round, floor, ceil, trunc), min/max, powers, roots and random numbers. Rounding has rules that surprise people, toFixed returns a string, and money is safest stored as whole paise or cents.

Think of it like this

Paying at a shop. The bill says ₹199.50 and you only have whole rupees. Rounding decides what you pay: the shop may round to the nearest rupee (₹200), always down in your favour (₹199), or always up in theirs (₹200). A careful shopkeeper doesn't write half-paise in the book at all: everything is counted in whole paise, so ₹199.50 is written as 19950 paise. JavaScript gives you all these choices; you must pick the right one.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Math object
A built-in object that holds maths functions like Math.round and constants like Math.PI.
Round
Change a number to a nearby simpler one, such as the nearest whole number.
Floor / ceil
Floor goes down to the whole number below; ceil goes up to the whole number above.
Truncate
Cut off the decimal part without rounding, so 4.9 becomes 4 and -4.9 becomes -4.
Significant digits
The digits that carry meaning, counted from the first non-zero digit.
Smallest unit
The smallest coin of a currency, like paise for rupees or cents for dollars.
Radian
The unit Math.sin and friends use for angles. A full circle is 2π radians (360 degrees).
Pseudo-random
Numbers that look random but come from a formula, so in theory they can be predicted.
Clamp
Keep a number inside a range: anything too small becomes the minimum, too big becomes the maximum.

Step by step

01Pick the rounding that matches the rule

Ask what the real-world rule is. "Nearest" is round. "How many full boxes can I fill?" is floor. "How many boxes do I need?" is ceil.

boxes.jswhole filejavascript
const items = 53;
const perBox = 10;
console.log(Math.floor(items / perBox)); // 5 full boxes
console.log(Math.ceil(items / perBox));  // 6 boxes needed
console.log(items % perBox);             // 3 items in the last box
Pick the rounding that matches the rulediagram
Rendering diagram…

02Display with toFixed, but keep numbers as numbers

toFixed is for the last step, showing a value. If you call it early, you get a string, and + then joins text instead of adding.

price.jswhole filejavascript
const price = (19.5).toFixed(2); // "19.50", a string
console.log(price + 1);          // "19.501"  (joined!)
console.log(Number(price) + 1);  // 20.5

03Count money in paise

Turn rupees into paise once, with Math.round. After that every sum is a sum of whole numbers, which is always exact.

cart.jswhole filejavascript
const pricesInRupees = [4.35, 0.1, 0.2];
const paise = pricesInRupees.map((r) => Math.round(r * 100)); // [435, 10, 20]
const total = paise.reduce((sum, p) => sum + p, 0);             // 465
console.log((total / 100).toFixed(2));                          // "4.65"

04Split fairly

Dividing 10000 paise by 3 gives 3333.33... Give everyone the floor, then hand out the leftover paise one by one, so the shares add up to exactly the total.

split.jswhole filejavascript
function split(totalPaise, people) {
  const base = Math.floor(totalPaise / people);
  const extra = totalPaise % people;
  return Array.from({ length: people }, (_, i) => base + (i < extra ? 1 : 0));
}
console.log(split(10000, 3)); // [ 3334, 3333, 3333 ]

05Make a random whole number

Math.random() gives 0 <= r < 1. Multiply to stretch the range, floor to make whole numbers, and add min to shift it.

dice.jswhole filejavascript
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 6)); // 1, 2, 3, 4, 5 or 6

Try it yourself

  1. 1

    Predict the table

    In the first example, add -0.5 and 0.5 to values. Predict all four columns for both before you run it. (Watch for -0.)

  2. 2

    Change the tax rate

    In the second example, change the tax to 5%. Predict the paise of tax and the final amount, then run it.

  3. 3

    Check the formula's edges

    In the third example, change randomInt(1, 6) to randomInt(0, 1) in the test. What should new Set(rolls).size print now? Then remove + 1 from the formula and see which value disappears.

Code & diagrams

round, floor, ceil and trunc side by side New tab

Look at the `-2.5` row: `Math.round` sends halves towards positive infinity, so it gives `-2`, not `-3`. The last value clamps 140 into the range 0 to 100.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

2.4 2 2 3 2
2.5 3 2 3 2
2.6 3 2 3 2
-2.4 -2 -3 -2 -2
-2.5 -2 -3 -2 -2
-2.6 -3 -3 -2 -2
9 9 NaN -Infinity
7.5 -1 1024 9 5
1 100
toFixed, binary decimals and money in paise New tab

`toFixed` rounds halves away from zero (`-1.5` becomes `-2`) while `Math.round` rounds them up (`-1`). `1.005` is stored as slightly less than 1.005, so both ways give 1.00.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

0.30000000000000004 0.30
10.00 string
1.00 1
-2 -1
1234.57 0.00012
434.99999999999994 434 435
[ 435, 10, 20, 9999 ] 10464
1884 123.48
Random numbers you can trust (and test) New tab

Ten thousand rolls all land between 1 and 6 and all six faces appear. Press the button to roll for real; that part isn't logged, because random values can't have a fixed expected output.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

true
6
4 true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Add after toFixed

Write const subtotal = (19.5).toFixed(2); console.log(subtotal + 10);.

terminal
$ Console
── what you'll see ──
19.5010

Break #2

Convert rupees to paise with floor

Write const paise = Math.floor(4.35 * 100);.

terminal
$ Console
── what you'll see ──
434 (one paisa lost)

Break #3

Pass an array to Math.max

Write const best = Math.max([72, 95, 88]);.

terminal
$ Console
── what you'll see ──
NaN

Myth vs fact

Myth

Math.round rounds .5 away from zero.

Fact

It rounds .5 towards positive infinity: Math.round(-2.5) is -2. toFixed does round halves away from zero, which is why the two can disagree on negatives.

Myth

toFixed(2) always rounds correctly.

Fact

It rounds the number actually stored in binary, which may be slightly below what you typed: (1.005).toFixed(2) is "1.00". For display, Intl.NumberFormat rounds the decimal value you'd expect.

Myth

Using toFixed(2) everywhere fixes money bugs.

Fact

It hides them in display while the stored values still drift. Store integer paise or cents and round once, at defined points.

Myth

Math.random() is fine for generating passwords or tokens.

Fact

It's a fast pseudo-random generator, not a secure one. Use crypto.getRandomValues or crypto.randomUUID for anything secret.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The spec rounds toFixed using the exact binary value: it picks the n for which n / 10^f - x is closest to zero, choosing the larger n on an exact tie. That's why results depend on how the decimal was stored. Intl.NumberFormat (ES2023 edition of the Intl spec) adds roundingMode options like "halfEven" (banker's rounding, which sends .5 to the even neighbour) and "halfExpand" (its default).

  • ▸

    V8's Math.random uses the xorshift128+ algorithm, seeded per page. It's statistically good and fast, but anyone who sees a few outputs can predict the rest, which is why it must never protect secrets. There is no built-in way to seed it for repeatable tests; use a small seeded generator in test code.

  • ▸

    Most Math functions are allowed to be approximate (the spec says "implementation-approximated"), so Math.sin or Math.pow may differ in the last digit between engines. Math.sin(Math.PI) is about 1.2e-16, not 0, because Math.PI itself is rounded. Compare floating results with a tolerance, not ===.

  • ▸

    For money beyond Number.MAX_SAFE_INTEGER paise, or for exact decimal rates, use BigInt (Topic 1.3) for whole units, or a decimal library. A native Decimal type has been discussed at TC39 but is not part of the language.

Remember this

  1. 1

    **Math** is a built-in object full of maths functions and constants. You don't create it; you call its methods directly: Math.round(2.6). Constants include Math.PI (3.14159...) and Math.E. All its functions take numbers and turn other values into numbers first, so Math.max(1, "7") is 7 and anything that can't become a number gives NaN (Phase 1, Topic 1.3).

  2. 2

    The four roundings. Math.round(x) goes to the nearest whole number, and a .5 always goes up towards positive infinity: Math.round(2.5) is 3 but Math.round(-2.5) is -2. Math.floor(x) always goes down (towards minus infinity): Math.floor(-4.2) is -5. Math.ceil(x) always goes up: Math.ceil(4.1) is 5. Math.trunc(x) (ES2015) chops off the decimals, so it goes towards zero: Math.trunc(-4.7) is -4. For positive numbers floor and trunc agree; for negatives they don't.

  3. 3

    Other everyday methods. Math.min(a, b, ...) and Math.max(...) take separate arguments, so use spread for an array: Math.max(...scores). With no arguments, Math.max() is -Infinity. Math.abs(x) removes the minus sign, Math.sign(x) gives -1, 0 or 1. Powers: x ** y (ES2016) or Math.pow(x, y); roots: Math.sqrt, Math.cbrt; Math.hypot(3, 4) is 5 (the distance formula). The trig functions (Math.sin, Math.cos, Math.atan2) use radians, not degrees: degrees * Math.PI / 180. A common helper is clamping: Math.min(Math.max(x, low), high) keeps x inside a range.

  4. 4

    Rounding to decimal places. num.toFixed(2) rounds to 2 decimals and returns a string ((10).toFixed(2) is "10.00"), which is what you want for display but not for more maths. Because numbers are stored in binary (Topic 1.3), some decimals can't be stored exactly: 1.005 is really 1.00499999999999989..., so (1.005).toFixed(2) is "1.00", and Math.round(1.005 * 100) / 100 is 1. toPrecision(n) rounds to n significant digits. For display, Intl.NumberFormat (Topic 4.5) rounds the way people expect and adds separators.

  5. 5

    Money. Never add up money as decimal rupees or dollars: 0.1 + 0.2 is 0.30000000000000004, and the errors pile up across a cart. Store money as an integer number of the smallest unit (paise, cents): ₹4.35 is 435. Integers up to Number.MAX_SAFE_INTEGER (about 9 quadrillion) are exact. Convert typed input with Math.round(rupees * 100) (never floor or parseInt: 4.35 * 100 is 434.99999999999994), do all maths in paise, decide rounding once (for tax or discount), and divide by 100 only to display. When you split ₹100 three ways, give the leftover paise to someone so the parts still add up to the total.

  6. 6

    Random numbers. Math.random() returns a decimal from 0 (included) up to 1 (not included). A random whole number from min to max inclusive is Math.floor(Math.random() * (max - min + 1)) + min. Math.random() is not secure: its output can be predicted, so never use it for passwords, tokens or anything an attacker might guess. Use crypto.getRandomValues(new Uint32Array(1)) or crypto.randomUUID() (in secure HTTPS pages) instead. To shuffle an array fairly, use the Fisher–Yates algorithm (Hectal's /dsa course covers it), not sort(() => Math.random() - 0.5), which is biased.

Explain it without notes

01

What is the difference between Math.floor and Math.trunc?

02

Why does (1.005).toFixed(2) give "1.00"?

03

How should an app store and add up prices?

04

How do you make a random integer between two values, and when is Math.random the wrong tool?

Practice

01

Write roundTo(x, places) that rounds a number to a given number of decimal places and returns a number (not a string), so roundTo(3.14159, 2) is 3.14.

02

Write toPaise(text) that turns a typed amount like "4.35" or "12" into whole paise (435, 1200).

03

Write percentOff(pricePaise, percent) that returns the discounted price in paise, rounded to the nearest paisa.

Trade-offs

  • ↔

    Integer paise make every sum exact but need converting at the edges (input and display); decimal numbers are simpler to write and quietly wrong in totals.

  • ↔

    toFixed is quick and built in but returns a string and rounds the binary value; Intl.NumberFormat handles rounding, separators and currency symbols but is more code to set up (create the formatter once and reuse it).

Done when you can

  • I can choose between round, floor, ceil and trunc, including for negative numbers.

  • I can use Math.min/Math.max with spread, and clamp a value into a range.

  • I know toFixed returns a string and why (1.005).toFixed(2) is "1.00".

  • I store money as whole paise or cents and convert with Math.round.

  • I can make a random integer in a range and I know when to use crypto instead.