JavaScript Basics: Calculate Exact Integers with BigInt
A counter can grow beyond the range where ordinary JavaScript numbers represent every integer exactly. This tutorial uses BigInt to increase a large counter, divide whole quantities, and save an integer as JSON text.
All examples use built-in features and were tested on Linux with Node.js v24.19.0. Assertions stop the program if a result differs from the expected value. A successful run prints the output shown below.
Prepare a scratch directory
You need Bash, a text editor, the mktemp, rm, and rmdir utilities, and Node.js on your PATH. Save source files as UTF-8; their .mjs extension selects ES modules. Run these commands in Bash and keep using the same terminal:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Create each named file with your editor. No package installation is needed.
1. Increase a large counter
Save this as large-counter.mjs:
import assert from 'node:assert/strict';
const previousCount = BigInt('9007199254740993');
const nextCount = previousCount + 1n;
assert.equal(typeof previousCount, 'bigint');
assert.equal(nextCount, 9007199254740994n);
assert.equal(previousCount, 9007199254740993n);
assert.throws(() => previousCount + 1, TypeError);
assert.equal(1n === 1, false);
console.log(`Previous count: ${previousCount}`);
console.log(`Next count: ${nextCount}`);
console.log(`Value type: ${typeof nextCount}`);Run it:
node large-counter.mjsOutput:
Previous count: 9007199254740993
Next count: 9007199254740994
Value type: bigintCreate a BigInt with an integer literal ending in n, or convert an integer string with BigInt(). Passing an already-rounded Number cannot recover lost digits. Arithmetic such as addition requires matching numeric types: use 1n, rather than 1. Strict equality also distinguishes those types. Template strings display the digits without the source-code n suffix.
2. Divide into whole groups
Save this as whole-groups.mjs:
import assert from 'node:assert/strict';
const itemCount = 23n;
const groupSize = 5n;
const completeGroups = itemCount / groupSize;
const leftoverItems = itemCount % groupSize;
assert.equal(completeGroups, 4n);
assert.equal(leftoverItems, 3n);
assert.equal(completeGroups * groupSize + leftoverItems, itemCount);
assert.equal(-23n / 5n, -4n);
assert.equal(-23n % 5n, -3n);
assert.throws(() => itemCount / 0n, RangeError);
console.log(`Complete groups: ${completeGroups}`);
console.log(`Leftover items: ${leftoverItems}`);
console.log(`Negative division: ${-23n / 5n}`);Run it:
node whole-groups.mjsOutput:
Complete groups: 4
Leftover items: 3
Negative division: -4BigInt stores integers, not fractions. Division truncates toward zero; % gives a remainder, which can be negative. Dividing by zero throws. BigInt also cannot be passed directly to Math functions. Choose it for whole-number arithmetic, not as a drop-in decimal type.
3. Make a JSON representation explicit
Save this as counter-json.mjs:
import assert from 'node:assert/strict';
const counter = { count: 9007199254740993n };
assert.throws(() => JSON.stringify(counter), TypeError);
const savedText = JSON.stringify({ count: counter.count.toString() });
const parsedRecord = JSON.parse(savedText);
const restoredCount = BigInt(parsedRecord.count);
assert.equal(typeof parsedRecord.count, 'string');
assert.equal(restoredCount, counter.count);
console.log(savedText);
console.log(`Restored count: ${restoredCount}`);
console.log(`Restored type: ${typeof restoredCount}`);Run it:
node counter-json.mjsOutput:
{"count":"9007199254740993"}
Restored count: 9007199254740993
Restored type: bigintJSON.stringify() rejects BigInt values by default. This example defines count as a decimal string in the saved format, then explicitly restores that known field. JSON does not remember the original BigInt type. Do not automatically convert every numeric-looking string: a label or identifier might need its original spelling. External data also needs validation before conversion. Converting a large BigInt to Number just to serialize it can lose precision.
Clean up
In the same terminal, remove only the files created here, return to your original directory, and remove the now-empty scratch directory:
rm -- large-counter.mjs whole-groups.mjs counter-json.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirAcknowledgements
This post was created with assistance from AI.