Skip to main content

Building Blocks in JS

Data types in depth - BigInt Data Type in JavaScript

In the wide landscape of JavaScript, where data types like strings, numbers, and Booleans play their roles, there’s a powerful "thing" known as BigInt. This data type was introduced to solve a particular problem: dealing with numbers too large to be accurately represented by the standard Number data type. In this article, we’ll dive deep into BigInt!

What was the problem with Big Numbers?

Before BigInt was introduced, JavaScript developers had to rely on the Number data type for all numerical calculations. While this worked fine for most scenarios, it had its limits—specifically, the maximum safe integer value in JavaScript is (2^53−1 = read as 2 to the power 53, minus 1) 9,007,199,254,740,991. (I wish I had so much in my bank!) Anyways, any number larger than this could lead to inaccurate results due to rounding errors.

To put this into perspective, imagine you're calculating the number of grains of rice that could fit in a vast warehouse. Numbers like these can quickly exceed the safe integer limit in JavaScript, making calculations unreliable, got it?

BigInt

BigInt - This data type allows you to work with integers larger than 2^53 - 1 without losing precision. You can create a BigInt by appending an n to the end of an integer or by using the BigInt() function.

let bigNumber = 1234567890123456789012345678901234567890n;
let anotherBigNumber = BigInt("1234567890123456789012345678901234567890");

console.log(bigNumber); // 1234567890123456789012345678901234567890n

Both of these approaches yield the same result: a BigInt that can handle massive numbers with ease.

Imagine you’re working for the Reserve Bank of India (RBI), and your job is to manage the country’s total currency circulation. With over 1.4 billion people, handling such enormous numbers is a daily task. The total currency in circulation involves billions and sometimes trillions of rupees, especially during festival seasons like Diwali, when cash flow spikes.

Say you’re calculating the total amount of money in circulation during Diwali. Given that each rupee note and coin adds to the total, you can easily see how the numbers can grow beyond the safe limit for standard JavaScript numbers.

Arithmetic with BigInt

Just like with regular numbers, you can perform arithmetic operations on BigInts, including addition, subtraction, multiplication, division, and even modulo operations.

Addition

Let’s say the RBI needs to calculate the total currency circulation after introducing a new batch of notes:

let currentCirculation = 500000000000000000000000000000000n;
let newNotes = 10000000000000000000000000000000n;

let updatedCirculation = currentCirculation + newNotes;
console.log(updatedCirculation); // 510000000000000000000000000000000n

Here, we’ve added the new batch of notes to the existing circulation, resulting in an updated total.

Subtraction

Similarly, if some notes are withdrawn from circulation, you can subtract that number:

let withdrawnNotes = 50000000000000000000000000000000n;

let reducedCirculation = updatedCirculation - withdrawnNotes;
console.log(reducedCirculation); // 460000000000000000000000000000000n

Multiplication

Now, Say the RBI decides to double the currency in circulation:

let doubledCirculation = reducedCirculation * 2n;
console.log(doubledCirculation); // 920000000000000000000000000000000n

Division

If, instead, they decide to halve the currency in circulation:

let halvedCirculation = doubledCirculation / 2n;
console.log(halvedCirculation); // 460000000000000000000000000000000n

Modulo

Now let's see how modulo would work

let states = 29n;
let perStateDistribution = halvedCirculation / states;
let remainder = halvedCirculation % states;

console.log(perStateDistribution); // 15862068965517241379310344827586n
console.log(remainder); // 6n

Handling Mixed Data Types

One important thing to note is that you cannot directly mix BigInt with regular numbers in arithmetic operations. Doing so will result in a TypeError.

let regularNumber = 100;
let bigIntNumber = 200n;

let result = regularNumber + bigIntNumber; // TypeError: Cannot mix BigInt and other types

To avoid this, you’ll need to either convert the regular number to a BigInt:

let result = BigInt(regularNumber) + bigIntNumber;
console.log(result); // 300n

Or, if the result doesn’t need to handle extremely large numbers, you can convert the BigInt to a regular number (though this might lead to precision loss if the BigInt is very large):

let result = Number(bigIntNumber) + regularNumber;
console.log(result); // 300

BigInt and JSON

A limitation of BigInt is that it isn’t natively supported by JSON. If you try to stringify an object containing a BigInt, you’ll encounter an error:

let data = {
    totalCurrency: 500000000000000000000000000000000n
};

console.log(JSON.stringify(data)); // TypeError: Do not know how to serialize a BigInt

Run the code in the browser, you should see the above error.

To work around this, you can either convert the BigInt to a string before serialization:

let data = {
    totalCurrency: 500000000000000000000000000000000n.toString()
};

console.log(JSON.stringify(data)); // {"totalCurrency":"500000000000000000000000000000000"}

Or use a library that supports BigInt serialization, you can find some on npm

Comparing BigInts

BigInts can be compared just like regular numbers using comparison operators like <, >, <=, and >=.

Example: Comparing Circulation Numbers

Let’s say the RBI needs to compare the currency circulation during two different years to see if there’s been an increase:

let circulation2019 = 460000000000000000000000000000000n;
let circulation2020 = 500000000000000000000000000000000n;

if (circulation2020 > circulation2019) {
    console.log("The currency circulation increased in 2020.");
} else {
    console.log("The currency circulation did not increase in 2020.");
}

In this case, the comparison shows that the circulation did indeed increase from 2019 to 2020.

BigInt in Real-World Applications

BigInt isn't just for theoretical or niche applications. It's increasingly relevant in a world where data sizes and numbers are growing exponentially. Here are a few areas where BigInt could be especially useful:

Financial Calculations

As we've seen in our RBI example, handling extremely large financial figures accurately is crucial for central banks, large corporations, and financial institutions.

Cryptography

In cryptography, you often deal with extremely large prime numbers, which are essential for encryption algorithms. BigInt allows these calculations to be handled accurately.

let prime1 = 982451653n; // A large prime number
let prime2 = 961748927n; // Another large prime number

let product = prime1 * prime2;
console.log(product); // 944871836856449431n

Scientific Calculations

BigInt can also be valuable in scientific fields where calculations involve large constants or require high precision, such as astronomy or physics.

Blockchain

In the world of blockchain, where transactions and smart contracts often involve large sums of cryptocurrency, BigInt ensures that these numbers are handled without loss of precision.

BigInt Methods and Properties

JavaScript provides some useful methods and properties to work with BigInt.

BigInt.asIntN()

This method clamps a BigInt value to a signed integer between -2^(width-1) and 2^(width-1) - 1. It's particularly useful when you need to fit a BigInt into a specific bit width.

Note: I know this must be a bit confusing if you do not understand this completely, try running the code below in the browser to get a better understanding or feel free to skip this for now since you can still get things done (almost) without it and return here once you need it in your work!

let largeBigInt = 1234567890123456789012345678901234567890n;
let clampedBigInt = BigInt.asIntN(64, largeBigInt);

console.log(clampedBigInt); // 259

BigInt.asUintN(width, bigint)

Similar to BigInt.asIntN, this method wraps a BigInt to an unsigned integer with the specified number of bits. This is useful when you want to ensure that a BigInt fits within a specific bit-width but treats the number as always positive.

Example: Let’s use the same large grain count but wrap it as an unsigned 64-bit integer:

let grainsSold = 12345678901234567890n;
let wrappedGrains = BigInt.asUintN(64, grainsSold);
console.log(wrappedGrains); // 18317683885361562726

Here, the value is positive, and it fits within the unsigned 64-bit integer range.

BigInt.prototype.toString([radix])

The toString() method converts a BigInt to a string. You can optionally specify a radix (base) for the conversion, which is useful if you need to represent the BigInt in binary, hexadecimal, or any other number base.

Example: Imagine you need to store the grain count in different bases for some reason (should be crazy at this point right? But let's use this example to understand ):

let grainsSold = 12345678901234567890n;
console.log(grainsSold.toString()); // "12345678901234567890"
console.log(grainsSold.toString(2)); // Binary: "101010101011110011011010011111000000010101010"
console.log(grainsSold.toString(16)); // Hexadecimal: "ab54a98ceb1f0ad2"

BigInt.prototype.valueOf()

The valueOf() method returns the primitive value of a BigInt object. It’s not commonly used directly, as JavaScript usually does this automatically when performing operations with BigInt.

Example: This is more of a behind-the-scenes feature, but you can use it explicitly:

let grainsSold = 12345678901234567890n;
console.log(grainsSold.valueOf()); // 12345678901234567890n

Conclusion

I know this was a bit harder to understand, but some things become easy with time, I promise! Just keep practicing.