What the Math Object Is
Math is a built-in object that collects the mathematical helpers, and it is unusual in one way: you never create one. There is no new Math(). Every method and every constant sits directly on the object itself, so you always write Math.round(...) and Math.PI. In Java or C# this arrangement would be called a static utility class, and the idea is identical.
The constants are worth knowing because they save you typing an approximation. Math.PI is π to the full precision JavaScript can hold, and Math.E is the base of natural logarithms. Writing Math.PI instead of 3.14 costs nothing, is far more accurate, and tells the reader what the number is.
Every Math method converts its argument to a number first. Handing it something that cannot be converted produces NaN rather than an error, so Math.round('abc') is NaN while Math.round('3.7') is 4, because that string converts cleanly. The conversion is convenient, and it is also why an unexplained NaN in a calculation almost always traces back to a form field that was never converted properly.
One thing Math does not do is repair floating-point arithmetic. It works on the same imprecise decimals as everything else in the language, so Math.round gives you a clean number to display rather than a guarantee that the sums behind it were exact. The section on toFixed below deals with that properly, because it matters the moment you touch money.
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
// Area of a circle, using the real value of pi
const r = 7;
console.log((Math.PI * r ** 2).toFixed(2)); // '153.94'
// Math converts its arguments before working
console.log(Math.round('3.7')); // 4 - the string converted cleanly
console.log(Math.round('abc')); // NaN - this one did not
console.log(Math.round(null)); // 0 - null converts to 0
console.log(Math.max()); // -Infinity, with no arguments at all Mathis not a class and cannot be extended usefully. If you find yourself wantingMath.average, write a normal function in your own module rather than adding properties to a built-in object — patching built-ins is a habit that causes conflicts you cannot debug.
Rounding: Four Methods, Four Different Answers
There are four rounding methods and they differ in what they do with the fractional part. Math.round goes to the nearest whole number, with exact halves going up. Math.floor always goes down. Math.ceil always goes up. Math.trunc simply deletes the fractional part and keeps what is left.
For positive numbers floor and trunc agree on every value, which is exactly why the difference goes unnoticed for months. On negative numbers they part company: Math.floor(-2.5) is -3, because "down" means more negative, while Math.trunc(-2.5) is -2, because it only removes the .5. If your data can ever be negative — a temperature, an adjustment, a balance — decide deliberately which of the two you meant.
Math.round's half-up rule is asymmetric on negatives for the same reason. Math.round(2.5) is 3 and Math.round(-2.5) is -2, not -3, because "up" means towards positive infinity rather than away from zero. This is the sort of thing that surprises people when a report containing negative adjustments refuses to balance.
In practice the pattern you will write most often is Math.floor combined with division: Math.floor(totalSeconds / 60) for whole minutes, Math.floor(index / pageSize) for a page number. Pair it with % for the leftover and you have the standard way to break a total down into units.
console.log(Math.round(4.4)); // 4
console.log(Math.round(4.5)); // 5
console.log(Math.floor(4.9)); // 4
console.log(Math.ceil(4.1)); // 5
console.log(Math.trunc(4.9)); // 4
// Negatives are where they disagree
console.log(Math.floor(-2.5)); // -3 - down means more negative
console.log(Math.trunc(-2.5)); // -2 - the fraction is simply removed
console.log(Math.ceil(-2.5)); // -2
console.log(Math.round(-2.5)); // -2 - half goes UP, towards positive
// The everyday pattern: floor for the whole part, % for the remainder
const totalSeconds = 275;
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
console.log(`${minutes}m ${seconds}s`); // '4m 35s'
// Pagination
const itemIndex = 47;
const pageSize = 20;
console.log(Math.floor(itemIndex / pageSize) + 1); // page 3 - There is no
Mathmethod for "round to two decimal places". You do it by scaling:Math.round(value * 100) / 100. A reusable version of that appears in the last section of this lesson.
toFixed, and Why Money Is Different
toFixed(n) is a method on numbers rather than on Math, and it does a different job from rounding: it returns a string with exactly n decimal places, padding with zeros where needed. That is what you want for display, because Math.round(2.5 * 100) / 100 gives you 2.5 when what you needed to show was 2.50.
The trap is exactly that it returns a string. total.toFixed(2) + 100 joins text instead of adding numbers, in the same way a form field does. Format at the very last moment, when the value is about to be put on the screen, and keep everything a number until then. Once a value has been through toFixed, treat it as text.
For money the deeper problem is that decimal arithmetic is not exact. 0.1 + 0.2 is 0.30000000000000004, so a long column of rupee amounts can drift a fraction of a paisa from the correct total, and a final rounding step can then land on the wrong side. The standard fix, in every language, is to store money in the smallest unit — paise as whole numbers — and to divide by 100 only at the point of display.
For the display itself, Intl.NumberFormat does considerably more than toFixed. It applies the grouping separators a locale expects, which for en-IN means the lakh and crore grouping rather than grouping in thousands, and it can attach a currency symbol. It is built into the browser and needs no library.
const price = 1234.5;
console.log(price.toFixed(2)); // '1234.50' <- a STRING
console.log(typeof price.toFixed(2)); // 'string'
console.log(price.toFixed(2) + 100); // '1234.50100' <- joined, not added
console.log(Number(price.toFixed(2)) + 100); // 1334.5
// Money in the smallest unit avoids the drift entirely
const paise = 12345; // i.e. 123.45 rupees
console.log((paise / 100).toFixed(2)); // '123.45'
// Decimal arithmetic is not exact
console.log(0.1 + 0.2); // 0.30000000000000004
console.log((0.1 + 0.2).toFixed(2)); // '0.30' - fine for display
// Locale-aware formatting, built into the browser
const fmt = new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' });
console.log(fmt.format(1234567.5)); // grouped in lakhs, with the rupee symbol toFixedrounds the value it is given, so(1.005).toFixed(2)can return'1.00'rather than'1.01'— because 1.005 is not exactly 1.005 in binary. One more reason to keep money in whole paise.
The Rest of the Everyday Toolkit
Math.max and Math.min take any number of arguments and return the largest or smallest. They do not take an array, which catches everyone out once — spread it in with Math.max(...marks). On an empty array that gives -Infinity, which is technically correct and almost never what you want on a screen, so check the length before you call it.
Math.abs removes the sign, which makes it the tool for asking how far apart two numbers are: Math.abs(a - b) < 0.001 is the correct way to compare two decimals, given that === cannot be trusted on them. Math.sqrt is the square root, and Math.pow(a, b) raises to a power — although the ** operator from Lesson 3 is now the ordinary way to write that.
There is a limit on how many arguments you can spread into a single function call. It is large, but it is real, so spreading an array of a million numbers into Math.max can fail outright. For very large arrays use reduce instead. For anything you will meet in this course, spread is fine and clearer.
The trigonometric functions all work in radians, not degrees. If your angle came from a form or a design tool it is almost certainly in degrees, so multiply by Math.PI / 180 first. Forgetting that conversion is the standard reason a rotation, a chart segment or a canvas drawing comes out wildly wrong on the first attempt.
const marks = [45, 82, 30, 91, 67];
console.log(Math.max(...marks)); // 91
console.log(Math.min(...marks)); // 30
console.log(Math.max(...[])); // -Infinity - check the length first
console.log(Math.abs(-7.5)); // 7.5
console.log(Math.abs(3 - 10)); // 7
console.log(Math.sqrt(144)); // 12
console.log(Math.pow(2, 10)); // 1024
console.log(2 ** 10); // 1024 - the modern way to write it
// Trigonometry works in radians, so convert degrees first
const degrees = 90;
console.log(Math.sin(degrees * Math.PI / 180).toFixed(4)); // '1.0000'
// Comparing two decimals for closeness
console.log(Math.abs((0.1 + 0.2) - 0.3) < 0.0001); // true Math.min()with no arguments returnsInfinityandMath.max()returns-Infinity. That looks odd until you see why: those are the identity values, chosen so that comparing them against anything real always loses.
Math.random and Whole Numbers in a Range
Math.random() returns a decimal from 0 up to — but never including — 1. Everything else you will ever do with random numbers in JavaScript is built on top of that single function.
To get a whole number from 0 to n-1, multiply and floor: Math.floor(Math.random() * n). That is exactly what you need to pick a random element from an array, because array indexes run from 0 to length - 1. For a range from min to max inclusive, the formula is Math.floor(Math.random() * (max - min + 1)) + min. The + 1 is the part people leave out, and leaving it out quietly makes the top value impossible — a dice that never rolls a six, and a test suite that never notices.
Two things Math.random is not suitable for. It is not cryptographically secure, so it must never generate a password, a token, an OTP, or anything a user should be unable to predict; crypto.getRandomValues exists for that. And it is not unique — two calls can easily return the same value — so it is not an id generator, whatever you saw in a tutorial.
For shuffling, resist sort(() => Math.random() - 0.5). It looks clever, appears to work, and produces a measurably biased order, because a comparator that answers differently for the same pair on different calls breaks the assumption sort is built on. If you need a fair shuffle, use the Fisher-Yates loop below; it is six lines and it is correct.
console.log(Math.random()); // e.g. 0.7263... (0 up to <1)
// A whole number from 0 to 5
console.log(Math.floor(Math.random() * 6)); // 0, 1, 2, 3, 4 or 5
// Pick a random element
const subjects = ['Maths', 'Physics', 'Chemistry'];
console.log(subjects[Math.floor(Math.random() * subjects.length)]);
// A range from min to max INCLUSIVE - the + 1 matters
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 6)); // a dice roll, 1 to 6
// A fair shuffle (Fisher-Yates), on a copy so the original survives
function shuffle(arr) {
const copy = [...arr];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}
console.log(shuffle([1, 2, 3, 4, 5])); - For a unique identifier,
crypto.randomUUID()is built into modern browsers (in a secure context, meaning HTTPS or localhost) and into Node. Use it instead ofMath.random().toString(36), which is neither unique nor unpredictable.
Small Utilities Worth Keeping
Most real use of Math ends up as a handful of tiny helper functions that you write once and reuse everywhere. Keeping them as named functions rather than inline expressions makes the calling code readable, and gives you exactly one place to change a rounding rule when someone decides the requirement was different.
clamp keeps a value inside a range and is the neatest use of min and max together. It turns up constantly in interface work — limiting a progress bar to 0 and 100, keeping a dragged element on the screen, capping a page number so a bad URL cannot break a listing.
roundTo handles decimal places as a number rather than a string, which is what you want when the value still has arithmetic ahead of it. Multiply, round, divide back. It is still floating-point arithmetic underneath and can be a hair off on awkward values, so for money you should still be working in whole paise as described earlier.
percentage looks too trivial to be worth a function, and it earns its place because the guard against a zero denominator lives inside it instead of being forgotten at three different call sites. Dividing by zero in JavaScript produces Infinity rather than an error, so a missing guard shows up as "Infinity%" on someone's screen rather than as a crash you would have caught in testing.
// Keep a value inside a range
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
console.log(clamp(150, 0, 100)); // 100
console.log(clamp(-5, 0, 100)); // 0
console.log(clamp(42, 0, 100)); // 42
// Round to a number of decimal places, keeping it a number
function roundTo(value, places) {
const factor = 10 ** places;
return Math.round(value * factor) / factor;
}
console.log(roundTo(3.14159, 2)); // 3.14 (a number, not a string)
// Percentage, with the divide-by-zero guard in exactly one place
function percentage(part, whole) {
if (whole === 0) return 0;
return roundTo((part / whole) * 100, 1);
}
console.log(percentage(235, 300)); // 78.3
console.log(percentage(5, 0)); // 0
console.log(5 / 0); // Infinity - no error whatsoever Math.round— nearest whole number; halves go up, towards positive infinityMath.floor— always down;Math.ceil— always upMath.trunc— drop the fraction; differs fromflooron negativesMath.max(...arr)— spread the array in; it does not accept one directlyMath.abs— distance from zero; the right way to compare decimalsMath.random()— 0 up to but not including 1; never for anything secrettoFixed(n)— a display string with n decimals, not a number
- Dividing by zero gives
Infinity, and0 / 0givesNaN. Neither throws anything, so a guard clause is the only thing standing between a missing denominator and a nonsense figure on a live page.
