Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 18 – Maths for DSA

Big integers, floating point and precision

Pick integer types that will not overflow, use big integers in Python, Java, Go and JavaScript, and compare floating-point values safely.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 8 of 25 minutes free
Free: How far each type goes · Floating point: why 0.1 + 0.2 is not 0.3 · Comparing floats safely · Exercise and self-check. With a pass: 3 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • Choose integer types that will not overflow from the problem's limits
  • Use the big-integer types of Python, Java, Go and JavaScript
  • Compare floating-point values safely with tolerances
  • Store money and other exact quantities as whole numbers

Before you start

Every number type keeps a limited precision, and the bugs come from forgetting where the limit is. A signed 32-bit integer stops at 2,147,483,647 and a 64-bit one at about 9.22 × 10¹⁸; past that, Java and Go wrap around without an error, and C++ makes signed overflow undefined. Big integers grow as needed: Python’s int, Java’s BigInteger, Go’s math/big and JavaScript’s BigInt compute 100! exactly, at a cost in speed. Floating point stores numbers in binary with about 15 to 17 significant decimal digits, so 0.1 + 0.2 is 0.30000000000000004. Compare floats with a tolerance, add many of them with math.fsum, and keep money as whole cents or paise.

How far each type goes

Pick the type from the largest value the problem can produce, not from the inputs alone. A sum of 10⁵ values up to 10⁹ reaches 10¹⁴, past 32 bits; a product of two values up to 10⁹ reaches 10¹⁸, just inside 64 bits; and factorials pass both limits quickly:

A logarithmic line from 1 to 10²⁰ marking where int32, exact integers in a double and int64 end, with 13! and 21! just past two of them.10⁰10⁵10¹⁰10¹⁵10²⁰int32 max2³¹ − 1exact doubles end2⁵³int64 max2⁶³ − 113!21!

Where exact integers end, on a logarithmic scale from 1 to 10²⁰

Text description of the diagram

The picture is a horizontal line with a logarithmic scale: the marks 10⁰, 10⁵, 10¹⁰, 10¹⁵ and 10²⁰ are equally spaced, so each step multiplies by 100,000. Three shaded bars start at 1 and end at the largest whole number a type holds exactly.

  • The shortest bar, labelled int32 max, ends at 2³¹ − 1 = 2,147,483,647, about 2.1 × 10⁹: the largest signed 32-bit integer.
  • The middle bar, labelled exact doubles end, ends at 2⁵³, about 9.0 × 10¹⁵: past it, a 64-bit floating-point number, which is what a JavaScript Number is, can no longer hold every whole number exactly.
  • The longest bar, labelled int64 max, ends at 2⁶³ − 1 = 9,223,372,036,854,775,807, about 9.22 × 10¹⁸: the largest signed 64-bit integer.

Two dots mark factorials: 13! = 6,227,020,800 lies just past the end of the 32-bit bar, and 21!, about 5.1 × 10¹⁹, just past the end of the 64-bit bar. Anything to the right of a bar needs a wider type or a big integer.

Where factorials pass each limit, in Python Python · limits/limits.py
import math
import sys

INT32_MAX, INT64_MAX = 2**31 - 1, 2**63 - 1

f = 1
for n in range(1, 26):
    previous, f = f, f * n
    if previous <= INT32_MAX < f:
        print(f"{n}! = {f:,} is the first factorial past the 32-bit limit {INT32_MAX:,}")
    if previous <= INT64_MAX < f:
        print(f"{n}! = {f:,} is the first factorial past the 64-bit limit {INT64_MAX:,}")
    if float(previous) == previous and float(f) != f:
        print(f"{n}! is the first factorial a float cannot hold exactly: {float(f)!r}")

wrapped = (math.factorial(21) + 2**63) % 2**64 - 2**63  # what a 64-bit signed multiplication keeps
print(f"21! wrapped into a signed 64-bit integer: {wrapped:,}")
print(f"100! has {len(str(math.factorial(100)))} digits, computed exactly")

try:
    print(len(str(10**5000)))
except ValueError as error:
    print("str(10**5000) raises ValueError:", error)
sys.set_int_max_str_digits(0)  # 0 lifts the limit for this program
print(f"after sys.set_int_max_str_digits(0): {len(str(10**5000))} digits")

Output

13! = 6,227,020,800 is the first factorial past the 32-bit limit 2,147,483,647
21! = 51,090,942,171,709,440,000 is the first factorial past the 64-bit limit 9,223,372,036,854,775,807
23! is the first factorial a float cannot hold exactly: 2.585201673888498e+22
21! wrapped into a signed 64-bit integer: -4,249,290,049,419,214,848
100! has 158 digits, computed exactly
str(10**5000) raises ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
after sys.set_int_max_str_digits(0): 5001 digits

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 limits.py

The same limits for JavaScript's Number and BigInt JavaScript · limits/limits.mjs
// A Number is a 64-bit float: integers are exact only up to Number.MAX_SAFE_INTEGER = 2^53 - 1.
console.log(`Number.MAX_SAFE_INTEGER = ${Number.MAX_SAFE_INTEGER}`);
console.log(`2 ** 53 + 1 === 2 ** 53: ${2 ** 53 + 1 === 2 ** 53}`);

let exact = 1n;
let approx = 1;
for (let n = 1; n <= 25; n++) {
  exact *= BigInt(n);
  approx *= n;
  if (BigInt(approx) !== exact) {
    console.log(`${n}! is the first factorial a Number gets wrong: ${approx} instead of ${exact}`);
    break;
  }
}

let f = 1n;
for (let n = 1n; n <= 100n; n++) f *= n;
console.log(`100! with BigInt has ${f.toString().length} digits`);
try {
  console.log(f + 1);
} catch (error) {
  console.log(`f + 1 with a BigInt f throws a ${error.name}; f + 1n works and ends in ${(f + 1n).toString().slice(-3)}`);
}

Output

Number.MAX_SAFE_INTEGER = 9007199254740991
2 ** 53 + 1 === 2 ** 53: true
23! is the first factorial a Number gets wrong: 2.585201673888498e+22 instead of 25852016738884976640000
100! with BigInt has 158 digits
f + 1 with a BigInt f throws a TypeError; f + 1n works and ends in 001

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node limits.mjs

13! is the first factorial past the 32-bit limit and 21! the first past the 64-bit one, where a 64-bit multiplication would keep −4,249,290,049,419,214,848, a negative number with no error. A JavaScript Number is a 64-bit floating-point value, exact for whole numbers only up to 2⁵³ − 1 = 9,007,199,254,740,991, so 2⁵³ + 1 equals 2⁵³; its first wrong factorial is 23!, because the smaller ones carry enough factors of 2 to fit. Big integers have no such limit, but mixing a BigInt with a Number throws a TypeError, and CPython refuses to turn an integer of more than 4,300 digits into a string until sys.set_int_max_str_digits raises the limit, a protection against slow conversions.

Sizing works the same way for any formula. Take up to 2 × 10⁵ items with values up to 10⁹: their total reaches 2 × 10¹⁴, so it needs 64 bits, and the sum of the products of all pairs reaches about 2 × 10²⁸, which no fixed-width integer holds, so it needs big integers or an answer modulo a prime. Write the worst case down before choosing the type.

Floating point: why 0.1 + 0.2 is not 0.3

A float holds a binary fraction with 53 significant bits, a precision of about 16 decimal digits. The decimal 0.1 has no finite binary expansion, just as 1/3 has none in decimal, so the float written 0.1 is the nearest binary value, slightly above 0.1:

0.1 + 0.2, ten million additions and a million prices Python · floats/floats.py
import math
from decimal import Decimal

print(f"0.1 + 0.2 = {0.1 + 0.2!r}, and 0.1 + 0.2 == 0.3 is {0.1 + 0.2 == 0.3}")
print(f"the float written 0.1 is exactly {Decimal(0.1)}")
print(f"math.isclose(0.1 + 0.2, 0.3) = {math.isclose(0.1 + 0.2, 0.3)}")

total = 0.0
for _ in range(10_000_000):
    total += 0.1
print(f"0.1 added ten million times with +=: {total!r}")
print(f"sum([0.1] * 10_000_000):             {sum([0.1] * 10_000_000)!r}")
print(f"math.fsum([0.1] * 10_000_000):       {math.fsum([0.1] * 10_000_000)!r}")

money = 0.0
for _ in range(1_000_000):
    money += 0.07  # a million items at 0.07 each, kept as a float
cents = 7 * 1_000_000  # the same amounts kept as whole cents
print(f"a million prices of 0.07 with +=: {money!r}; in whole cents: {cents // 100}.{cents % 100:02d}")

Output

0.1 + 0.2 = 0.30000000000000004, and 0.1 + 0.2 == 0.3 is False
the float written 0.1 is exactly 0.1000000000000000055511151231257827021181583404541015625
math.isclose(0.1 + 0.2, 0.3) = True
0.1 added ten million times with +=: 999999.9998389754
sum([0.1] * 10_000_000):             1000000.0
math.fsum([0.1] * 10_000_000):       1000000.0
a million prices of 0.07 with +=: 70000.00000019418; in whole cents: 70000.00

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 floats.py

The two rounding errors of 0.1 and 0.2 add up to one unit in the last place of 0.3, so == says False while math.isclose says True. Over ten million additions with += the errors pile up to 999,999.9998389754. sum() returned 1,000,000.0, since Python 3.12 adds floats with compensated summation, and math.fsum always returns the correctly rounded sum. A million prices of 0.07 added as floats came to 70,000.00000019418, while the same prices kept as whole cents added up to exactly 70,000.00. Floating point is the right tool for measurements, where 15 digits are plenty, and the wrong one for money or for counts that must be exact.

Comparing floats safely

math.isclose(a, b) returns True when abs(a − b) ≤ max(rel_tol × max(|a|, |b|), abs_tol), with a relative tolerance of 10⁻⁹ by default and no absolute tolerance. The relative part scales with the numbers, so it works for 10⁻⁶ and for 10¹⁶ alike; near 0 nothing is relatively close, so comparisons against 0 need an explicit abs_tol.

Number types for exact and approximate work
Criterion 64-bit integerBig integer64-bit floatDecimal or fraction
Exact? Yes, up to about 9.22 × 10¹⁸Yes, at any sizeNo: about 15 to 17 digitsYes, for decimals or ratios
Speed FastestSlower as digits growFastSlowest
Overflow Wraps in Java and Go; undefined in C++Grows insteadBecomes infinity past about 1.8 × 10³⁰⁸Limited by the chosen precision
When to choose Counts, sizes, money in centsFactorials, exact large resultsMeasurements, geometry, physicsCurrency rules, exact ratios

Three more tools build on these rules. Kahan summation keeps the low bits that each addition rounds away. The decimal module rounds in base 10 under rules you choose, such as half up or half even. ULPs, the gaps between neighbouring floats, give a tolerance that scales with the numbers.

Exercise and self-check

Add two numbers of up to 10⁵ digits, digit by digit, the way big-integer libraries do it inside.

Exercise · Easy · Python, JavaScript

Add two huge numbers given as decimal strings

Write add_strings(a, b) (JavaScript: addStrings). a and b are strings of decimal digits, up to 10⁵ digits each, that may start with zeros, and the function returns their sum as a string of digits without leading zeros ("0" for zero). Do it digit by digit, as on paper, without converting the whole numbers to int or BigInt: that is the skill the exercise practises, and the sample tests compare your answer with the built-in big integers.

The sample tests check single digits, carries that run through every digit, leading zeros, numbers of different lengths, a few hundred random pairs and two numbers of 10⁵ digits.

Python · Starter code · add_strings.py

def add_strings(a, b):
    """The sum of two decimal strings, as a decimal string without leading zeros ("0" for zero)."""
    raise NotImplementedError("write add_strings")
The sample tests · test_add_strings.py
import random

from add_strings import add_strings


def test_small_sums():
    """single digits, and a sum that gains a digit"""
    assert add_strings("2", "3") == "5"
    assert add_strings("123", "877") == "1000"
    assert add_strings("0", "0") == "0"


def test_long_carry():
    """a carry that runs through every digit"""
    assert add_strings("999", "1") == "1000"
    assert add_strings("1", "99999999999999999999") == "100000000000000000000"


def test_leading_zeros():
    """leading zeros in the input never reach the output"""
    assert add_strings("007", "0003") == "10"
    assert add_strings("000", "0") == "0"
    assert add_strings("0000123", "0") == "123"
    assert add_strings("5", "05") == "10"


def test_random_against_int():
    """300 random pairs of up to 3,000 digits, against Python's int"""
    rng = random.Random(12)
    for _ in range(300):
        a = "".join(rng.choice("0123456789") for _ in range(rng.randint(1, 3000)))
        b = "".join(rng.choice("0123456789") for _ in range(rng.randint(1, 3000)))
        assert add_strings(a, b) == str(int(a) + int(b)), (a[:20], b[:20])


def test_hundred_thousand_digits():
    """two numbers of 10^5 digits"""
    assert add_strings("9" * 100_000, "1") == "1" + "0" * 100_000
    assert add_strings("1" * 100_000, "8" * 100_000) == "9" * 100_000

JavaScript · Starter code · add_strings.mjs

/** The sum of two decimal strings, as a decimal string without leading zeros ("0" for zero). */
export function addStrings(a, b) {
  throw new Error('write addStrings');
}
The sample tests · add_strings.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { addStrings } from './add_strings.mjs';

/** A small seeded generator (Park and Miller), so every run builds the same numbers. */
function generator(seed) {
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  return (k) => Math.floor(random() * k);
}

test('single digits, and a sum that gains a digit', () => {
  assert.equal(addStrings('2', '3'), '5');
  assert.equal(addStrings('123', '877'), '1000');
  assert.equal(addStrings('0', '0'), '0');
});

test('a carry that runs through every digit', () => {
  assert.equal(addStrings('999', '1'), '1000');
  assert.equal(addStrings('1', '99999999999999999999'), '100000000000000000000');
});

test('leading zeros in the input never reach the output', () => {
  assert.equal(addStrings('007', '0003'), '10');
  assert.equal(addStrings('000', '0'), '0');
  assert.equal(addStrings('0000123', '0'), '123');
  assert.equal(addStrings('5', '05'), '10');
});

test('200 random pairs of up to 1,000 digits, against BigInt', () => {
  const pick = generator(12);
  const digits = (n) => Array.from({ length: n }, () => String(pick(10))).join('');
  for (let i = 0; i < 200; i++) {
    const a = digits(1 + pick(1000));
    const b = digits(1 + pick(1000));
    assert.equal(addStrings(a, b), (BigInt(a) + BigInt(b)).toString(), `${a.slice(0, 20)} ${b.slice(0, 20)}`);
  }
});

test('two numbers of 10^5 digits', () => {
  assert.equal(addStrings('9'.repeat(100000), '1'), '1' + '0'.repeat(100000));
  assert.equal(addStrings('1'.repeat(100000), '8'.repeat(100000)), '9'.repeat(100000));
});
A hint

Walk both strings from their last character, adding the two digits and the carry; write total % 10 and carry total // 10 (JavaScript: Math.floor(total / 10)). Keep going while either string has digits left or the carry is not 0, collect the digits in a list, reverse it at the end and strip the leading zeros.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Big Number Calculator Compute with numbers of many digits, or check a factorial and a sum from this lesson.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What is the largest whole number a signed 64-bit integer can hold?

    Choose one answer.

    Show the answer to question 1

    Answer: 9,223,372,036,854,775,807, that is 2⁶³ − 1

    One of the 64 bits holds the sign, so the largest value is 2⁶³ − 1. 2⁶⁴ − 1 is the largest unsigned 64-bit value, 2⁵³ − 1 the largest integer up to which every whole number fits a double, and 2³¹ − 1 the 32-bit limit.

  2. Question 2 of 5 What is the smallest n for which n! no longer fits in a signed 32-bit integer?

    Type a number.

    Show the answer to question 2

    Answer: 13

    12! = 479,001,600 fits under 2,147,483,647, and 13! = 6,227,020,800 does not. A loop that multiplies 32-bit integers silently goes wrong from 13! on in Java and Go.

  3. Question 3 of 5 A program adds prices as floats and must check that the total is 0.3. Which test is right?

    Choose one answer.

    Show the answer to question 3

    Answer: math.isclose(total, 0.3)

    0.1 + 0.2 is 0.30000000000000004, so == and the string test fail on a total that is right to 16 digits, and round(total) is 0. math.isclose compares with a relative tolerance of 10⁻⁹ by default. For money, keep whole cents and compare integers instead.

  4. Question 4 of 5 Why does math.isclose(1e-12, 0.0) return False?

    Choose one answer.

    Show the answer to question 4

    Answer: Its default tolerance is relative, and no number but 0 is relatively close to 0, so abs_tol must be given

    The test is abs(a − b) ≤ max(rel_tol × max(|a|, |b|), abs_tol) with rel_tol = 1e-09 and abs_tol = 0.0. Against 0, the relative part is 1e-09 × 1e-12, far smaller than the difference, so the answer is False until an absolute tolerance such as abs_tol=1e-9 is passed.

  5. Question 5 of 5 How should a shop store prices so that its totals are exact?

    Choose one answer.

    Show the answer to question 5

    Answer: As whole numbers of the smallest unit, such as cents or paise

    Whole units add, subtract and multiply exactly in integer types. Rounding floats after each step still adds binary rounding errors, which a million additions of 0.07 showed: 70,000.00000019418 instead of 70,000.00.

Overflow in five languages, reproduced and caught Pro: opens with a Pro, Premium or Ultimate pass

Every language with fixed-width integers fails at 21!, and each offers a different way to notice. This part runs the same factorials in Python, JavaScript, Java, C++ and Go, shows what each prints at the overflow and how to catch it, and measures why Python limits decimal conversions of huge integers.

In this part:

  • 21! in five languages, with run records
  • Checking before multiplying, in any language
  • Why Python limits decimal conversions, measured

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Floating-point error measured: sums, tolerances, rounding and money Pro: opens with a Pro, Premium or Ultimate pass

Floating-point errors are small, predictable and easy to make worse. This part measures four ways to add the same numbers, shows how the gap between neighbouring floats grows, reproduces two tolerance mistakes and rounds money the way a bill must be rounded.

In this part:

  • Four ways to add 0.1, and their errors
  • How far apart neighbouring floats are
  • Rounding money in base 10

2 runnable examples (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Exact fractions, the JSON number trap and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Some answers must be exact without being integers, and some integers stop being exact on their way to a browser. This part uses exact fractions, compares ratios without floats, reproduces how a 64-bit ID loses digits in JavaScript, and ends with a practice set.

In this part:

  • Exact fractions and exact comparisons
  • A 64-bit ID that loses digits in JavaScript
  • Edge cases worth a test
  • Practice set

2 runnable examples (JavaScript, Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    Why does 0.1 + 0.2 == 0.3 evaluate to false in most languages, and how should you compare floating-point values?

    Floats store binary fractions with 53 significant bits, and 0.1, 0.2 and 0.3 have no exact binary form, just as 1/3 has none in decimal. Each is stored as the nearest binary value, so 0.1 is really 0.10000000000000000555…, and the rounding errors of 0.1 and 0.2 add up to one unit in the last place: their sum is 0.30000000000000004, one float above the float nearest 0.3. Compare with a tolerance instead of ==, a relative one such as Python's math.isclose(a, b), whose default is 10⁻⁹, plus an absolute one when values can be close to zero. Values that must be exact, such as money, belong in integers of the smallest unit or in a decimal type.

  2. Mid to senior

    Order IDs are 64-bit integers. The web front end shows some IDs a few units off, and links built from them open the wrong order. Explain what happens, and fix it from the database to the page.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid

    Report the exact total of 10⁷ transaction amounts with two decimal places. Which types do you use in Python, Java and JavaScript, what goes wrong with plain floats, and how large can the total grow before your choice breaks?

    Model answer, Pro. First step and follow-ups

  4. Fresher to mid

    The loop x = 0.0; while x != 1.0: x += 0.1 never stops. Explain why, fix it, and design an equality check for a geometry library that sees both tiny and huge coordinates.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Size the type from the largest result: 13! passes 32 bits, 21! passes 64 bits, and two values of 10⁹ multiply to 10¹⁸.
  • Java and Go wrap around silently, C++ signed overflow is undefined, and a JavaScript Number is exact only up to 2⁵³ − 1.
  • Big integers are exact at any size: Python’s int, BigInteger, math/big and BigInt.
  • Floats carry about 15 to 17 digits: compare with math.isclose, add many with math.fsum, and keep money in whole units.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.