Math:数学运算工具类
本章定位:
java.lang.Math提供了常用数学函数(绝对值、幂运算、三角函数、取整等),所有方法都是静态的,是科学计算、游戏开发、数据模拟的基础工具类。
小崔的年终奖计算
小崔在计算飞翔科技的年终奖分配时遇到了几个问题:
double bonus = 3.7; // 3.7 个月年终奖
// 向上取整还是向下取整?
// round() 和 ceil() 有什么区别?
白歌看着他困惑的样子,在白板上画了一张取整函数对比图。
Math 类概述
Math 类位于 java.lang 包,所有方法都是 static,构造函数私有化,不允许实例化。
常量
| 常量 | 值 | 说明 |
|---|---|---|
Math.PI | 3.141592653589793 | 圆周率 π |
Math.E | 2.718281828459045 | 自然常数 e |
常用方法速查
| 方法 | 功能 | 示例 |
|---|---|---|
abs(x) | 绝对值 | Math.abs(-5) → 5 |
max(a, b) | 最大值 | Math.max(3, 7) → 7 |
min(a, b) | 最小值 | Math.min(3, 7) → 3 |
pow(a, b) | a 的 b 次幂 | Math.pow(2, 3) → 8.0 |
sqrt(x) | 平方根 | Math.sqrt(16) → 4.0 |
cbrt(x) | 立方根 | Math.cbrt(27) → 3.0 |
round(x) | 四舍五入 | Math.round(3.5) → 4 |
ceil(x) | 向上取整 | Math.ceil(3.1) → 4.0 |
floor(x) | 向下取整 | Math.floor(3.9) → 3.0 |
random() | [0.0, 1.0) 随机数 | Math.random() |
sin(x) / cos(x) / tan(x) | 三角函数(弧度制) | Math.sin(Math.PI/2) → 1.0 |
log(x) / log10(x) | 自然对数 / 常用对数 | Math.log(Math.E) → 1.0 |
exp(x) | e 的 x 次幂 | Math.exp(1) → 2.718... |
toDegrees(rad) | 弧度转角度 | Math.toDegrees(Math.PI) → 180.0 |
toRadians(deg) | 角度转弧度 | Math.toRadians(180) → 3.1415... |
取整函数对比(重点)
这是小崔最容易混淆的地方:
| 函数 | 方向 | 正数 3.7 | 负数 -3.7 | 返回值类型 |
|---|---|---|---|---|
floor(x) | 向负无穷 | 3.0 | -4.0 | double |
ceil(x) | 向正无穷 | 4.0 | -3.0 | double |
round(x) | 四舍五入 | 4 | -4 | long(float 参数返回 int) |
记忆口诀:floor 是"地板"(往下走),ceil 是"天花板"(往上走),round 是"四舍五入"。
Math.random() 的底层
Math.random() 底层调用的是 java.util.Random 类:
// Math 类源码
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
所以 Math.random() 本质上等价于 new Random().nextDouble(),返回 [0.0, 1.0) 之间的 double。
完整示例一:年终奖计算与取整
场景
小崔需要根据员工绩效系数计算实际发放的年终奖月数。
代码
public class BonusCalculator {
public static void main(String[] args) {
// 各部门绩效系数
double devCoeff = 3.7; // 技术部:3.7 个月
double salesCoeff = 4.2; // 销售部:4.2 个月
double hrCoeff = 2.3; // 人事部:2.3 个月
System.out.println("===== 飞翔科技年终奖计算 =====\n");
// ----- 技术部的年终奖 -----
System.out.println("技术部基础系数: " + devCoeff);
System.out.println(" floor (向下取整): " + Math.floor(devCoeff) + " 个月");
System.out.println(" ceil (向上取整): " + Math.ceil(devCoeff) + " 个月");
System.out.println(" round (四舍五入): " + Math.round(devCoeff) + " 个月");
// 实际发放取 floor,不满整月不发
System.out.println(" 实际发放: " + (int) Math.floor(devCoeff) + " 个月");
System.out.println();
// ----- 销售部的年终奖(特殊规则:四舍五入) -----
System.out.println("销售部基础系数: " + salesCoeff);
long salesBonus = Math.round(salesCoeff);
System.out.println(" 四舍五入后: " + salesBonus + " 个月");
System.out.println();
// ----- 计算年终奖金额 -----
int baseSalary = 15000; // 月薪
double bonusAmount = baseSalary * devCoeff;
System.out.println("技术部年终奖总额: " + String.format("%,.2f", bonusAmount) + " 元");
// ----- 幂运算:公司利润增长预测 -----
double annualGrowth = 1.15; // 年增长率 15%
double fiveYearMultiplier = Math.pow(annualGrowth, 5);
System.out.println("5年后利润倍数: " + String.format("%.4f", fiveYearMultiplier));
// ----- 平方根:办公室面积规划 -----
double officeArea = 10000; // 办公室面积 10000 m²
double side = Math.sqrt(officeArea);
System.out.println("正方形办公室边长: " + String.format("%.2f", side) + " m");
// ----- 三角函数:公司Logo设计中的圆形计算 -----
double radius = 50;
double circumference = 2 * Math.PI * radius;
double area = Math.PI * radius * radius;
System.out.println("Logo 圆周长: " + String.format("%.2f", circumference) + " mm");
System.out.println("Logo 圆面积: " + String.format("%.2f", area) + " mm²");
}
}
运行输出
===== 飞翔科技年终奖计算 =====
技术部基础系数: 3.7
floor (向下取整): 3.0 个月
ceil (向上取整): 4.0 个月
round (四舍五入): 4 个月
实际发放: 3 个月
销售部基础系数: 4.2
四舍五入后: 4 个月
技术部年终奖总额: 55,500.00 元
5年后利润倍数: 2.0114
正方形办公室边长: 100.00 m
Logo 圆周长: 314.16 mm
Logo 圆面积: 7,853.98 mm²
完整示例二:三角函数与对数运算
场景
白歌在飞翔科技内部技术分享中,用 Math 函数演示了信号波形生成和数据压缩中的对数变换。
代码
public class MathAdvancedDemo {
public static void main(String[] args) {
System.out.println("===== 飞翔科技 数学函数高级应用 =====\n");
// ========== 1. 三角函数:模拟正弦波采样 ==========
System.out.println("--- 正弦波 10 点采样(0° ~ 360°) ---");
for (int i = 0; i <= 360; i += 36) {
double radian = Math.toRadians(i); // 角度 → 弧度
double sinValue = Math.sin(radian);
// 用星号可视化波形
int stars = (int) ((sinValue + 1) * 20); // 映射到 [0, 40]
StringBuilder bar = new StringBuilder();
for (int j = 0; j < stars; j++) {
bar.append("*");
}
System.out.printf("%3d° | sin=%.4f | %s%n", i, sinValue, bar);
}
// ========== 2. 对数运算:数据压缩比例 ==========
System.out.println("\n--- 对数变换(Web 访问量压缩) ---");
long[] pageViews = {10, 100, 1000, 10000, 100000, 1000000};
System.out.println("原始值 → log10 → 归一化");
for (long pv : pageViews) {
double logVal = Math.log10(pv);
// 归一化到 [0, 1](假设最大 10^6)
double normalized = logVal / 6.0;
System.out.printf(" %,8d → %.4f → %.4f%n", pv, logVal, normalized);
}
// ========== 3. 指数与自然对数 ==========
System.out.println("\n--- 自然指数验证 ---");
double x = 2.0;
double expX = Math.exp(x); // e^2
double logExpX = Math.log(expX); // ln(e^2) = 2
System.out.println("Math.exp(2.0) = " + String.format("%.6f", expX));
System.out.println("Math.log(exp(2)) = " + String.format("%.6f", logExpX));
System.out.println("e 的精确值验证: " + (Math.abs(logExpX - x) < 1e-10));
// ========== 4. 极值运算:多维度对比 ==========
System.out.println("\n--- 极值运算 ---");
double a = 3.14, b = -2.71, c = 1.41;
double maxOfThree = Math.max(Math.max(a, b), c);
double minOfThree = Math.min(Math.min(a, b), c);
System.out.println("max(3.14, -2.71, 1.41) = " + maxOfThree);
System.out.println("min(3.14, -2.71, 1.41) = " + minOfThree);
// ========== 5. 立方根:体积反算边长 ==========
System.out.println("\n--- 立方根:仓库体积反算 ---");
double warehouseVolume = 3375; // 立方米
double sideLength = Math.cbrt(warehouseVolume);
System.out.println("体积 " + warehouseVolume + " m³ 的正方体仓库,边长 = "
+ String.format("%.2f", sideLength) + " m");
}
}
运行输出
===== 飞翔科技 数学函数高级应用 =====
--- 正弦波 10 点采样(0° ~ 360°) ---
0° | sin=0.0000 |
36° | sin=0.5878 | *******************************
72° | sin=0.9511 | ***************************************
108° | sin=0.9511 | ***************************************
144° | sin=0.5878 | *******************************
180° | sin=0.0000 |
216° | sin=-0.5878 | ********
252° | sin=-0.9511 | *
288° | sin=-0.9511 | *
324° | sin=-0.5878 | ********
360° | sin=-0.0000 |
--- 对数变换(Web 访问量压缩) ---
原始值 → log10 → 归一化
10 → 1.0000 → 0.1667
100 → 2.0000 → 0.3333
1,000 → 3.0000 → 0.5000
10,000 → 4.0000 → 0.6667
100,000 → 5.0000 → 0.8333
1,000,000 → 6.0000 → 1.0000
--- 自然指数验证 ---
Math.exp(2.0) = 7.389056
Math.log(exp(2)) = 2.000000
e 的精确值验证: true
--- 极值运算 ---
max(3.14, -2.71, 1.41) = 3.14
min(3.14, -2.71, 1.41) = -2.71
--- 立方根:仓库体积反算 ---
体积 3375.0 m³ 的正方体仓库,边长 = 15.00 m
易错场景
反例一:Math.round() 的负数陷阱
// ❌ 可能误解:以为 round 总是向正无穷方向取整
System.out.println(Math.round(-3.5)); // 输出 -3,不是 -4!
Math.round()的规则是:加上 0.5 后向下取整。-3.5 + 0.5 = -3.0,floor 后是 -3。
// ✅ 纠正:理解 round 的实现
// round(x) = floor(x + 0.5)
// -3.5 + 0.5 = -3.0 → floor(-3.0) = -3
// 3.5 + 0.5 = 4.0 → floor(4.0) = 4
反例二:Math.abs(Integer.MIN_VALUE)
// ❌ 惊悚事实:Math.abs(Integer.MIN_VALUE) 返回负数!
System.out.println(Math.abs(Integer.MIN_VALUE)); // -2147483648,仍然是负数!
原因:Integer.MIN_VALUE = -2147483648,它的绝对值 2147483648 超出了 int 最大值 2147483647,取反后溢出又回到自身。
// ✅ 纠正:对于 int 最小值,用 long 处理
int min = Integer.MIN_VALUE;
long absMin = Math.abs((long) min); // 2147483648,正确!
反例三:Math.random() 在并发场景下的问题
// ❌ Math.random() 底层是全局单例 Random,高并发下有锁竞争
for (int i = 0; i < 1000000; i++) {
double r = Math.random(); // 底层 synchronized
}
纠正:
// ✅ 高并发场景使用 ThreadLocalRandom(JDK 7+)
import java.util.concurrent.ThreadLocalRandom;
for (int i = 0; i < 1000000; i++) {
int r = ThreadLocalRandom.current().nextInt(100); // 无锁
}
面试考点
Q1:Math.round(-1.5) 的结果是多少?为什么?
结果是 -1,不是 -2。
round()的实现是floor(x + 0.5)。-1.5 + 0.5 = -1.0,floor(-1.0) = -1.0,转为 long 就是 -1。同理Math.round(-1.6)=floor(-1.1)= -2。面试官会特别关注这个负数边缘情况。
Q2:Math.random() 和 new Random().nextDouble() 有什么区别?
没有本质区别——
Math.random()底层就是Random.nextDouble()。区别在于Math.random()使用的是全局单例Random对象,无法指定种子;而new Random(long seed)可以指定种子以获得可复现的随机序列。
Q3:Math.abs(Integer.MIN_VALUE) 返回什么?为什么?
返回
Integer.MIN_VALUE本身(-2147483648),仍然是负数。因为它的绝对值 2147483648 超出了 int 的表达范围(最大值 2147483647),取反溢出绕回自身。这是 Java 中一个著名的边缘案例,解决方法是先强转为long再取绝对值。
Q4:floor、ceil、round 三者对负数的处理有何不同?
floor向负无穷方向取整(-3.7 → -4.0);ceil向正无穷方向取整(-3.7 → -3.0);round四舍五入(-3.7 → -4)。关键记忆点:对于负数,floor和round可能结果相同(都是更小的整数),但ceil得到更大的整数。例如 -3.2:floor=-4.0,ceil=-3.0,round=-3。
Q5:为什么高并发场景不推荐 Math.random()?
Math.random()底层调用java.util.Random,其next()方法使用 CAS 操作保证线程安全,高并发下多个线程竞争同一个AtomicLong seed,性能下降明显。JDK 7 引入的ThreadLocalRandom为每个线程维护独立的种子,无竞争,性能更高。
大翔在部门例会上说:"数学是计算机科学的基石。不过别担心,你不需要记住所有的公式——只要知道 Math 类提供了什么,用的时候查 Javadoc 就够了。但取整函数和随机数的边界值,必须刻在脑子里。"