Skip to content
aviral gupta

// B2.2 · ~30 min · Beginner

Numbers, Math and rounding

After this lesson you can calculate with every arithmetic operator, round numbers the way you mean to, and deal with NaN, Infinity and numbers hidden inside text.

Lesson 2 of 5 in B2 Grammar, types and operators

You will be able to

  • Calculate with +, -, *, /, % and **, and explain why 0.1 + 0.2 is not exactly 0.3
  • Round with Math.round, Math.floor, Math.ceil and Math.trunc, and format with toFixed, which returns a string
  • Spot NaN and Infinity, test with Number.isNaN, read numbers with parseInt and parseFloat, and know the safe integer limit
  1. Warm-up · Activity 1 of 7

    Warm-up: what does this print?

    console.log(7 / 2);
  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    console.log(0.1 + 0.2);
  3. Practice · Activity 3 of 7

    17 eggs go into boxes of 5. Fill in the operator so that the program prints how many eggs are left over, 2.

    console.log(17 ____ 5);
    console.log(17 5);
  4. Practice · Activity 4 of 7

    Match each call to what console.log prints for it.

  5. Practice · Activity 5 of 7

    What does this print?

    const price = (19.999).toFixed(2);
    console.log(price + 1);
  6. Brain teaser · Activity 6 of 7

    Brain teaser. What does this print?

    console.log((1.005).toFixed(2));
  7. Apply · Activity 7 of 7

    Mini-task. Four friends share a bill of 47.8 euros and add a 10% tip, rounded to whole euros. Print the tip, the total, what each person pays with two decimals, and what each pays if everyone rounds up to whole euros.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

Splitting a restaurant bill

Three friends split a bill of 87.40 euros. The exact share has a long tail of digits, so the program formats it with toFixed, rounds it up with Math.ceil, and uses % to seat ten guests at tables of four. It ends with two numbers read from text and a calculation that breaks. Run it, then change people to 4.

main.js

// Splitting a restaurant bill between friends.
const bill = 87.4;
const people = 3;
const share = bill / people;
console.log("exact share:", share);
console.log("rounded:", share.toFixed(2), typeof share.toFixed(2));
console.log("each pays, whole euros:", Math.ceil(share));
console.log("tip at 10%:", Math.round(bill * 0.1));
console.log("tables of 4 for 10 guests:", Math.ceil(10 / 4), "with", 10 % 4, "at the last");
console.log("from text:", Number.parseFloat("12.5%"), Number.parseInt("3 guests", 10));
console.log("broken:", 0 / 0, Number.isNaN(0 / 0), 1 / 0);

Run it with

node main.js

Output

exact share: 29.133333333333336
rounded: 29.13 string
each pays, whole euros: 30
tip at 10%: 9
tables of 4 for 10 guests: 3 with 2 at the last
from text: 12.5 3
broken: NaN true Infinity
  • The exact share ends in …336, not …333: the binary storage shows in the last digit.
  • toFixed(2) gives the string "29.13", ready to print but not to calculate with.
  • Math.ceil rounds up, so three people paying 30 each cover the bill.
  • 10 % 4 is 2: two guests sit at the third table.
  • parseFloat and parseInt read the number at the start of the text and ignore the rest.
  • 0 / 0 is NaN, which Number.isNaN detects; 1 / 0 is Infinity, not an error.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Exercises

Exercise 1 of 2

Share the prize

Three winners share a prize of 100 euros. Print three lines: Each: 33.33 (the exact share with two decimals), Round up: 34 (the share rounded up to whole euros) and Remainder: 1 (the euros left over if each gets a whole number of euros). Calculate every value from prize and winners.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    (prize / winners).toFixed(2) gives "33.33". The brackets make the division happen first.

  2. Hint 2

    Math.ceil always rounds up, so 33.33… becomes 34.

  3. Hint 3

    The remainder after dividing is prize % winners.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

const prize = 100;
const winners = 3;
console.log("Each:", (prize / winners).toFixed(2));
console.log("Round up:", Math.ceil(prize / winners));
console.log("Remainder:", prize % winners);
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

const prize = 100;
const winners = 3;
// Print three lines:
// Each: 33.33
// Round up: 34
// Remainder: 1
console.log("Each:", prize / winners);

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';

const lines = async () => (await runMain()).trimEnd().split('\n');

test('prints the share with two decimals', async () => {
  const got = await lines();
  assert.equal(got[0], 'Each: 33.33', `the first line is ${JSON.stringify(got[0])}, expected "Each: 33.33"`);
});

test('prints the share rounded up', async () => {
  const got = await lines();
  assert.equal(got[1], 'Round up: 34', `the second line is ${JSON.stringify(got[1])}, expected "Round up: 34"`);
});

test('prints the remainder', async () => {
  const got = await lines();
  assert.equal(got[2], 'Remainder: 1', `the third line is ${JSON.stringify(got[2])}, expected "Remainder: 1"`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

Read the labels

Three labels were read as text: "12.5 kg", "7 items" and "abc". Number() turns all three into NaN. Print weight: 12.5 with Number.parseFloat, count: 7 with Number.parseInt (base 10), and invalid: true by checking with Number.isNaN whether parseFloat of "abc" failed.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Number.parseFloat(weightText) reads 12.5 and stops at the space before kg.

  2. Hint 2

    Number.parseInt(countText, 10) reads the whole number 7.

  3. Hint 3

    Number.parseFloat("abc") is NaN, and Number.isNaN(NaN) is true.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

const weightText = "12.5 kg";
const countText = "7 items";
const badText = "abc";
console.log("weight:", Number.parseFloat(weightText));
console.log("count:", Number.parseInt(countText, 10));
console.log("invalid:", Number.isNaN(Number.parseFloat(badText)));
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

const weightText = "12.5 kg";
const countText = "7 items";
const badText = "abc";
console.log("weight:", Number(weightText));
console.log("count:", Number(countText));
console.log("invalid:", Number(badText));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';

const lines = async () => (await runMain()).trimEnd().split('\n');

test('reads the weight', async () => {
  const got = await lines();
  assert.equal(got[0], 'weight: 12.5', `the first line is ${JSON.stringify(got[0])}, expected "weight: 12.5"`);
});

test('reads the count', async () => {
  const got = await lines();
  assert.equal(got[1], 'count: 7', `the second line is ${JSON.stringify(got[1])}, expected "count: 7"`);
});

test('reports the invalid label', async () => {
  const got = await lines();
  assert.equal(got[2], 'invalid: true', `the third line is ${JSON.stringify(got[2])}, expected "invalid: true"`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

Calling toFixed on a string

const price = "4.5";
console.log(price.toFixed(2));

What Node.js prints

TypeError: price.toFixed is not a function

Why, and the fix

toFixed is a number method, and "4.5" in quotes is a string, which has no toFixed. This often happens with values from a form or with a value that already went through toFixed once. Convert first: Number(price).toFixed(2) prints 4.50.

A dot straight after a whole number

console.log(5.toFixed(2));

What Node.js prints

SyntaxError: Invalid or unexpected token

Why, and the fix

JavaScript reads the dot after 5 as the start of the decimals, so 5.toFixed is a number with letters in its decimals, which cannot be read. Nothing in the file runs. Put the number in brackets, (5).toFixed(2), or store it in a variable first: const n = 5; n.toFixed(2). Both print 5.00.

Asking toFixed for too many digits

const share = 100 / 3;
console.log(share.toFixed(101));

What Node.js prints

RangeError: toFixed() digits argument must be between 0 and 100

Why, and the fix

toFixed accepts 0 to 100 digits after the point; anything else stops the program with a RangeError, the error for a value outside its allowed range. Two digits are usually what you want for money. Asking for many digits does not make the number more exact either: it only shows more of the binary storage error.

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

Six operators, and numbers stored in binary

JavaScript has +, -, * and /, and / always gives the full result: 7 / 2 is 3.5. % gives the remainder after dividing: 17 % 5 is 2, and the result has the sign of the left number, so -7 % 3 is -1. ** raises to a power: 2 ** 10 is 1024. As in maths, ** comes first, then *, / and %, then + and -; brackets change the order. Every number is stored in binary with about 17 significant digits, and many decimals, such as 0.1, cannot be stored exactly. That is why 0.1 + 0.2 prints 0.30000000000000004.

Four ways to round, and toFixed for display

Math.round rounds to the nearest whole number, and exactly .5 goes up, towards plus infinity: Math.round(2.5) is 3, but Math.round(-2.5) is -2. Math.floor always goes down (Math.floor(-4.5) is -5), Math.ceil always goes up (Math.ceil(4.1) is 5), and Math.trunc cuts the decimals off (Math.trunc(-4.9) is -4). To show money, use toFixed: (0.1 + 0.2).toFixed(2) is "0.30". Mind the quotes: toFixed returns a string, so adding to it with + joins text. Use it at the end, for printing, not for further calculation.

NaN, Infinity and numbers inside text

A calculation that has no numeric answer gives NaN: 0 / 0, or Number("abc") * 2. NaN spreads, since NaN + 1 is NaN too, and it is not equal even to itself, so test for it with Number.isNaN(value). Dividing by zero gives Infinity or -Infinity, not an error. When a number starts a piece of text, Number.parseFloat("12.5 kg") reads 12.5 and stops at the first character that cannot belong to a number; Number.parseInt("42px", 10) reads the whole number 42. Pass 10 as the second argument. Whole numbers are exact only up to Number.MAX_SAFE_INTEGER, 9007199254740991.

Sources

Last reviewed September 30, 2026