Menu

Java Math 类:pow、sqrt、abs、round、random 等方法

如何使用 Java 的 Math 类:Math.pow、Math.sqrt、Math.abs、Math.max 和 Math.min,用 round、floor、ceil 取整,用 Math.random 生成指定范围的随机数,以及常量 PI 和 E,并说明每个方法的返回类型。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

一个装满静态方法的工具箱

java.lang.Math 是一个装满现成数学函数的类:幂、根、绝对值、取整、三角函数、对数和随机数。它用起来很方便,原因有两点:

  • 它位于 java.lang,每个 Java 文件都会自动导入这个包,不需要写 import 语句。
  • 每个方法都是 static 的。你直接通过类名调用,比如 Math.sqrt(25),永远不需要创建 Math 对象。静态成员一页解释了 static 的含义。

注意有些结果带 .0,有些不带。每个 Math 方法都有固定的返回类型,弄清楚这些返回类型,这个类基本就学会了。

方法作用返回类型
Math.pow(a, b)a 的 b 次方double
Math.sqrt(x)平方根double
Math.cbrt(x)立方根double
Math.abs(x)绝对值与 x 类型相同
Math.max(a, b) / Math.min(a, b)两者中较大 / 较小的值两种类型中较宽的那个
Math.round(x)最接近的整数传入 double 时为 long,传入 float 时为 int
Math.floor(x) / Math.ceil(x)向下取整 / 向上取整double
Math.random()0.0 到 1.0 之间的随机数double
Math.PI, Math.E常量 π 和 edouble

Math.pow:求幂

Math.pow(base, exponent) 求一个数的若干次方。它接收两个 double 参数并返回 double,即使你传入的是整数:

要把结果存进 int,用 (int) 强制转换。对于较小的整数次幂,double 结果是精确的,所以转换后答案正确。结果很大时转换就会出问题:大于 Integer.MAX_VALUE(2,147,483,647)的 double 转换后正好等于 Integer.MAX_VALUE,所以 (int) Math.pow(2, 40) 得到的是 2147483647。更大的幂请转换成 long,同时记住超过 2⁵³ 后,double 无法精确表示每一个整数。类型转换一页讲解了强制转换会怎样改变数值。

Java 没有幂运算符。2 ^ 3 能通过编译,但 ^ 是按位异或,结果是 1 而不是 8。这是 Java 数学代码中最常见的初学者 bug 之一。

一千万及以上的 double 会以科学计数法打印,第一次打印 10 的幂时很多人都会吃一惊:

1.0E7 表示 1.0 × 10⁷。数值不变,只是打印形式变了。要把它打印成普通的数字,可以转换成 long,或者用 String.format("%.0f", value) 格式化。

Math.sqrt 和 Math.cbrt:求根

Math.sqrt(x) 以 double 返回 x 的平方根,Math.cbrt(x) 返回立方根。

负数的平方根不是实数,所以 Math.sqrt(-4) 返回 NaN(“not a number”,非数字),而不是抛出异常。NaN 会传染给它参与的所有运算,所以如果输入可能为负,求根之前先检查。Math.hypot(a, b) 直接计算 sqrt(a*a + b*b),常用来求两点之间的距离。

Math.abs:绝对值

Math.abs(x) 去掉符号。和 pow、sqrt 不同,它返回的类型与传入的类型相同:传入 int 返回 int,传入 double 返回 double。

它通常用在只关心大小、不关心方向的差值上,比如两个位置之间的距离。

Math.max 和 Math.min

Math.max(a, b) 返回两个值中较大的那个,Math.min(a, b) 返回较小的那个。它们只接收两个参数。比较三个或更多值时,嵌套调用:

两个参数类型不同时,较窄的那个会先被拓宽。Math.max(3, 7.5) 比较的是两个 double 值,返回 double。最后一个例子把数值限制在 0 到 100 之间:Math.min 把上限定为 100,Math.max 保证它不低于 0。

取整:round、floor、ceil

Java 有三个取整方法,它们的取整方向和返回类型都不一样:

  • Math.round 取最接近的整数。正好一半时向上取整,也就是朝正无穷方向,所以 2.5 变成 3,-2.5 变成 -2。参数为 double 时返回 long(参数为 float 时返回 int)。
  • Math.floor 总是向下取整,朝负无穷方向,返回 double。
  • Math.ceil 总是向上取整,朝正无穷方向,返回 double。
  • (int) 强制转换根本不是取整:它直接丢掉小数部分,正数因此变小,负数因此变大。

因为 Math.round(double) 返回 long,不加强制转换就赋给 int 是无法通过编译的:

int n = Math.round(2.6);          // error: incompatible types: possible lossy conversion from long to int
int m = (int) Math.round(2.6);    // OK, m is 3
long k = Math.round(2.6);         // OK

要保留若干位小数,先放大,取整,再缩小回去。除以 100.0(一个 double)而不是 100,才能保留小数:

第二行是 long 除以 int,属于整数除法,所以小数丢失了。如果只是要显示一个四舍五入后的数字,String.format("%.2f", value) 更简单,而且不会改变存储的值。

Math.random:随机数

Math.random() 返回一个大于等于 0.0 且小于 1.0 的 double。每次调用的值都不同,所以本节程序的输出在你每次点击“运行”时都会变化。

要得到某个范围内的整数,就把这个值拉伸、转换、再平移。要得到从 min 到 max(两端都包含)的整数:

int value = (int) (Math.random() * (max - min + 1)) + min;

以掷骰子为例,min 是 1,max 是 6,过程如下:

  1. Math.random() * 6 得到一个从 0.0 到 6.0(不含 6.0)的 double。
  2. (int) 丢掉小数部分,得到 0, 1, 2, 3, 4, 5 中的一个。
  3. + 1 把它平移到 1, 2, 3, 4, 5, 6。

第一行显示五次随机掷骰的结果。第二行检查一万次掷骰,实际运行中总是打印 lowest: 1, highest: 6:这个公式能取到范围的两端,而且从不越界。

Math.random() * (max - min + 1) 外面的括号很重要。强制转换只作用于紧跟在它后面的值,所以 (int) Math.random() * 6 会先转换 Math.random()。它总是 0,而 0 * 6 是 0,于是每次“掷骰”的结果都一样。

Math.random() 用在游戏和练习里没问题。更正式的场景可以用 java.util.Random 类和 ThreadLocalRandom.current().nextInt(min, max + 1)(位于 java.util.concurrent),它们直接返回整数,而且 Random 可以接收一个种子,在测试中重复同一个序列。这些都不适合用来生成密码或安全令牌,那种场合请用 java.security.SecureRandom。

Math.PI 和 Math.E

Math.PI(π)和 Math.E(自然常数 e)是 static final double 常量:

用它们代替手写的 3.14。它们的精度是 double 能达到的最高精度,而且名字本身就告诉读者这个数是什么。

整数结果与 double 结果

大多数 Math 方法接收并返回 double。传入 int 值时,Java 会自动把参数拓宽成 double,但你在调用之前写的运算仍然按 int 计算。大多数错误答案就出在这里:

items / perBox 就是 7 / 2,两个 int 相除会丢掉余数,所以在 Math.ceil 拿到它之前它就已经是 3 了。Math.ceil(3.0) 是 3.0。把其中一个操作数变成 double,用强制转换或写成 4.0 这样的字面量,除法就能保留小数部分。运算符一页更详细地讲解了整数除法。

通用规则:先想清楚最终要什么类型,需要小数时让除法或乘法在 double 中进行,最后只在结尾转换回 int 或 long 一次。

其他常用方法

同一个类中还有几个方法,调用方式都一样:

  • Math.log 是自然对数,Math.log10 是以 10 为底的对数。
  • Math.floorMod(a, b) 返回的余数与 b 同号,在一个范围内循环(比如一周中的星期几)时正需要这样。% 运算符的结果与 a 同号。
  • 三角函数方法(sin、cos、tan)接收弧度。先用 Math.toRadians 把角度转换成弧度。
  • Math.addExact、Math.multiplyExact 以及同类方法在溢出时会抛出 ArithmeticException,而不是悄悄绕回成一个负数。

常见问题

Java 中 Math.pow 怎么用?

Math.pow(base, exponent) 返回 base 的 exponent 次方,类型是 double,所以 Math.pow(2, 3) 的结果是 8.0。要把结果存进 int,需要强制转换:int n = (int) Math.pow(2, 10); 得到 1024。Java 没有 ^ 幂运算符,^ 是按位异或。

如何用 Math.random 生成指定范围内的随机数?

Math.random() 返回一个大于等于 0.0、小于 1.0 的 double。要得到从 min 到 max(两端都包含)的整数,写 (int) (Math.random() * (max - min + 1)) + min。掷骰子就是 (int) (Math.random() * 6) + 1。

Java 中使用 Math 类需要 import 吗?

不需要。Math 位于 java.lang 包中,每个 Java 文件都会自动导入这个包。它的所有方法都是 static 的,所以直接通过类名调用,比如 Math.sqrt(16),不用创建对象。

Java 中 Math.round 为什么返回 long?

Math.round(double) 返回 long,因为 double 能表示的数值远远超出 int 的范围。把结果赋给 int 需要强制转换:int n = (int) Math.round(2.6);。Math.round(float) 则返回 int。

Math.floor、Math.ceil 和 Math.round 有什么区别?

Math.floor 向下取整(2.7 变成 2.0,-2.3 变成 -3.0),Math.ceil 向上取整(2.1 变成 3.0),Math.round 取最接近的整数,正好一半时向上(2.5 变成 3)。floor 和 ceil 返回 double,round 返回 long。

Coddy 编程语言插图

和 Coddy 一起学编程

开始