尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

C++ __int128 完全指南:解决 long long 溢出与输入输出

发布时间:2026/9/28 17:42:23

资讯中心
01
ARTICLE

C++ __int128 完全指南:解决 long long 溢出与输入输出

C++ __int128 完全指南:解决 long long 溢出与输入输出
1. 为什么需要 __int128从 long long 的边界说起做算法题或者写数值计算程序的时候几乎每个人都遇到过这样的场景题目要求对两个大整数做乘法结果要对某个数取模你信心满满地写了long long本地样例全过一提交就 WA盯着代码看了半小时才发现——溢出了。long long是有符号 64 位整数能表示的范围是 -9223372036854775808 到 9223372036854775807也就是大约 9.2×10^18。这个范围听起来很大但在算法竞赛和某些工程场景里它经常不够用。最典型的例子就是两个 10^9 级别的数相乘结果直接冲到 10^18 量级如果还要再乘一个数或者累加多次溢出几乎是必然的。这时候很多人的第一反应是用大数库比如 Java 的BigInteger或者 Python 的原生大整数。但在 C 里标准库并没有提供任意精度整数。手写高精度用数组模拟当然可以但代码量大、容易出错、性能也不一定好。而__int128就是 GCC/Clang 提供的一个中间档解决方案——它比long long大一圈能表示到约 1.7×10^38足以覆盖绝大多数算法题和工程计算的需求而且它是编译器原生支持的运算速度接近硬件级别用起来和普通整数几乎一样。这篇文章就是围绕__int128展开的。我会从它的本质、适用场景、输入输出的坑、实际代码模板一直讲到常见问题的排查。适合正在刷算法题、参加竞赛、或者写数值计算程序时被溢出折磨过的 C 程序员。如果你还在用long long硬扛大数乘法这篇内容应该能帮你省下不少调试时间。2. __int128 的本质与适用边界2.1 它到底是什么编译器扩展而非标准类型先说一个很多人容易误解的点__int128不是 C 标准的一部分。你在 C 标准文档里翻不到它cstdint里也没有它的定义。它是 GCC 和 Clang 这两个编译器提供的扩展类型属于编译器送你用的东西。这意味着两件事。第一如果你用的是 MSVCVisual Studio 自带的编译器__int128是不存在的编译直接报错。第二即使你用 GCC/Clang代码的可移植性也会打折扣——换到不支持它的平台上就跑不了。所以用之前先确认你的编译环境这一点后面会专门讲。它的完整写法是__int128有符号和unsigned __int128无符号。在 64 位平台上它占 16 个字节也就是 128 位。有符号版本的范围大约是 -1.7×10^38 到 1.7×10^38无符号版本是 0 到约 3.4×10^38。2.2 和 long long、大数库的对比为了让你直观感受__int128的定位我整理了一张对比表类型位数大致范围是否标准运算速度输入输出支持int32±2.1×10^9是最快原生long long64±9.2×10^18是快原生__int128128±1.7×10^38否扩展快需手写手写高精度任意任意是慢需手写boost::multiprecision任意任意是库中等需适配从表里能看出来__int128的甜点区非常明确当你的数值超过 64 位但不超过 128 位时它是最优解。运算速度接近原生整数代码改动量极小唯一麻烦的就是输入输出需要自己处理。2.3 什么时候该用它什么时候不该用我个人的判断标准是这样的该用算法题里两个 10^9 级别的数相乘、快速幂取模、组合数计算、哈希时的乘法防溢出、某些几何计算中的叉积。不该用需要超过 128 位的精度比如计算 100 的阶乘这时候老老实实上高精度或者 boost需要跨平台兼容 MSVC 的项目对性能极度敏感且能用 64 位搞定的场景。有一个很实用的技巧当你写a * b担心溢出时可以先把其中一个数转成__int128再乘这样整个表达式就按 128 位计算了。比如(__int128)a * b这是最常见的用法改动量只有一个强制转换。3. 输入输出的完整解决方案3.1 为什么 cin/cout 和 scanf/printf 都不行这是新手最容易踩的坑。你兴冲冲地定义了一个__int128 x然后写cin x编译器直接给你一长串错误信息。原因很简单标准库的输入输出流只针对内置的标准类型做了重载__int128是扩展类型标准库根本不认识它。scanf和printf也一样。你找不到%d之外的格式符能对应 128 位整数%lld只能处理 64 位。所以输入输出必须自己写函数。3.2 手写输入函数逐字符解析输入的核心思路是把数字当成字符串读进来逐位累加。因为__int128本身支持乘 10 和加个位数的运算所以这个过程很自然。#include bits/stdc.h using namespace std; __int128 read() { __int128 x 0, f 1; char ch getchar(); while (ch 0 || ch 9) { if (ch -) f -1; ch getchar(); } while (ch 0 ch 9) { x x * 10 (ch - 0); ch getchar(); } return x * f; }这段代码的逻辑很直白先跳过所有非数字字符如果遇到负号就把符号位设成 -1然后每读到一个数字字符就把当前结果乘 10 再加上这个数字。getchar()比cin快很多适合大量输入的场景。注意这个函数用的是getchar()如果你的程序里混用了cin要注意缓冲区同步问题。稳妥的做法是全程用 C 风格输入输出或者在用cin之前加ios::sync_with_stdio(false)并避免混用。3.3 手写输出函数递归或栈输出比输入稍微绕一点因为你要把数字拆成一个个字符。最优雅的写法是递归void print(__int128 x) { if (x 0) { putchar(-); x -x; } if (x 9) print(x / 10); putchar(x % 10 0); }递归的思路是先把高位全部输出再输出最低位。x 9的时候递归处理x / 10这样会一直深入到最高位然后逐层返回时输出每一位。对于__int128来说最多 39 位数字递归深度完全不用担心栈溢出。如果你不喜欢递归也可以用字符数组手动模拟void print(__int128 x) { if (x 0) { putchar(0); return; } if (x 0) { putchar(-); x -x; } char buf[50]; int len 0; while (x 0) { buf[len] 0 (x % 10); x / 10; } while (len--) putchar(buf[len]); }两种写法效果一样递归版更简洁数组版更直观。我个人在比赛里习惯用递归版因为代码短不容易写错。3.4 一个完整的输入输出模板把上面的东西拼起来就是一个可以直接抄的模板#include bits/stdc.h using namespace std; __int128 read() { __int128 x 0, f 1; char ch getchar(); while (ch 0 || ch 9) { if (ch -) f -1; ch getchar(); } while (ch 0 ch 9) { x x * 10 (ch - 0); ch getchar(); } return x * f; } void print(__int128 x) { if (x 0) { putchar(-); x -x; } if (x 9) print(x / 10); putchar(x % 10 0); } int main() { __int128 a read(); __int128 b read(); print(a b); putchar(\n); print(a * b); putchar(\n); return 0; }这个模板我用了很多次实测在各种在线评测系统上都能稳定运行。注意print函数在输出后不会自动换行需要自己加putchar(\n)。4. 实战场景与核心代码4.1 场景一大数乘法防溢出这是__int128最经典的用法。假设你要计算a * b % mod其中a、b、mod都是long long范围内的数。直接写a * b % mod在a和b都接近 10^9 时会溢出因为乘积达到 10^18 虽然还在long long范围内但如果a和b接近 10^18乘积就是 10^36直接爆掉。long long mul_mod(long long a, long long b, long long mod) { return (__int128)a * b % mod; }一行搞定。(__int128)a * b先把a提升到 128 位乘法的结果也是 128 位取模后再转回long long。这个写法比快速乘用加法模拟乘法简洁得多而且速度快。4.2 场景二快速幂中的中间结果快速幂算法里每一步都要做base base * base % mod。如果mod接近 10^18base * base就会达到 10^36必须用__int128兜底long long fast_pow(long long base, long long exp, long long mod) { long long result 1; base % mod; while (exp 0) { if (exp 1) result (__int128)result * base % mod; base (__int128)base * base % mod; exp 1; } return result; }这段代码在模数很大的时候特别有用比如某些数论题目里mod是 10^18 级别的质数。4.3 场景三组合数计算计算组合数 C(n, m) 时中间结果往往很大。如果题目要求输出精确值而不是取模且 n 不太大比如 n ≤ 30可以用__int128直接算__int128 C(int n, int m) { if (m n - m) m n - m; __int128 res 1; for (int i 1; i m; i) { res res * (n - m i) / i; } return res; }这里每一步都先乘后除保证整除。__int128能覆盖 C(60, 30) 这样的量级约 1.18×10^17再大就不行了。4.4 场景四哈希中的乘法字符串哈希或者多项式哈希里经常要做hash hash * base ch这样的运算。如果base和hash都很大乘法会溢出。用__int128可以安全地计算后再取模const long long MOD 1000000000000000003LL; const long long BASE 131; long long compute_hash(const string s) { long long h 0; for (char c : s) { h ((__int128)h * BASE c) % MOD; } return h; }4.5 场景五几何计算中的叉积计算多边形面积或者判断点在线段的哪一侧时会用到叉积(x2-x1)*(y3-y1) - (x3-x1)*(y2-y1)。如果坐标范围是 10^9叉积的中间结果会达到 10^18 甚至更大用__int128可以避免精度问题__int128 cross(__int128 x1, __int128 y1, __int128 x2, __int128 y2) { return x1 * y2 - x2 * y1; }5. 常见问题与排查技巧5.1 编译报错__int128 未定义最常见的原因是你用了 MSVC。__int128只在 GCC 和 Clang 上可用。如果你在 Windows 上用 Visual Studio需要换成 MinGW 或者 WSL 里的 GCC。在 VS Code 里配置 C/C 环境时也要注意选择正确的编译器。另一个可能的原因是你用的 GCC 版本太老。__int128从 GCC 4.6 开始支持现在主流版本都没问题但如果你在某个老旧的在线评测系统上可能会遇到不支持的情况。5.2 输出结果不对符号处理遗漏手写输出函数时最容易忘的就是负号处理。如果你的print函数没有处理x 0的情况负数会输出成一堆乱码。记住先判断符号再取绝对值然后递归输出。还有一个隐蔽的坑x -x在x是最小负数时会溢出。不过对于__int128来说这个边界值极其罕见实际使用中可以忽略。5.3 性能问题输入输出太慢如果你用cin/cout配合手写的read/print可能会因为缓冲区不同步导致性能下降。解决方案是全程用getchar/putchar或者加上ios::sync_with_stdio(false); cin.tie(0);。在大量输入的场景下getchar版本的read函数比cin快 3 到 5 倍这个差距在数据量大的时候非常明显。5.4 类型转换的陷阱__int128和long long之间的隐式转换有时候会出问题。比如long long a 1000000000; long long b 1000000000; __int128 c a * b; // 错误a*b 先按 long long 计算已经溢出了正确的写法是__int128 c (__int128)a * b;强制先把a提升到 128 位。这个坑我踩过不止一次尤其是在表达式复杂的时候很容易忘记加转换。5.5 常见问题速查表问题现象可能原因解决方法编译报错 __int128 未定义用了 MSVC 或老版本 GCC换 GCC/Clang升级编译器输出乱码没处理负号在 print 函数里先判断符号结果溢出忘记强制类型转换在乘法前加 (__int128)输入输出慢混用了 cin 和 getchar统一用 C 风格 IO取模结果为负负数取模的符号问题加 mod 再取模6. 几个容易被忽略的细节6.1 无符号版本的取舍unsigned __int128的范围是 0 到约 3.4×10^38比有符号版本大一倍。如果你的计算确定不会出现负数用无符号版本能多出一倍的表示范围。但要注意无符号版本的输入输出函数需要单独写不能直接复用有符号版本的代码。6.2 和位运算的配合__int128支持所有的位运算与、或、异或、左移、右移。这在处理位掩码或者状态压缩时很有用。比如(__int128)1 100可以生成一个第 100 位为 1 的数这在long long里是做不到的。6.3 在结构体和类中使用__int128可以作为结构体成员也可以作为函数参数和返回值。但要注意它不能直接用于printf和cin所以在结构体的输入输出方法里也要用自定义函数。6.4 调试时的打印技巧调试的时候如果不想写完整的print函数可以用一个简单的宏#define PRINT(x) do { __int128 _t (x); if (_t 0) { putchar(-); _t -_t; } \ if (_t 9) print(_t / 10); putchar(_t % 10 0); } while(0)这样在代码里随时可以PRINT(ans)查看中间结果不用每次都写完整的输出语句。7. 我个人的使用体会用了几年__int128最大的感受就是它填补了long long和高精度之间的空白而且填补得恰到好处。绝大多数算法题里数值范围不会超过 128 位这时候用__int128比手写高精度省事太多性能也好得多。但我也踩过不少坑。最开始不知道cin不支持它调了半天后来又在类型转换上栽跟头a * b忘了加(__int128)导致溢出再后来是在 MSVC 上编译失败才发现它不支持。这些坑现在都成了肌肉记忆写代码的时候会下意识地检查。如果你经常做算法题或者写数值计算程序我建议把第 3.4 节那个模板保存下来需要的时候直接复制。输入输出这两个函数是绕不过去的与其每次重写不如整理成自己的代码片段库。最后分享一个小技巧如果你不确定某个中间结果会不会超过long long可以先用__int128算一遍然后打印出来看看。如果结果在long long范围内再改回去用long long提升性能如果超了就保留__int128。这个先保守后优化的策略能帮你在正确性和性能之间找到平衡。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。