首页 / Java 入门教程 / 数学类 Math:常用的数学工具

Java 入门教程

数学类 Math:常用的数学工具

本教程共 100 篇 · 第 59 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程MathStrictMath数学运算

59. 数学类 Math

本节目标:掌握 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
```java

这三个方法都针对 intlongfloatdouble 做了重载,传什么类型就返回什么类型,不会莫名其妙变成 double

`abs` 藏着一个几乎没人知道的坑:

```java
System.out.println(Math.abs(Integer.MIN_VALUE)); // -2147483648,还是负的!

因为 int 的负数范围比正数多一个,最小值取绝对值后超出上限,溢出回了自己。做数据校验时如果输入可能是这个边界值,得单独处理,或者转成 long 再取绝对值。

maxmin 只接受两个参数,没有可变参数版本。求多个数的最值,要么串起来 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
```java

注意返回类型不一样。`floor` 和 `ceil` 返回 double(所以打印出来带小数点),`round` 返回 long(参数是 float 时返回 int)。想拿到 int 通常要再强制转换一次。

负数上这三个的差别最明显:

```java
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,余数带负号
```java

余数是负的,写循环下标、做环形队列、算星期几时立刻出 bug。Math 提供了按「向下取整」定义的两个方法:

```java
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,立方根
```java

`pow` 无论传什么都返回 double。算整数次幂再转回 int 时可能有精度损失,对精度敏感就自己写循环乘,或者用 BigInteger 的 pow。

对数和常数也现成:`Math.log` 是自然对数,`Math.log10` 以 10 为底,`Math.exp(x)` 算 e 的 x 次方。

```java
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 系列

intlong 相加溢出时不报错,只是悄悄绕回去变成一个错误结果,这类 bug 极难排查:

System.out.println(Integer.MAX_VALUE + 1); // -2147483648,直接变负数
```java

Math 提供了一组带 Exact 后缀的方法,溢出就抛 `ArithmeticException`:

```java
Math.addExact(Integer.MAX_VALUE, 1);  // 抛 ArithmeticException
Math.multiplyExact(100000, 100000);   // 溢出,抛异常

配套的还有 subtractExactincrementExactdecrementExactnegateExacttoIntExact

在金额、库存、计数这些关键路径上,把「悄悄算错」变成「立刻报错」反而是好事——错误当场暴露,总比几个月后对账对不上强。

生成随机数

Math.random() 返回 0(含)到 1(不含)之间的 double:

double r = Math.random();                 // 0.0 ~ 0.999...
int dice = (int)(Math.random() * 6) + 1;  // 1 ~ 6 的骰子
```bash

2 行的套路值得记:乘以范围大小取整,再加上起点。想要 a 到 b 之间的整数,写 `(int)(Math.random() * (b - a + 1)) + a`。

> [!NOTE]
> `Math.random()` 只图省事。需要设种子复现结果(比如单元测试)、需要生成随机 long 或高斯分布,用 `java.util.Random`。Java 17 起还有更现代的 `RandomGenerator` 接口,可选多种算法。安全场景(生成令牌、密码盐)必须用 `SecureRandom`,普通随机数是可预测的。

## 三角函数与其他工具

`sin`、`cos`、`tan` 的参数是**弧度不是角度**,这是最常见的错误来源:

```java
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,弧度转角度

反三角 asinacosatan,以及双曲函数 sinhcoshtanh 也都齐备。算两点间夹角常用 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)); // 跨平台结果完全确定
```bash

两个类的方法名几乎完全对应,换个类名就能切。

> [!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.50.5 等于 3.0 向下取整还是 3-2.50.5 等于 -2.0 向下取整就是 -2。规则本身是对称的,只是「朝正方向进位」在负数上看着别扭。

## 三角函数与弧度

sin、cos、tan 的参数是弧度不是角度,这是新人最常踩的单位坑:

```java
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 相加溢出时不会报错,只是悄悄变成错误结果,极难查。addExactmultiplyExact 等会在溢出时抛 ArithmeticException,把”悄悄错”变成”立刻报错”:

Math.addExact(Integer.MAX_VALUE, 1); // 抛 ArithmeticException
```bash

在金额、计数等关键场景,用它比裸加号安全得多。

## 一个记忆法

floor 像地板(往小取)、ceil 像天花板(往大取)、round 像四舍五入。三个单词记住,取整方法就不再混。浮点相等别用 ==,用 `Double.compare` 配极小容差,因为 NaN 和正负零用 == 会得到反直觉的结果。

## 小结

`Math` 是躺在 `java.lang` 里的数学工具箱,全是静态方法,随取随用。`abs/max/min` 求绝对值和最值,注意 `abs` 在 int 最小值上会溢出。`floor/ceil/round` 三种取整返回类型各不相同,强制转换是截断。负数取余用 `floorMod` 才不会给你负数。关键计算用 `addExact` 系列把溢出变成异常。随机数图省事用 `Math.random`,要可控换 `Random`,要安全换 `SecureRandom`。跨平台结果必须逐位一致时才换 `StrictMath`。