
备战蓝桥杯Java组的同学到了第十天基本已经把基础语法、集合框架、常用算法都过了一遍这时候最容易卡住的一个点就是高精度计算。这里的“高精度”不是单片机调ADC采样精度也不是数据处理里的精度校准而是纯粹意义上的大整数运算——你用long存不下、用double会丢精度、老老实实自己写数组模拟又容易在进位借位前导零这些细节上翻车的那些题。蓝桥杯Java组里高精度很少单独出成一道压轴难题但它经常作为递推、组合数学、快速幂的中转站不会它很多题做到一半就断档了。这篇文章我把高精度在Java里的三种主流玩法——BigInteger、BigDecimal、手写数组从原理到真题再到坑点一次讲透适合正在刷蓝桥杯真题、或者面试前突击Java大数操作的同学直接抄作业。1. 高精度问题到底是什么蓝桥杯Java组为什么躲不开1.1 算不下的数大整数运算的本质先聊一个最根本的问题什么时候需要高精度Java里的long类型最大能表示约9.22乘以10的18次方这个数字看起来很大但放到算法题里根本不是个事。题目里随便来一个“求100的阶乘”“求斐波那契数列第500项”“求2的1000次方”结果立刻膨胀到几十位甚至几百位数字long直接溢出double虽然能表示很大范围但精度不够低位数字全是错的。高精度计算的本质就是把人手算竖式的过程交给程序去做把一个超长的数字拆成一位一位或者几位一组存进数组或者字符串里然后逐位相加、相减、相乘处理进位和借位。BigInteger在Java底层干的就是这件事只不过它帮你把竖式运算封装好了内部用int数组存储数值每个数组元素保存一段二进制数据对外提供add、subtract、multiply这些看起来像普通整数运算的方法。我在带学生刷题时经常说一句话高精度题考的不是你会不会乘法口诀而是你能不能把“竖式”这个小学概念转化成边界条件清晰、不会越界、不会漏进位的代码。很多同学一看数据范围是10的100次方就开始慌其实只要理解了存储方式和运算逻辑这类题反而是送分题。1.2 高精度在蓝桥杯里的出场方式蓝桥杯的高精度题直接出“大数加法”“大数减法”其实很少因为太直白区分度不够。真正常见的是下面几种变形第一种是作为基础练习出现比如“高精度减法”分数10分输入两个长度不超过100位的正整数输出差。这种题基本就是送分但每年都有不少人在前导零和负数处理上丢分。第二种是藏在递推和动态规划里。斐波那契、卡特兰数、第二类斯特林数这些数列项数一大数值就是天文数字。题目本身考的是递推公式或者DP状态转移但你的变量类型必须是高精度才能装下最终结果。第三种是结合数论比如大数取模、快速幂取模、组合数计算。这时候BigInteger的modPow、modInverse这些方法就非常有用能帮你跳过手写快速幂的麻烦。还有一类比较偏的就是进制转换。比如蓝桥杯基础练习里的“十六进制转八进制”数据范围大到直接用Long会溢出这时候用BigInteger的toString(8)一行搞定。把这几种出场方式串起来看你会发现高精度在蓝桥杯里更像是一个“基础设施”就像你写工程代码时要用的日志工具一样单独拿出来不值钱但没有它很多功能就实现不了。2. Java选手的高精度三板斧BigInteger、BigDecimal、数组模拟2.1 BigInteger 高频方法速查与选择逻辑BigInteger是Java里处理大整数的官方方案位于java.math包下不需要额外导入第三方库。它的用法和我们熟悉的int、long几乎一致但注意所有运算都返回新的BigInteger对象因为BigInteger是不可变类。我整理了一张备战蓝桥杯时最常用的方法表你直接照着查就行方法作用使用示例add加法a.add(b)subtract减法a.subtract(b)multiply乘法a.multiply(b)divide除法结果截断a.divide(b)remainder取余符号与被除数相同a.remainder(b)mod取模结果恒为非负a.mod(b)pow幂运算a.pow(10)modPow模幂运算快速幂取模a.modPow(exp, mod)modInverse模逆元要求与mod互质a.modInverse(mod)gcd最大公约数a.gcd(b)compareTo比较大小返回-1、0、1a.compareTo(b)toString(radix)按指定进制转字符串a.toString(16)valueOf将long转成BigIntegerBigInteger.valueOf(100)isProbablePrime概率素数判断a.isProbablePrime(100)shiftLeft / shiftRight左移右移a.shiftLeft(10)testBit判断某一位是否为1a.testBit(0)实际用的时候有几个细节值得注意。第一代码里尽量用BigInteger.ZERO、BigInteger.ONE、BigInteger.TEN这些常量不要每次都valueOf(0)去创建对象虽然影响不大但养成好习惯总没错。第二BigInteger的equals方法比较的是数值是否相等而compareTo也是比较数值大小两者在BigInteger这里语义一致但到了BigDecimal那儿就有坑了这个后面细说。第三new BigInteger(String)和valueOf(long)的适用场景不同前者用来解析输入的大数字符串后者用来把int、long值转换进去。2.2 BigDecimal小数高精度的隐藏考点BigInteger解决的是整数高精度BigDecimal解决的是小数高精度。Java里float和double都是浮点数的二进制近似表示0.1加0.2这种简单运算都会得到一个莫名其妙的结果比如0.30000000000000004。高精度小数题在蓝桥杯里出现频率不高但在一些模拟题、计算几何题、概率题里会作为干扰项出现。BigDecimal的核心方法包括add、subtract、multiply、divide、setScale、compareTo、stripTrailingZeros、toPlainString。其中最容易出问题的是divide因为小数除法可能除不尽你必须指定精度和舍入方式否则会抛ArithmeticException。比如BigDecimal a new BigDecimal(10); BigDecimal b new BigDecimal(3); BigDecimal result a.divide(b, 10, RoundingMode.HALF_UP); // 保留10位小数四舍五入这个10代表保留的小数位数RoundingMode.HALF_UP是我们熟悉的四舍五入还有HALF_DOWN、CEILING、FLOOR等模式具体用哪个要看题目要求。另一个常见的坑是BigDecimal的equals和compareTo不一致。equals不仅比较数值还比较精度scale所以new BigDecimal(1.0)和new BigDecimal(1.00)用equals判等是false但用compareTo判等是true。蓝桥杯里比较小数大小一律用compareTo别用equals。最后别忘了println一个BigDecimal时如果数值很大或很小可能输出科学计数法比如1E20。如果题目要求输出完整数字用toPlainString方法转成不带指数的字符串。2.3 什么时候别用BigInteger自己写数组反而更快说句实在话BigInteger虽然好用但性能确实不如手写数组。原因在于BigInteger是不可变对象每次运算都new一个新对象而且内部存储用的是int数组表示二进制大整数加减乘除都要重新分配内存。数据长度只有几十位时区别不大但一旦数字长度达到几千位或者循环执行几万次BigInteger可能直接把你卡到超时。我遇到过一道题要求计算10000的阶乘用BigInteger循环乘本地跑了两秒多换成手写数组模拟乘法不到一百毫秒就跑完了。蓝桥杯Java组的评测机配置不算高时间限制经常是1秒这种差距就是AC和TLE的区别。所以我的建议是如果题目数据长度在100位以内或者只需要做几次运算直接无脑用BigInteger如果题目要求输出几百上千位的大数而且涉及大量乘法和加法自己用int数组写一个简单的高精度模板更稳妥。后面第三章我会给出具体的模板代码你直接背下来用就行。3. 真题拆解从高精度减法到大数递推一步步AC3.1 10分的高精度减法读题、写码、过样例先看一道最典型的高精度减法题。题目描述很简洁计算两数之差输入共两行第一行是被减数a第二行是减数b题目保证a大于b每个数的长度不超过100位输出a减b的结果。这种题用BigInteger写就是三行核心代码import java.math.BigInteger; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); BigInteger a new BigInteger(sc.nextLine().trim()); BigInteger b new BigInteger(sc.nextLine().trim()); System.out.println(a.subtract(b)); sc.close(); } }注意几个细节读入时用trim去掉可能存在的首尾空格和换行题目说a大于b所以不需要处理负数但如果你做题时发现评测数据里可能出现a等于b的情况那结果就是0BigInteger会正确输出0不会出问题。如果用C的思路手写数组套路就是倒序存储、逐位相减、处理借位、去掉前导零。Java版模板我放在下面public class BigSub { // 高精度减法前提 a b参数为数字字符串返回结果为字符串 public static String subtract(String a, String b) { StringBuilder sb new StringBuilder(); int i a.length() - 1, j b.length() - 1; int borrow 0; while (i 0 || j 0) { int da i 0 ? a.charAt(i) - 0 : 0; int db j 0 ? b.charAt(j) - 0 : 0; int diff da - db - borrow; if (diff 0) { diff 10; borrow 1; } else { borrow 0; } sb.append(diff); i--; j--; } while (sb.length() 1 sb.charAt(sb.length() - 1) 0) { sb.deleteCharAt(sb.length() - 1); } return sb.reverse().toString(); } }这段代码的要点是被减数的每一位和减数的对应位相减再减去借位borrow如果不够减就向高位借1相当于当前位加10borrow置1最后结果为了处理方便是倒着存的所以反转回去同时去掉最高位多余的零。3.2 大数阶乘与斐波那契BigInteger的正确打开方式计算阶乘是大数题的常客。比如输入n输出n的阶乘n的范围可能在1000到10000之间。n等于1000时阶乘有2568位long连零头都装不下。用BigInteger实现阶乘很直接import java.math.BigInteger; import java.util.Scanner; public class Factorial { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); BigInteger result BigInteger.ONE; for (int i 2; i n; i) { result result.multiply(BigInteger.valueOf(i)); } System.out.println(result); sc.close(); } }但这里我要多说一句当n很大时BigInteger的multiply性能问题就会暴露。10000的阶乘循环10000次每次的乘数从1变到10000结果的长度逐渐增长到几万位整体耗时非常可观。如果你的目标是蓝桥杯拿高分建议在这道题上直接上手写数组乘法。手写数组计算阶乘核心是“大数乘以普通整数”的模板public class BigMulInt { // 数组倒序存储大数每个元素存0~9乘以int b返回新数组 public static int[] multiplySmall(int[] a, int b) { if (b 0) return new int[] {0}; int carry 0; int[] res new int[a.length 20]; for (int i 0; i a.length; i) { int cur a[i] * b carry; res[i] cur % 10; carry cur / 10; } int k a.length; while (carry 0) { res[k] carry % 10; carry / 10; } return Arrays.copyOf(res, k); } }这里数组是倒序存的res[0]存个位res[1]存十位依此类推。每次乘完一位把结果模10存下来整除10的部分作为进位往高位传。循环结束后如果进位还没处理完就继续往高位写。斐波那契数列也一样第500项大概有105位数字用BigInteger递推非常轻松public static BigInteger fib(int n) { if (n 1) return BigInteger.valueOf(n); BigInteger a BigInteger.ZERO; BigInteger b BigInteger.ONE; for (int i 2; i n; i) { BigInteger t a.add(b); a b; b t; } return b; }很多同学在递推大数时容易犯一个毛病用int数组去存储每一项结果在计算过程中不断扩容效率很低。其实BigInteger的add运算非常快只有乘法容易成为瓶颈所以纯粹的大数递推用BigInteger问题不大。3.3 进阶套路快速幂取模与组合数大数蓝桥杯里有些题表面上看和高精度没关系比如“计算a的b次方对mod取模”但b可能大到10的18次方如果你直接循环乘循环10的18次方次跑一辈子也跑不完。这时候就要用快速幂而BigInteger恰好提供了现成的modPow方法。BigInteger base new BigInteger(sc.next()); BigInteger exp new BigInteger(sc.next()); BigInteger mod new BigInteger(sc.next()); BigInteger result base.modPow(exp, mod);这个方法内部实现的就是二进制快速幂时间复杂度是O(log exp)而且每一步都取模数字不会无限膨胀。我之前遇到一道题要求计算组合数C(n, m)对一个大质数取模当时我手动写了卢卡斯定理加快速幂后来发现直接组合数公式加modPow也就两行理解原理之后代码可以极简。如果你要算的组合数本身不取模但结果大到离谱那就只能上高精度了。组合数有一个递推公式C(n, 0)等于1C(n, k)等于C(n, k-1)乘以(n-k1)再除以k。这个递推在BigInteger里很容易实现public static BigInteger combination(int n, int k) { BigInteger result BigInteger.ONE; for (int i 1; i k; i) { result result.multiply(BigInteger.valueOf(n - i 1)) .divide(BigInteger.valueOf(i)); } return result; }注意乘法除法的顺序先乘后除这样每一步的结果在数学上仍是整数不会丢精度。中间结果可能会比较大但BigInteger正好管够。再往下扩展像卡特兰数、错排公式、斯特林数套路都是一样的递推公式里出现大整数乘法就上BigInteger出现除不尽的数就上BigDecimal出现取模就用modPow。把这些组合起来10道题里有8道高精度相关题都能解决。4. 高精度题最容易踩的坑附排查方法4.1 不可变对象与循环性能陷阱BigInteger是不可变类这意味写一次循环就是创建几百上千个临时对象内存和时间的开销都不小。我见过一个同学用BigInteger算2的10000次方直接for循环里一遍遍multiply本地跑了几秒才出结果提交就超时。遇到这种“指数级别增长”的运算优先用快速幂思想。BigInteger的pow方法其实内部也用类似快速幂的实现复杂度是O(log n)。比如计算2的10000次方直接BigInteger.TWO.pow(10000)就能秒出结果不要自己循环10000次乘2。如果你的代码确实必须循环几千次做乘法而且数字长度特别大那么手写数组比BigInteger不知道快多少倍。此外循环里尽量复用变量不要在循环体内部new StringBuilder、new数组这些临时对象多了也会拖慢速度。还有个很容易忽略的性能点System.out.println输出大数字符串时底层会逐字符写输出流如果输出内容很大比如几万位的数字可以考虑先拼接到StringBuilder再一次性输出虽然大多数评测环境println也能过但保险起见养成好习惯。4.2 输入输出和边界条件的细节先看输入。很多大数题的输入是一个很长的数字字符串可能有前导零比如“00012”。BigInteger会忽略前导零解析成12没问题。但如果你自己手写字符串处理就要在比较大小或去除前导零时小心。再看输出。BigInteger的toString方法返回十进制字符串不会有科学计数法这一点比BigDecimal省心。但注意println可以直接传BigInteger对象它内部会自动调用toString。边界条件永远是高精度题的扣分重灾区。代码里要专门考虑这么几种情况结果等于0时不能输出空串减法中a等于b时输出0乘法中某个因数为0时数组长度不能是0除法中除数为0直接属于非法输入但题目一般会保证不出现。手写数组还有一个经典的坑数组长度开小了。大数相乘的结果位数最多是两数位数之和所以乘法数组长度要开成a.length加b.length。我以前做乘法题时数组少开了一位结果高位进位时数组越界直接运行时异常排查了好久才找到原因。4.3 手写数组的进位、借位、前导零三板斧如果你决定手写数组那么“进位”“借位”“前导零”这三个坑必须一次性避开。进位处理的原则是加法里当前位结果大于等于10时模10留下个位整除10加到下一位乘法里逐位乘完后集中处理进位因为每一位都可能积累多位数的进位分散处理容易漏。借位处理是减法独有的。建议用一个borrow变量表示当前是否向高位借了1每一位的计算公式是当前位结果等于被减数当前位减减数当前位再减borrow如果结果为负就加10同时borrow置1否则borrow置0。这里最容易出错的是两个数位数不齐的情况短的数高位补0就行。前导零处理是“看着简单扣分最狠”的地方。加法和减法结果最高位可能为0比如100减99等于001你不去掉前导零输出就是“001”直接答案错误。我自己的习惯是输出前用一个循环从数组最高位往前找第一个非0元素然后从这个位置开始倒序输出如果整个数组都是0直接输出0。这个逻辑写成一个函数所有手写数组题统一调用省心很多。还有一个小技巧如果你用int数组逐位存储十进制数那么每个元素只存0到9有些浪费数组空间也影响效率。进阶做法是压位也就是每个数组元素存10000以内的数万进制这样数组长度缩小四倍加法乘法效率大幅提升。压位的进位判断从“大于等于10”变成“大于等于10000”输出时每一位要补0到4位除了最高位。这个技巧在你将来处理几千位大数乘法时会非常有价值。最后再聊一点我自己的体会。这几天带大家刷题有一个很明显的感受高精度题是最容易让人产生“我懂了但写不对”的题。原因在于它的知识点本身不难难的是把每一步边界都想清楚。我的建议是不管你用BigInteger还是手写数组都先把模板在自己电脑上跑通然后用同一套模板去刷五六道真题直到你闭着眼都能写出来为止。等你真正进了考场看到高精度题直接调用模板把这部分分数稳稳拿到手后面的难题才有底气慢慢磨。