정수 오버플로와 BigInt: 언어별 안전한 한계
INT_MAX + 1 을 C·Java·C#·Swift·Dart·PHP·파이썬에서 실행해 비교합니다. JSON.parse 가 19자리 ID 를 바꾸는 이유, 문자열·BigInt 해법, SQLite 한계까지 확인합니다.
시리즈: 숫자·금액 계산 강좌 2/6 (초급). 금액, 카운터, ID 를 정수로 저장하면서 "정수는 실수와 달리 항상 정확하다" 고 생각하는 신입 개발자를 위한 글입니다. 다 읽으면 언어마다 정수가 얼마나 큰지, 한계를 넘으면 무슨 일이 일어나는지, 19자리 ID 를 JSON 과 자바스크립트에서 자릿수를 잃지 않고 다루는 법을 알게 됩니다.
1회에서는 실수가 근삿값이라는 것을 봤습니다. 정수는 정확하지만 범위 안에서만 그렇고, 끝에서 벌어지는 일은 언어마다 전혀 다릅니다. C 는 정의되지 않은 동작, 자바는 조용히 한 바퀴, Swift 는 프로그램을 멈추고, PHP 는 float 로 바뀌고, 자바스크립트는 조용히 반올림합니다. 원화는 소수 단위가 없어서 금액을 정수로 다루는 게 당연한데, 32비트 int 에 들어가는 건 약 21억 4748만까지입니다. 직원 1,500명에게 월 200만 원씩 주는 회사의 급여 합계 30억 원도 들어가지 않습니다. 주문 시스템, 결제 API, 몇 년씩 도는 카운터, DB ID 를 JSON 으로 받는 프런트엔드를 만든다면 이 중 하나는 꼭 만납니다. 아래는 모두 Apple Silicon Mac(M1 Pro), macOS 26 에서 Apple clang 21, OpenJDK 26, .NET 10(SDK 10.0.403), Swift 6.4.2, Dart 3.11.5, PHP 8.5.6, Python 3.9.6 과 3.12.14, JavaScriptCore jsc, sqlite 3.51.0 으로 실행했습니다. 경로는 ~/demo/ 로 바꿨습니다.
5분 만에 첫 오버플로
월급 2,000,000원 × 1,500명 = 3,000,000,000원(30억 원)입니다. first_ov.c 로 저장합니다.
#include <stdio.h>
int main(void) {
int price = 2000000;
int qty = 1500;
int total = price * qty;
printf("total = %d\n", total);
long long ok = (long long)price * qty;
printf("ok = %lld\n", ok);
return 0;
}
$ clang -std=c17 -Wall -Wextra -o first_ov first_ov.c && ./first_ov
total = -1294967296
ok = 3000000000
컴파일 경고도 크래시도 없이 합계가 음수가 될 뿐입니다. 이 Mac 에서 C 의 int 는 32비트라 -2,147,483,648 부터 2,147,483,647(21억 4748만 3647)까지만 담습니다. 30억은 들어가지 않고, 남은 비트를 읽으면 -1,294,967,296 이 됩니다. 계산 결과가 타입의 범위를 넘는 것을 정수 오버플로라고 합니다.
해법은 곱셈을 넓은 타입에서 하는 것입니다. (long long)price * qty 는 곱하기 전에 한쪽을 64비트로 바꿉니다. long long total = price * qty; 는 늦습니다. 곱셈은 이미 32비트에서 끝났습니다.
-fsanitize=undefined(UBSan, 정의되지 않은 동작 검사기)를 붙이면 clang 이 정확한 줄을 알려 줍니다.
$ clang -std=c17 -Wall -Wextra -fsanitize=undefined -o first_ov_ub first_ov.c && ./first_ov_ub
first_ov.c:5:23: runtime error: signed integer overflow: 2000000 * 1500 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior first_ov.c:5:23
total = -1294967296
ok = 3000000000
정수는 얼마나 큰가: 언어별 크기
| 언어 | 기본 정수 | 범위 | 더 큰 것 |
|---|---|---|---|
| C (clang, macOS arm64) | int 32비트 |
±약 21억 | long / long long 64비트(여기서 sizeof 8, 8) |
| Java | int 32비트 |
±약 21억 | long 64비트, BigInteger |
| C# | int 32비트 |
±약 21억 | long 64비트, decimal, BigInteger |
| Swift | Int (64비트 플랫폼에서 64비트) |
±약 922경 | Int64, 기본 큰 정수 없음 |
| Dart (네이티브) | int 64비트 |
±약 922경 | BigInt |
| Dart (자바스크립트로 변환) | int 가 JS 숫자 |
2^53 까지 정확 | BigInt |
| PHP | int 64비트(PHP_INT_SIZE 8) |
±약 922경 | bcmath, gmp |
| Python | int |
제한 없음 | 필요 없음 |
| JavaScript | Number(double) |
2^53 - 1 까지 정확 | BigInt |
| SQLite | INTEGER 최대 8바이트 |
±약 922경 | TEXT 로 저장 |
외워 둘 숫자는 둘입니다. 2,147,483,647(2^31 - 1, 21억 4748만 3647, 32비트 한계)과 9,007,199,254,740,991(2^53 - 1, 약 9007조, double 이 빈틈없이 담는 가장 큰 정수). 64비트 한계는 9,223,372,036,854,775,807(약 922경)입니다.
한계를 넘으면: 7개 언어 비교
모든 언어에서 MAX + 1 과 2,000,000 × 1,500 을 계산했습니다. 출력은 실행 결과 그대로입니다.
C: 부호 있는 정수 오버플로는 정의되지 않은 동작(UB)입니다. 표준이 결과를 정하지 않고, 최적화기는 "일어나지 않는다" 고 가정해도 됩니다. 이 Mac 에서는 INT_MAX + 1 이 -2147483648 이었지만 우연이지 규칙이 아닙니다(-O2 에서 결과가 달라지는 루프는 C 정의되지 않은 동작과 최적화 참고). 부호 없는 오버플로는 정의돼 있어서 UINT_MAX + 1 은 0 이었습니다. UB 없이 확인하려면 clang/gcc 내장 함수 __builtin_add_overflow(a, 1, &r) 를 쓰고, INT_MAX 에서 참을 돌려줬습니다. -fno-sanitize-recover=all 을 붙이면 UBSan 이 첫 보고에서 프로그램을 멈추고, 종료 상태는 134 였습니다.
Java: 언제나 조용히 한 바퀴 돕니다.
a + 1 = -2147483648
price * qty = -1294967296
long wrong = -1294967296 // long wrong = price * qty;
long right = 3000000000 // long right = (long) price * qty;
Math.abs(Integer.MIN_VALUE) = -2147483648
java.lang.ArithmeticException: integer overflow // Math.addExact(a, 1)
java.lang.ArithmeticException: integer overflow // Math.multiplyExact(price, qty)
가장 작은 int 의 Math.abs 는 여전히 음수입니다. +2,147,483,648 은 int 에 없기 때문입니다. *Exact 메서드(Java 8 이후)가 안전한 버전입니다.
C#: 기본은 한 바퀴 돌고, checked 로 오버플로를 예외로 바꿀 수 있습니다. 상수는 컴파일할 때 검사됩니다.
price * qty = -1294967296
OverflowException: Arithmetic operation resulted in an overflow. // checked(price * qty)
Program.cs(1,9): error CS0220: The operation overflows at compile time in checked mode
마지막 줄은 int x = int.MaxValue + 1; 의 컴파일 오류입니다. SDK 메시지는 OS 언어 설정에 따라 번역돼 나올 수 있으니 검색할 때는 코드 CS0220 으로 찾는 게 확실합니다. 영어로 보려면 DOTNET_CLI_UI_LANGUAGE=en dotnet build 로 실행합니다(위 줄도 그렇게 얻었습니다). .csproj 에 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> 를 넣으면 프로젝트 전체가 검사 모드가 됩니다. dotnet new console 로 갓 만든 프로젝트에는 없습니다.
Swift: 오버플로가 나면 트랩, 즉 일부러 프로그램을 멈춥니다. 상수면 컴파일조차 안 됩니다.
ovc.swift:1:26: error: arithmetic operation '2147483647 + 1' (on type 'Int32') results in an overflow
실행 중에 정해지는 값이면 about to multiply 를 출력한 뒤 종료 상태 133(128 + 시그널 5, SIGTRAP)으로 끝났습니다. -Onone 이든 -O 든 같았습니다. 한 바퀴 돌기를 원하면 명시합니다. a &+ 1 은 -2147483648, a.addingReportingOverflow(1) 은 (-2147483648, true) 였습니다.
Dart: 어디서 돌리느냐에 따라 다릅니다. Dart VM 에서 int 는 64비트로 한 바퀴 돌아서 9223372036854775807 + 1 이 -9223372036854775808 이었습니다. dart compile js 로 자바스크립트로 바꾸면 int 가 JS 숫자가 되므로 표현할 수 없는 리터럴은 컴파일러가 거부합니다.
Error: The integer literal 9007199254740993 can't be represented exactly in JavaScript.
int.parse 로 읽은 값이면, 변환된 코드를 (브라우저가 아니라 jsc 로) 실행했을 때 64비트 최댓값은 9223372036854776000, 2^53 + 1 은 9007199254740992 가 됐고 identical(1, 1.0) 은 true 가 됐습니다. 같은 소스인데 숫자가 달라집니다. Flutter 웹은 따로 테스트하세요.
PHP: 넘친 int 는 float 가 됩니다.
var_dump(PHP_INT_MAX + 1); float(9.223372036854776E+18)
var_dump(PHP_INT_MAX + 1 == PHP_INT_MAX); bool(true)
(int)(PHP_INT_MAX + 1) -> Warning: The float 9.223372036854776E+18 is not representable as an int, cast occurred
int(-9223372036854775808)
intdiv(PHP_INT_MIN, -1) -> Fatal error: Uncaught ArithmeticError: Division of PHP_INT_MIN by -1 is not an integer
오류는 없지만 float 정밀도는 53비트라 PHP_INT_MAX + 1 == PHP_INT_MAX 가 참입니다. int 로 되돌리는 캐스트는 8.5.6 에서 경고를 냈습니다. 옛 버전에서도 나오는지는 시험하지 않았습니다. bcadd("9223372036854775807", "1") 와 gmp_add 는 둘 다 정확히 9223372036854775808 을 출력했습니다.
Python: 정수가 넘치지 않습니다. (2**63 - 1) + 1 은 9223372036854775808, 2**64 * 2**64 는 39자리를 모두 출력했습니다. 한계는 고정 크기 세계로 넘길 때 나옵니다. struct.pack('<i', 2**31) 은 struct.error: 'i' format requires -2147483648 <= number <= 2147483647, (2**64).to_bytes(8, 'little') 은 OverflowError: int too big to convert 였습니다.
자바스크립트: MAX_SAFE_INTEGER 와 자릿수가 바뀌는 ID
자바스크립트 Number 는 double(1회)입니다. 53비트 가수로 2^53 까지의 정수는 모두 정확하지만, 그 위로는 double 사이 간격이 2, 4, 8… 로 벌어집니다.
$ jsc safe.js
9007199254740991 true // Number.MAX_SAFE_INTEGER, === 2**53 - 1
9007199254740992 9007199254740992 true // 2**53 과 2**53 + 1 이 같다
true false // Number.isSafeInteger(2**53 - 1), (2**53)
9007199254740992 9007199254740992 9007199254740994 9007199254740996 9007199254740996 // 2**53 + 0..4
이게 가장 아픈 곳은 ID 입니다. 64비트 자동 증가 키나 분산 시스템의 Snowflake 방식 ID 는 2^53 을 넘습니다. 서버는 올바른 JSON 을 보냈는데 브라우저의 JSON.parse 가 조용히 값을 바꿉니다.
const body = '{"id": 1234567890123456789, "order_id": 9007199254740993, "sid": "1234567890123456789"}';
const o = JSON.parse(body);
print(o.id, o.order_id, o.sid);
print(JSON.stringify(o));
1234567890123456800 9007199254740992 1234567890123456789
{"id":1234567890123456800,"order_id":9007199254740992,"sid":"1234567890123456789"}
ID 는 11, 주문 ID 는 1 어긋났고 오류는 하나도 없습니다. 클라이언트가 그 ID 를 되돌려 보내면 다른 행을 고치거나 아무것도 못 고칩니다. 문자열인 sid 는 무사했습니다. 파이썬 json.loads 와 PHP 8.5 json_decode 는 정수가 크기 때문에 9007199254740993 을 제대로 읽습니다. PHP 는 64비트를 넘는 12345678901234567890 을 float(1.2345678901234567E+19) 로 만들고, JSON_BIGINT_AS_STRING 을 주면 string(20) "12345678901234567890" 이 됐습니다. 서버 쪽 비슷한 함정은 PHP prepared statement 가 JSON 숫자 타입을 바꾸는 이야기에 있습니다.
해법(우선순위 순):
- ID 는 문자열로 보낸다. API 가
"id": "1234567890123456789"를 돌려줍니다. ID 는 이름표이지 더할 수량이 아닙니다. 어떤 클라이언트에서도 됩니다. - 계산이 필요하면
BigInt.BigInt("1234567890123456789") + 1n은1234567890123456790이었습니다. BigInt 에는 고유한 규칙이 있고,jsc에서 확인했습니다.
1n + 1 TypeError: Invalid mix of BigInt and other type in addition.
JSON.stringify({x:10n}) TypeError: JSON.stringify cannot serialize BigInt.
Math.sqrt(16n) TypeError: Conversion from 'BigInt' to 'number' is not allowed.
- 파싱하면서 원문을 읽는다. 새 엔진에서는
JSON.parsereviver 에 각 숫자의 원문이 전달되고,JSON.rawJSON으로 BigInt 를 숫자 그대로 써낼 수 있습니다. 이 Mac 의jsc에서 둘 다 동작했습니다.
const r = JSON.parse(body, (k, v, ctx) => k === 'id' ? BigInt(ctx.source) : v);
print(String(r.id), typeof r.id); // 1234567890123456789 bigint
print(JSON.stringify({ id: JSON.rawJSON(r.id.toString()) })); // {"id":1234567890123456789}
이 두 기능은 언어에 최근 들어온 것입니다. 지원해야 하는 모든 브라우저와 Node.js 버전에 있는지 확인하고 쓰세요(실행한 건 jsc 뿐입니다). 1번 방법에는 이런 주의가 필요 없습니다.
조용한 32비트 벽이 하나 더 있습니다. 비트 연산자는 32비트 정수로 바꿉니다. (2147483647 + 1) | 0 은 -2147483648, Math.imul(2000000, 1500) 은 -1294967296 이었습니다. Number 와 BigInt 는 자바스크립트 성능과 숫자에서 더 다룹니다.
데이터베이스: SQLite INTEGER 의 한계
SQLite INTEGER 는 부호 있는 64비트입니다. 한계에서 세 가지로 다르게 움직입니다.
sqlite> SELECT 9223372036854775807 + 1, typeof(9223372036854775807 + 1);
9.22337203685478e+18|real
sqlite> SELECT sum(x) FROM (SELECT 9223372036854775807 AS x UNION ALL SELECT 1);
Runtime error near line 4: integer overflow
sqlite> SELECT total(x) FROM (SELECT 9223372036854775807 AS x UNION ALL SELECT 1);
9.22337203685478e+18
+ 는 조용히 float 로 바뀌고, sum() 은 오류, total() 은 항상 float 를 돌려줍니다. INTEGER 열에 12345678901234567890 을 넣으면 real 로 저장됐고, 따옴표로 감싼 같은 숫자도 열이 정수 친화성(affinity)이라 real 이 됐습니다. 64비트를 넘는 값은 TEXT 열에 넣습니다.
기본 키가 한계에 닿으면:
sqlite> CREATE TABLE t(id INTEGER PRIMARY KEY, n INTEGER);
sqlite> INSERT INTO t(id, n) VALUES (9223372036854775807, 1);
sqlite> INSERT INTO t(n) VALUES (2);
sqlite> SELECT * FROM t;
712334064962770860|2
9223372036854775807|1
가장 큰 rowid 가 쓰였으면 SQLite 는 비어 있는 값을 무작위로 고릅니다. 재사용도 역행도 하지 않겠다고 약속하는 AUTOINCREMENT 에서는 같은 삽입이 Error: stepping, database or disk is full (13) 로 실패했습니다. 하나씩 세어서 922경에 닿을 일은 없지만, 누가 거대한 ID 를 손으로 넣으면 닿습니다. SQL 기초는 SELECT·WHERE·ORDER BY를 보세요.
흔한 실수와 타입 고르기
| 실수 | 실제로 일어나는 일 | 고치는 법 |
|---|---|---|
int a, b 로 long long t = a * b;(자바 long t = a * b 도) |
먼저 32비트로 곱해서 넘침: -1294967296 |
한쪽을 넓힌다: (long long)a * b |
밀리초를 int 로 센다 |
2^31 밀리초는 약 24.8일 | 64비트로 |
음수가 아니게 하려는 Math.abs(x) |
Math.abs(Integer.MIN_VALUE) 는 -2147483648 |
최솟값을 확인하거나 long 으로 |
| JS 클라이언트에 숫자 ID 를 JSON 으로 | 1234567890123456789 가 1234567890123456800 으로 도착 |
ID 는 문자열로 보낸다 |
BigInt 와 Number 섞기 |
TypeError: Invalid mix of BigInt and other type |
명시적으로 바꾸고 값마다 타입 하나만 |
| 2^63 을 넘어도 PHP int 연산을 믿기 | 조용히 float 가 되고 == PHP_INT_MAX 가 참 |
bcmath 나 gmp |
| Dart 코드 하나를 VM 과 웹에서 공유 | 웹에서 9223372036854775807 이 9223372036854776000 |
2^53 미만으로 유지하거나 BigInt |
타입 고르기:
| 저장하는 것 | 쓸 타입 | 피할 타입 |
|---|---|---|
| 개수, 수량, 약 9×10^15 미만의 최소 통화 단위(원·센트) | 64비트 정수(long, Int, PHP/Dart VM int, INTEGER) |
합계가 커질 수 있으면 32비트 int |
| DB 나 다른 서비스의 ID | JSON 과 JS 에서는 문자열, 서버에서는 64비트 정수 | JS Number |
| 2^63 을 넘어 계산해야 하는 정수 | BigInteger, 파이썬 int, BigInt, gmp |
float |
| 조용히 넘치면 안 되는 값 | 자바 *Exact, C# checked, Swift 기본 연산자, __builtin_*_overflow |
C/Java/C# 의 맨 + |
돈(센트, decimal 형, 10원 단위 절사)은 4회와 3회에서 다룹니다. 타입 기초는 자바 타입과 PHP 타입에 있습니다.
측정: JSON.parse 를 무사히 통과하는 ID 비율과 BigInt 비용
JSON.parse 를 멀쩡히 통과하는 ID 는 얼마나 되나? 다섯 범위마다 파이썬(random.seed(7))으로 무작위 정수 100,000개를 만들어 JSON 숫자와 문자열 두 가지로 쓰고, jsc 에서 읽어 숫자를 문자열과 BigInt 로 비교했습니다.
| 범위 | 무사한 개수 | 최대 오차 |
|---|---|---|
| 2^53 - 1 이하 | 100,000 (100%) | 0 |
| 2^53 ~ 2^54 | 49,994 (49.99%) | 1 |
| 2^54 ~ 2^56 | 16,718 (16.72%) | 4 |
| 2^59 ~ 2^61 (Snowflake 크기) | 526 (0.53%) | 128 |
| 10^18 ~ 2^63 (19자리) | 189 (0.19%) | 512 |
2^53 을 넘으면 무사한 건 간격의 배수에 우연히 걸린 ID 뿐입니다. 19자리 ID 는 99.8% 가 최대 512 어긋나서 나옵니다.
BigInt 비용은? jsc 에서 0~9,999,999 합계, Apple Silicon Mac(M1 Pro), 7회 중앙값으로 Number 3.2 ms, BigInt 301.3 ms 였고 결과는 둘 다 49999995000000 이었습니다. JIT 가 Number 루프를 아주 잘 최적화하므로 약 94배라는 차이는 상한 정도로 보고 상수로 여기지 마세요. 파이썬 임의 길이 int 로 1,000,000개를 누적한 시간(7회 중앙값):
| 시작 값 | Python 3.9.6 | Python 3.12.14 |
|---|---|---|
| 0 | 27.7 ms | 26.6 ms |
| 2**64 | 28.1 ms | 26.9 ms |
| 2**256 | 30.6 ms | 29.0 ms |
| 2**4096 | 141.8 ms | 138.0 ms |
큰 정수가 공짜는 아니지만 수백 비트까지는 파이썬이 거의 티를 내지 않습니다. BigInt 는 필요한 곳에만 쓰면 됩니다.
솔직한 한계와 버전 메모
- C 정수 크기는 macOS arm64 의 clang 기준입니다. 64비트 Windows 에서는
long이 32비트입니다(여기서 실행하지 않음). - C 부호 있는 오버플로 결과는 이 컴파일러가
-O0에서 낸 값입니다. 표준은 아무것도 보장하지 않습니다. - Swift 트랩은 캡처한 출력에 메시지 없이 종료 상태 133 만 남았습니다. Xcode 디버거에서는 더 보입니다(여기서 실행하지 않음).
JSON.parse원문 접근과JSON.rawJSON은 이 Mac 의jsc에서만 확인했습니다. 파이썬 4,300자리 제한은 3.12.14 에서ValueError: Exceeds the limit (4300 digits) for integer string conversion이 났고, 3.9.6 에서는 5,001자리를 출력했습니다. 제한은 3.9.14 부터 들어갔습니다.- Dart 에서 변환한 자바스크립트는
jsc에서 실행했고 브라우저에서는 시험하지 않았습니다. Node.js 도 실행하지 않았습니다. - 시간은 한 대에서 잰 값입니다. 비율을 보세요.
연습 문제
1. 자바에서 3000000000 을 출력하는 것은 어느 것인가요?
int price = 2_000_000, qty = 1500;
long a = price * qty;
long b = (long) price * qty;
long c = (long) (price * qty);
2. API 가 {"id": 9007199254740993} 을 돌려주고, 웹 클라이언트는 그 ID 를 되돌려 보내 레코드를 지웁니다. jsc 에서 ID 는 무엇이 되고, API 는 어떻게 바꿔야 할까요?
3. 파이썬에서 x = 2**31 은 문제없습니다. 같은 값이 오류가 나는 곳을 하나 들고 오류 메시지를 적어 보세요.
정답
1. b 뿐입니다. 실행 결과는 a b c 순서로 -1294967296 3000000000 -1294967296 이었습니다. a 와 c 는 둘 다 먼저 32비트로 곱하고, c 의 캐스트는 오버플로 뒤에 일어납니다.
2. JSON.parse 결과는 1 작은 9007199254740992 여서 클라이언트는 ...993 이 아니라 ...992 를 지우려 합니다. ID 를 "id": "9007199254740993" 처럼 문자열로 돌려주고 클라이언트에서도 문자열로 다룹니다.
3. struct.pack('<i', 2**31) 이 struct.error: 'i' format requires -2147483648 <= number <= 2147483647 를 냈습니다. 바이너리 파일 형식, C 라이브러리, 32비트 DB 열에 쓸 때도 같은 일이 생깁니다.
다음 회
3회는 반올림입니다. 파이썬 round(2.5) 가 왜 2 인지, toFixed 가 실제로 무엇을 하는지, 부가세를 품목마다 계산하느냐 합계에서 계산하느냐에 따라 결과가 어떻게 달라지는지 봅니다.
자주 묻는 질문
JSON.parse 를 거치면 ID 뒷자리가 바뀌는 이유는?
자바스크립트 숫자는 double 이라서 모든 정수를 정확히 담는 건 2^53 - 1(Number.MAX_SAFE_INTEGER, 9007199254740991)까지입니다. 그 위로는 가장 가까운 double 이 선택됩니다. jsc 에서 1234567890123456789 는 1234567890123456800, 9007199254740993 은 9007199254740992 가 됐습니다. 큰 ID 는 JSON 문자열로 보내고 계산이 필요할 때만 BigInt 로 바꿉니다.
자바나 C# 에서 int 가 넘치면 어떻게 되나요?
기본적으로 둘 다 조용히 한 바퀴 돕니다. Integer.MAX_VALUE + 1 은 -2147483648, 2,000,000 × 1,500 은 -1294967296 이 나왔습니다. 자바 Math.addExact, Math.multiplyExact 는 ArithmeticException: integer overflow 를 던지고, C# 은 checked(...) 안이나 CheckForOverflowUnderflow 를 켰을 때 OverflowException 을 던집니다.
파이썬 정수도 오버플로가 나나요?
나지 않습니다. 필요한 만큼 자릿수가 늘어나서 (2**63 - 1) + 1 은 9223372036854775808 로 출력됐습니다. 한계는 바깥 세계와 만나는 곳에서 나옵니다. struct.pack('<i', 2**31) 은 struct.error 가 나고, 2022년 보안 릴리스(3.11, 3.10.7, 3.9.14 이후)부터는 4,300자리가 넘는 int 를 문자열로 바꾸면 기본적으로 ValueError 가 납니다.