---
title: BigInt
source: 'https://academia.sh/en/courses/javascript-fundamentals/bigint'
course: 'JavaScript Fundamentals'
language: en
updated: '2026-08-23T07:00:58+00:00'
license: 'CC BY-SA 4.0'
---

# BigInt

Cases where the safe integer range is crossed, the arbitrary-precision integer type, operator behavior, the ban on mixing types, and the serialization limit.

The previous lesson showed that the whole of the number type is the IEEE 754
double-precision representation, and that this representation carries a boundary at
$2^{53}-1$ for integers. Above the boundary, two different integers round to the same bit
pattern and come out equal.

This lesson takes up the cases where that boundary is actually crossed and the language's
answer to it: a separate primitive type carrying an integer of arbitrary size. The type
itself matters, and so does the strict boundary placed between it and the number type.

## Where the Boundary Is Crossed

The safe range is about nine quadrillion. This value, which looks distant in everyday
calculations, is crossed with a single reading in certain kinds of data.

- **Nanosecond-resolution timestamps.** The number of nanoseconds elapsed since a start
  point is a million times larger than the same value in milliseconds, and it exits the
  range.
- **64-bit integer identifiers coming from other systems.** Even if no arithmetic is done
  on the identifier, it has to be read back and written out without losing value.
- **Counters accumulating over a long period.** In a high-sample-rate measurement
  network, the total sample count can reach this range.

What all three share is that precision has to be preserved down to the last digit. When
working with the number type, the loss is silent; no error is thrown, only the wrong
value is carried.

## A Separate Primitive Type

BigInt is the primitive type that carries an integer of arbitrary digit count. It is
written in two forms: by appending `n` to the end of a number literal, or through a
conversion call.

```js
const a = 9007199254740993n;
const b = BigInt("9007199254740993");
console.log(a === b);
console.log(typeof a);
console.log(a + 1n);
```

```
true
bigint
9007199254740994n
```

In the previous lesson, this value could not be represented with the number type; here
it is preserved exactly. The `n` suffix in the output is not part of the value, it is the
runtime's inspection format. The suffix disappears when the value is converted to text:

```js
console.log(String(10n));
console.log((10n).toString());
console.log(10n);
```

```
10
10
10n
```

The first two lines are the result of the language's text-conversion rule, and are the
same in every environment. The display on the third line is the environment's inspection
format. The distinction established in the Ways to Run Code lesson shows up again here.

## Operator Behavior

Arithmetic operators work with integer semantics. The most noticeable difference is in
division:

```js
console.log(7n / 2n);
console.log(7n % 2n);
console.log(2n ** 64n);
```

```
3n
1n
18446744073709551616n
```

Division produces no fraction; the result is truncated toward zero. The distinction
between the two division operations established in the Basic Data Types lesson of the
Programming Fundamentals course applies here — with the BigInt type there is only integer
division. The truncation direction is toward zero, not downward; with negative operands,
this distinction changes the result.

The exponentiation operator gives exactly a magnitude the number type could not
represent. $2^{64}$ has twenty digits and is correct down to the last digit.

## Types Cannot Be Mixed

The two number types cannot come together in arithmetic operators:

```js
try {
  console.log(1n + 1);
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot mix BigInt and other types, use explicit conversions
```

This ban is not a shortcoming, it is a deliberate design decision. The two types'
semantics do not agree: division produces a fraction in the number type, it does not in
BigInt; large values round in the number type, they do not in BigInt. Which rule a mixed
expression should be evaluated under cannot be chosen in a single consistent way. The
language leaves the choice to the program's author and demands an explicit conversion.

Comparison operators behave differently. Operators that compare value can take the two
types together; the operator that also compares type keeps them apart:

```js
console.log(1n == 1);
console.log(1n === 1);
console.log(2n > 1);
```

```
true
false
true
```

The first and third lines compare the mathematical value. The second line gives false
because it also compares the types. These three operators' rules will be defined step by
step in the Equality Comparisons lesson.

## The Cost of Conversion

Conversion between the two types can be written explicitly, but not every direction is
safe.

Converting BigInt to number loses precision for values outside the range:

```js
const timeNs = 1700000000123456789n;
console.log(Number(timeNs));
console.log(BigInt(Number(timeNs)));
```

```
1700000000123456800
1700000000123456768n
```

The two lines have to be read together. The first line is the text form of the number
that comes out of the conversion; text conversion chooses the shortest decimal
representation that rounds back to the same bit pattern, which is why it looks round. The
second line writes the same value with a lossless type and shows the actual value:
`...768`. So the original value has been lost, and the amount of the loss is also
different from what it looks like at first glance.

Converting number to BigInt rejects fractional values:

```js
try {
  console.log(BigInt(1.5));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer
```

Built-in methods that work with the number type also do not accept BigInt:

```js
try {
  console.log(Math.max(1n, 2n));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
```

```
TypeError: Cannot convert a BigInt value to a number
```

The entire `Math` object is defined for floating-point numbers. If a comparison with
BigInt values is needed, comparison operators are used.

## Application to the Measurement Script

When a nanosecond-resolution timestamp is added to measurement records, the two types
stand side by side in the same record:

```js
const readings = [
  { station: "A1", timeNs: 1700000000123456789n, temperature: 21.5 },
  { station: "A1", timeNs: 1700000000923456789n, temperature: 21.7 },
];

const diff = readings[1].timeNs - readings[0].timeNs;
console.log("diff (ns):", diff.toString());
console.log("diff (ms):", (diff / 1000000n).toString());
```

```
diff (ns): 800000000
diff (ms): 800
```

The time difference was computed at nanosecond precision, then converted to
milliseconds. The fraction was dropped in the conversion — integer division truncates.
If a fractional result is needed, the division is done after switching to the number
type, and at that point the loss of precision is knowingly accepted.

The temperature field stayed in the number type; that is the right choice for a
floating-point magnitude. The two types being present in the same record is not a
problem — the problem is mixing the two in the same expression.

## The Serialization Limit

BigInt values prevent data from being written in JSON format:

```js
try {
  console.log(JSON.stringify({ counter: 10n }));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
console.log(JSON.stringify({ counter: String(10n) }));
```

```
TypeError: Do not know how to serialize a BigInt
{"counter":"10"}
```

The reason is not a shortcoming, it is the format's definition: JSON's number
representation carries no precision guarantee, and the reading side may convert the value
to the number type. Writing it as text to carry the value losslessly is the solution that
accepts the format's boundary. JSON's boundaries are the subject of a separate lesson.

## Summary

- The safe integer range is crossed in nanosecond timestamps, 64-bit external
  identifiers, and counters accumulating over a long period; the loss is silent.
- BigInt is a separate primitive type; it is written with the `n` suffix or through
  explicit conversion, and it carries an arbitrary number of digits.
- Division produces no fraction, it truncates toward zero; `Math` methods do not work
  with this type.
- The two number types cannot be mixed in arithmetic operators; comparison operators work
  together for value comparison, the operator that also compares type keeps them apart.
- Converting BigInt to number loses precision outside the range; it cannot be written
  directly to JSON, it is carried as text.

## Next Step

This lesson showed the one case that requires an explicit conversion between the two
types to be written. In the rest of the language, the situation is the reverse: types are
converted on their own in most operators, and the result depends on the order in which
the conversion is done. The next lesson will define the steps of this implicit coercion;
the result of expressions like `[] + {}` will be read not as something to memorize, but
as the output of a traceable procedure.
