数学类 Math:常用的数学工具
本教程共 100 篇 · 第 59 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
本节目标:掌握 Math 类里日常开发最常用的静态方法,看懂取整和取余的几个坑,知道什么时候该换 StrictMath。
做运算时,很多基础数学功能不用自己写。Java 在 java.lang.Math 里备好了一整套静态方法,不用导包、不用 new,直接点出来用。
它是个 final 类,构造方法私有,你根本 new 不出对象——设计上就是纯工具箱。
绝对值、最大值、最小值
最常用的三个,一目了然:
System.out.println(Math.abs(-10)); // 10
System.out.println(Math.max(3, 7)); // 7
System.out.println(Math.min(3, 7)); // 3
这三个方法都针对 int、long、float、double 做了重载,传什么类型就返回什么类型,不会莫名其妙变成 double。
abs 藏着一个几乎没人知道的坑:
System.out.println(Math.abs(Integer.MIN_VALUE)); // -2147483648,还是负的!
因为 int 的负数范围比正数多一个,最小值取绝对值后超出上限,溢出回了自己。做数据校验时如果输入可能是这个边界值,得单独处理,或者转成 long 再取绝对值。
max 和 min 只接受两个参数,没有可变参数版本。求多个数的最值,要么串起来 Math.max(Math.max(a, b), c),要么用后面章节讲的流。
三个取整:floor、ceil、round
取整最容易混淆,把英文单词记住就清楚了。
Math.floor(x):地板,往小了取,得到不大于 x 的最大整数。Math.ceil(x):天花板,往大了取,得到不小于 x 的最小整数。Math.round(x):四舍五入。
System.out.println(Math.floor(2.7)); // 2.0
System.out.println(Math.ceil(2.2)); // 3.0
System.out.println(Math.round(2.5)); // 3
System.out.println(Math.round(2.4)); // 2
注意返回类型不一样。floor 和 ceil 返回 double(所以打印出来带小数点),round 返回 long(参数是 float 时返回 int)。想拿到 int 通常要再强制转换一次。
负数上这三个的差别最明显:
System.out.println(Math.floor(-2.5)); // -3.0,往小了取
System.out.println(Math.ceil(-2.5)); // -2.0,往大了取
System.out.println(Math.round(-2.5)); // -2
Tip
round不是「四舍六入五成双」。Java 的实现本质是先加 0.5 再向下取整,所以正好 0.5 时永远朝正方向进位。这就是为什么 -2.5 得到 -2 而不是 -3。要银行家舍入,用第 60 章的 BigDecimal。
顺带说一句:直接把 double 强制转成 int 是截断,不是四舍五入。(int) 2.9 得到 2,(int) -2.9 得到 -2,和 floor 在负数上也不一样。三种行为区分清楚,别混用。
整除与取余的精确版
普通的除号和取余符号对负数的行为常让人意外:
System.out.println(-5 / 3); // -1,商朝零截断
System.out.println(-5 % 3); // -2,余数带负号
余数是负的,写循环下标、做环形队列、算星期几时立刻出 bug。Math 提供了按「向下取整」定义的两个方法:
System.out.println(Math.floorDiv(-5, 3)); // -2,商朝负无穷取整
System.out.println(Math.floorMod(-5, 3)); // 1,余数符号跟除数走
floorMod 保证结果和除数同号,除数为正时余数一定非负。举个实际场景:把索引 i 循环映射到长度为 n 的数组上,写 Math.floorMod(i, n) 无论 i 正负都能落在合法区间内,写 i % n 就可能给你一个负下标。
幂、根号、对数
System.out.println(Math.pow(2, 10)); // 1024.0,2 的 10 次方
System.out.println(Math.sqrt(9)); // 3.0,平方根
System.out.println(Math.cbrt(27)); // 3.0,立方根
pow 无论传什么都返回 double。算整数次幂再转回 int 时可能有精度损失,对精度敏感就自己写循环乘,或者用 BigInteger 的 pow。
对数和常数也现成:Math.log 是自然对数,Math.log10 以 10 为底,Math.exp(x) 算 e 的 x 次方。
System.out.println(Math.PI); // 3.141592653589793
System.out.println(Math.E); // 2.718281828459045
还有两个精度增强版:Math.expm1(x) 算 e 的 x 次方减一,Math.log1p(x) 算 1 加 x 的自然对数。x 极小时它们比手写公式准得多,做科学计算和金融复利时用得上。
溢出安全的 Exact 系列
int 和 long 相加溢出时不报错,只是悄悄绕回去变成一个错误结果,这类 bug 极难排查:
System.out.println(Integer.MAX_VALUE + 1); // -2147483648,直接变负数
Math 提供了一组带 Exact 后缀的方法,溢出就抛 ArithmeticException:
Math.addExact(Integer.MAX_VALUE, 1); // 抛 ArithmeticException
Math.multiplyExact(100000, 100000); // 溢出,抛异常
配套的还有 subtractExact、incrementExact、decrementExact、negateExact、toIntExact。
在金额、库存、计数这些关键路径上,把「悄悄算错」变成「立刻报错」反而是好事——错误当场暴露,总比几个月后对账对不上强。
生成随机数
Math.random() 返回 0(含)到 1(不含)之间的 double:
double r = Math.random(); // 0.0 ~ 0.999...
int dice = (int)(Math.random() * 6) + 1; // 1 ~ 6 的骰子
第 2 行的套路值得记:乘以范围大小取整,再加上起点。想要 a 到 b 之间的整数,写 (int)(Math.random() * (b - a + 1)) + a。
Note
Math.random()只图省事。需要设种子复现结果(比如单元测试)、需要生成随机 long 或高斯分布,用java.util.Random。Java 17 起还有更现代的RandomGenerator接口,可选多种算法。安全场景(生成令牌、密码盐)必须用SecureRandom,普通随机数是可预测的。
三角函数与其他工具
sin、cos、tan 的参数是弧度不是角度,这是最常见的错误来源:
System.out.println(Math.sin(Math.PI / 2)); // 1.0
System.out.println(Math.toRadians(180)); // 3.14159...,角度转弧度
System.out.println(Math.toDegrees(Math.PI)); // 180.0,弧度转角度
反三角 asin、acos、atan,以及双曲函数 sinh、cosh、tanh 也都齐备。算两点间夹角常用 atan2(y, x),它能正确区分四个象限。
几个容易被忽略但很好用的:
Math.hypot(x, y):算直角三角形斜边,比手写更抗溢出。Math.signum(x):取符号,返回 -1.0、0.0 或 1.0。Math.copySign(x, y):把 y 的符号安到 x 的数值上。Math.clamp(x, min, max):Java 21 新增,把 x 夹在区间内,超出取边界。以前得写嵌套三元运算符,现在一行。Math.ulp(x):x 和相邻浮点数的间距,判断浮点是否「足够接近」时用。
Math 与 StrictMath 的区别
Math 为了快,底层可能调用 CPU 的本地浮点指令,不同平台上超越函数的结果可能差最后一位。StrictMath 则用固定算法,严格保证任何机器上逐位一致。
System.out.println(StrictMath.sqrt(2)); // 跨平台结果完全确定
两个类的方法名几乎完全对应,换个类名就能切。
Tip绝大多数业务代码用
Math就够了,快且够准。只有在分布式计算要求各节点结果完全一致、或者游戏需要确定性回放这类场景,才值得换StrictMath。
常见疑问
问:Math 的方法都是静态的,会有线程安全问题吗?
不会。它们全是纯函数,只依赖参数、不改任何共享状态,多线程随便调。唯一的例外是 Math.random(),它内部有一个共享的随机数生成器,高并发下有竞争,性能敏感时改用 ThreadLocalRandom。
问:为什么 Math.pow(10, 2) 打印出 100.0 而不是 100?
因为返回类型是 double。想要整数就强制转换 (int) Math.pow(10, 2),但要留意大数时的精度问题。
问:Math.round(2.5) 和 Math.round(-2.5) 为什么不对称?
Java 的 round 定义是加 0.5 后向下取整。2.5 加 0.5 等于 3.0 向下取整还是 3;-2.5 加 0.5 等于 -2.0 向下取整就是 -2。规则本身是对称的,只是「朝正方向进位」在负数上看着别扭。
三角函数与弧度
sin、cos、tan 的参数是弧度不是角度,这是新人最常踩的单位坑:
Math.sin(Math.PI / 2); // 1.0,不是 sin(90)
Math.toRadians(180); // 角度转弧度
Math.toDegrees(Math.PI); // 弧度转角度
想算直角三角形斜边用 hypot(x, y),比手写 sqrt(x*x+y*y) 更准更稳,底层做了溢出保护。
溢出保护:Exact 系列
int、long 相加溢出时不会报错,只是悄悄变成错误结果,极难查。addExact、multiplyExact 等会在溢出时抛 ArithmeticException,把”悄悄错”变成”立刻报错”:
Math.addExact(Integer.MAX_VALUE, 1); // 抛 ArithmeticException
在金额、计数等关键场景,用它比裸加号安全得多。
一个记忆法
floor 像地板(往小取)、ceil 像天花板(往大取)、round 像四舍五入。三个单词记住,取整方法就不再混。浮点相等别用 ==,用 Double.compare 配极小容差,因为 NaN 和正负零用 == 会得到反直觉的结果。
小结
Math 是躺在 java.lang 里的数学工具箱,全是静态方法,随取随用。abs/max/min 求绝对值和最值,注意 abs 在 int 最小值上会溢出。floor/ceil/round 三种取整返回类型各不相同,强制转换是截断。负数取余用 floorMod 才不会给你负数。关键计算用 addExact 系列把溢出变成异常。随机数图省事用 Math.random,要可控换 Random,要安全换 SecureRandom。跨平台结果必须逐位一致时才换 StrictMath。