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.
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
What you’ll learn · Before you start
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
On this page
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:
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.
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
// 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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.
| Criterion | 64-bit integer | Big integer | 64-bit float | Decimal or fraction |
|---|---|---|---|---|
| Exact? | Yes, up to about 9.22 × 10¹⁸ | Yes, at any size | No: about 15 to 17 digits | Yes, for decimals or ratios |
| Speed | Fastest | Slower as digits grow | Fast | Slowest |
| Overflow | Wraps in Java and Go; undefined in C++ | Grows instead | Becomes infinity past about 1.8 × 10³⁰⁸ | Limited by the chosen precision |
| When to choose | Counts, sizes, money in cents | Factorials, exact large results | Measurements, geometry, physics | Currency 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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
-
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.
-
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.
-
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?
-
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.
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/bigand BigInt. - Floats carry about 15 to 17 digits: compare with
math.isclose, add many withmath.fsum, and keep money in whole units.
References
- Built-in types: numeric types, and the integer string conversion length limitation (Python Software Foundation)
- sys.set_int_max_str_digits and sys.int_info (Python Software Foundation)
- math.fsum, math.isclose and math.ulp (Python Software Foundation)
- Built-in functions: sum() (summation of floats changed in 3.12) (Python Software Foundation)
- Floating-point arithmetic: issues and limitations (The Python Tutorial) (Python Software Foundation)
- decimal: decimal fixed-point and floating-point arithmetic (Python Software Foundation)
- fractions: rational numbers (Python Software Foundation)
- Class BigInteger (Java SE 25) (Oracle)
- Class Math (Java SE 25): multiplyExact (Oracle)
- The Java Language Specification, Java SE 25: integer operations (Oracle)
- Package math/big (The Go Authors)
- The Go Programming Language Specification: integer overflow (The Go Authors)
- BigInt (MDN Web Docs)
- Number.MAX_SAFE_INTEGER (MDN Web Docs)
- IEEE Standard for Floating-Point Arithmetic (IEEE 754-2019) (IEEE)
- GATE 2027 syllabus for Computer Science and Information Technology (CS) (Indian Institute of Technology Madras (GATE 2027))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress