Skip to content

S03-11 核心类-数学类

[TOC]

java.lang.Math

java.lang.Math 是 Java 标准库中提供的用于执行基本数学运算的工具类。其核心设计思想体现了典型的“不可变、无状态”单例工具类模式。

架构设计特性

架构设计特性:

  • 不可实例化与不可继承Math 类被显式声明为 final class,这意味着它无法被继承。同时,其构造方法被私有化(private Math() {}),从底层语法层面彻底杜绝了外部通过 new 关键字创建实例的可能。
  • 无状态与纯静态:该类内部不保存任何实例状态,所有常量(如 Math.PIMath.E)均由 public static final 修饰,所有运算功能均通过 static 静态方法提供,调用方无需承担对象创建与销毁的内存开销。

底层执行与性能优化

底层执行与性能优化:

Math 类的许多关键方法(如 sincossqrt 等)并未采用纯 Java 字节码实现,而是被标记为 native 方法。在运行时,JVM 会将这些调用映射到本地系统的 C/C++ 库,进而直接调用底层 CPU 的硬件加速指令(如 x87SSE 指令集)。

注意事项:跨平台精度差异

由于底层实现依赖于特定操作系统的数学库或硬件浮点运算单元(FPU),相同的 Math 方法在不同的操作系统(如 Linux vs Windows)或不同的处理器架构上,可能会产生极其微小的精度差异。如果业务场景对跨平台的计算确定性(Reproducibility)有着严苛要求(例如区块链共识算法、跨平台多端同步的物理引擎),必须使用 java.lang.StrictMathStrictMath 严格遵循 FFDL (Freely Distributable Math Library) 规范,确保在所有平台上输出完全一致的位结果,但会牺牲一部分硬件加速带来的性能。

java
package lang.math.demo;

/**
 * 演示 Math 类的核心设计特性与边界溢出问题
 */
public class MathCorePrincipleDemo {
  public static void main(String[] args) {
    // 1. 验证静态常量的读取
    System.out.println("圆周率 PI = " + Math.PI); 
    System.out.println("自然对数底数 E = " + Math.E); 

    // 2. 传统浮点数计算的精度局限性
    double val1 = 0.1;
    double val2 = 0.2;
    System.out.println("0.1 + 0.2 直接相加结果: " + (val1 + val2));  // 输出 0.30000000000000004

    // 3. 传统算术运算的溢出隐患(不抛出异常,直接回绕)
    int maxInt = Integer.MAX_VALUE;
    int overflowResult = maxInt + 1;
    System.out.println("Integer.MAX_VALUE + 1 = " + overflowResult);  // 输出 -2147483648
  }
}

API:Math

基础常规运算

  • int Math.abs()(int a),返回参数的绝对值。适用于处理物理位移、数值差距等绝对距离计算。

  • double Math.ceil()(double a),返回大于或等于参数的最小双精度浮点数(向上取整)。适用于计算分页总数等向上对齐场景。

  • double Math.floor()(double a),返回小于或等于参数的最大双精度浮点数(向下取整)。适用于资产结算中的去尾法场景。

  • long Math.round()(double a),返回最接近参数的 long 型整数(四舍五入)。适用于标准的数据四舍五入展示。

    java
    package lang.math.demo;
    
    public class MathBasicOpDemo {
      public static void main(String[] args) {
        // 绝对值
        System.out.println("abs(-5.5): " + Math.abs(-5.5));
    
        // 向上取整
        System.out.println("ceil(10.1): " + Math.ceil(10.1));
        System.out.println("ceil(-10.1): " + Math.ceil(-10.1));
    
        // 向下取整
        System.out.println("floor(10.9): " + Math.floor(10.9));
        System.out.println("floor(-10.9): " + Math.floor(-10.9));
    
        // 四舍五入
        System.out.println("round(10.5): " + Math.round(10.5));
        System.out.println("round(10.4): " + Math.round(10.4));
      }
    }

指数与对数运算

  • double Math.sqrt()(double a),返回 double 值的正平方根。适用于两点间几何距离(欧氏距离)的计算。

  • double Math.cbrt()(double a),返回 double 值的立方根。适用于体积与边长的逆向换算。

  • double Math.pow()(double a, double b),返回第一个参数的第二个参数次幂的值。适用于复利计算、几何级数递增等场景。

  • double Math.log()(double a),返回 double 值的自然对数(以 e 为底)。适用于信息熵、算法时间复杂度对数阶评估。

    java
    package lang.math.demo;
    
    public class MathExpLogDemo {
      public static void main(String[] args) {
        // 平方根与立方根
        System.out.println("sqrt(16.0): " + Math.sqrt(16.0));
        System.out.println("cbrt(27.0): " + Math.cbrt(27.0));
    
        // 幂运算 (2的10次方)
        System.out.println("pow(2, 10): " + Math.pow(2, 10));
    
        // 对数运算
        System.out.println("log(Math.E): " + Math.log(Math.E));
        System.out.println("log10(100.0): " + Math.log10(100.0));
      }
    }

三角函数与角度转换

  • double Math.toRadians()(double angdeg),将角度转换为近似相等的弧度。由于 Math 类的三角函数均接受弧度制参数,此方法常作为前置转换工具。

  • double Math.toDegrees()(double angrad),将弧度转换为近似相等的角度。适用于将运算结果转换为直观的角度展示。

  • double Math.sin()(double a),返回角的三角正弦值(参数为弧度)。适用于物理引擎中的周期性振动、波动模拟。

  • double Math.cos()(double a),返回角的三角余弦值(参数为弧度)。适用于图形学中的向量投影、坐标旋转。

    java
    package lang.math.demo;
    
    public class MathTrigDemo {
      public static void main(String[] args) {
        // 将 45 度角转换为弧度
        double radians = Math.toRadians(45.0);
        System.out.println("45 度的弧度值: " + radians);
    
        // 计算正弦与余弦值
        System.out.println("sin(45°): " + Math.sin(radians));
        System.out.println("cos(45°): " + Math.cos(radians));
    
        // 弧度转回角度
        System.out.println("弧度转角度: " + Math.toDegrees(radians));
      }
    }

精确算术运算

注意事项:溢出控制

标准的 Java 算术运算符(如 +-*)在遭遇数值越界时会悄然发生二进制截断回绕(如正数变负数),不会暴露任何异常。自 Java 8 起,Math 类引入了 Exact 系列方法,在结果溢出时会强制抛出 ArithmeticException,是编写高安全要求业务(如金融账目结算)的底层核心方法。

  • int Math.addExact()(int x, int y),返回两个参数的。若结果溢出,则抛出 ArithmeticException。

  • long Math.multiplyExact()(long x, long y),返回两个参数的乘积。若结果溢出,则抛出 ArithmeticException。

  • int Math.toIntExact()(long value)将 long 值转换为 int 值。若值超出 int 范围则抛出 ArithmeticException。适用于大范围数值向小范围安全收拢。

    java
    package lang.math.demo;
    
    public class MathExactDemo {
     public static void main(String[] args) {
         int max = Integer.MAX_VALUE;
    
         try {
             // 尝试执行会导致溢出的精确加法
             System.out.println("尝试精确加法...");
             int result = Math.addExact(max, 1);
         } catch (ArithmeticException e) {
             System.err.println("捕获到算术溢出异常: " + e.getMessage());
         }
    
         long hugeValue = 5000000000L;
         try {
             // 尝试进行不安全的类型向下转换
             System.out.println("尝试安全转换 long 为 int...");
             int narrowValue = Math.toIntExact(hugeValue);
         } catch (ArithmeticException e) {
             System.err.println("捕获到类型转换溢出异常: " + e.getMessage());
         }
     }
    }

实战:游戏经济计算

本案例模拟一个2D 沙盒游戏引擎中的物理与经济计算模块。该模块包含:

  1. 玩家之间的欧氏距离计算(应用指数与平方根)。

  2. 远程武器弹道投射落点预估(应用角度转换与三角函数)。

  3. 游戏商城大额金币结算的安全防御(应用精确算术防溢出)。

java
package lang.math.demo;

/**
 * 游戏引擎数学与安全计算实战案例
*/
public class GameEngineMathSimulator {

  /**
  * 计算两点之间的几何距离(欧氏距离)
  */
  public static double calculateDistance(double x1, double y1, double x2, double y2) {
    // dx = x2 - x1, dy = y2 - y1
    double dx = x2 - x1;
    double dy = y2 - y1;
    // distance = sqrt(dx^2 + dy^2)
    return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
  }

  /**
  * 根据发射速度和仰角,计算理想状态下抛体运动的水平射程
  * 公式: R = (v^2 * sin(2 * theta)) / g
  */
  public static double calculateProjectileRange(double velocity, double angleInDegrees) {
    final double G = 9.80665; // 标准重力加速度
    // 将发射角度转换为弧度制
    double thetaInRadians = Math.toRadians(angleInDegrees);
    // 计算水平射程
    return (Math.pow(velocity, 2) * Math.sin(2 * thetaInRadians)) / G;
  }

  /**
  * 安全地计算玩家购买多件高价值装备时的总价,防止黑客利用整型溢出刷道具
  */
  public static int calculateTotalCostSafely(int unitPrice, int quantity) {
    try {
      // 使用精确乘法,一旦溢出立马阻断
      return Math.multiplyExact(unitPrice, quantity);
    } catch (ArithmeticException e) {
      // 记录日志并抛出业务层异常
      throw new IllegalArgumentException("交易金额超出系统单次处理上限,交易已拦截。原因: " + e.getMessage());
    }
  }

  public static void main(String[] args) {
    System.out.println("=== 1. 空间几何距离计算 ===");
    double playerX = 12.0, playerY = 45.5;
    double enemyX = 115.3, enemyY = 89.2;
    double distance = calculateDistance(playerX, playerY, enemyX, enemyY);
    System.out.printf("玩家与敌人的实时距离: %.2f 米\n\n", distance);

    System.out.println("=== 2. 弹道轨迹落点预测 ===");
    double launchVelocity = 50.0; // 50 m/s
    double launchAngle = 30.0;    // 30度仰角
    double range = calculateProjectileRange(launchVelocity, launchAngle);
    System.out.printf("炮弹预估落点距离: %.2f 米\n\n", range);

    System.out.println("=== 3. 游戏商城高额结算防刷校验 ===");
    int luxurySwordPrice = 1500000000; // 15亿金币
    int purchaseCount = 2;             // 购买2把

    System.out.printf("商品单价: %d, 购买数量: %d\n", luxurySwordPrice, purchaseCount);
    try {
      int totalCost = calculateTotalCostSafely(luxurySwordPrice, purchaseCount);
      System.out.println("应付总额: " + totalCost);
    } catch (IllegalArgumentException e) {
      System.out.println("[安全警报] " + e.getMessage());
    }
  }
}

java.math.BigDecimal

核心原理

在计算机中,标准的浮点数(如 floatdouble)采用二进制的 IEEE 754 标准表示。由于许多十进制小数(例如 0.10.2)在二进制下是无限循环小数,因此传统的浮点运算会不可避免地产生舍入误差与精度丢失。

java.math.BigDecimal 通过内部维护一个任意精度的整数一个表示小数位数的缩放因子,实现了对十进制数字的精确表述。其核心数学计算公式为:

image-20260525171001676

其中:

  • unscaledValue:未缩放的整数值,内部由 java.math.BigInteger 存储,用于保障数值的绝对精度。
  • scale:缩放因子(刻度),是一个 int 类型的整数,代表小数点右侧的位数(如果为负数,则表示该数值乘上 10 的正数次幂)。
java
public class BigDecimal extends Number implements Comparable<BigDecimal> {
  // 1. 符号位:1 表示正数,-1 表示负数,0 表示数值为 0
  private final int signum;

  // 2. 未缩放的绝对值(核心数值):用 int 数组存储超大整数
  // 例如:123456789 → [123456789];超大数会拆分为多个 int 存储
  private final int[] intCompact;

  // 3. 紧凑值:当数值较小时,直接用 long 存储,替代 int 数组,提升性能
  private final long longCompact;

  // 4. 缩放因子(小数点位数):最终值 =  unscaledValue × 10^(-scale)
  // 例如:123.45 → unscaled=12345,scale=2 → 12345 × 10^-2 = 123.45
  private final int scale;

  // 5. 精度:数值的有效数字位数(例如 123.45 → 精度 5)
  private transient int precision;
}

下面的代码直观展示了传统浮点数精度丢失现象以及 BigDecimal 的精准计算对比:

java
import java.math.BigDecimal;

public class CorePrincipleDemo {
  public static void main(String[] args) {
    // 1. 传统浮点数的精度丢失问题
    double d1 = 0.1;
    double d2 = 0.2;
    System.out.println("double 计算 0.1 + 0.2 = " + (d1 + d2)); // 输出: 0.30000000000000004

    // 2. BigDecimal 的精准计算
    BigDecimal b1 = new BigDecimal("0.1");
    BigDecimal b2 = new BigDecimal("0.2");
    BigDecimal result = b1.add(b2);
    System.out.println("BigDecimal 计算 0.1 + 0.2 = " + result); // 输出: 0.3
  }
}

注意事项

在使用 BigDecimal 时,必须严格遵循其设计规范,否则极易引入严重的逻辑漏洞。

构造函数陷阱:

直接使用 new BigDecimal(double) 会将二进制浮点数的非精确值直接带入对象中。严禁在需要精确计算的场景下使用此构造器。应当优先使用 new BigDecimal(String) 或者 BigDecimal.valueOf(double)

等值比较陷阱:

BigDecimalequals() 方法不仅比较数值大小,还会比较两者的精度(scale)。例如,1.01.00 使用 equals() 比较会返回 false。若只需进行纯数学数值的等值判定,必须使用 compareTo()

不可变性陷阱:

BigDecimalString 类似,属于不可变对象(Immutable)。所有的算术运算(如 addsubtract 等)都不会修改原始对象的值,而是返回一个新的 BigDecimal 对象。

除法无限循环异常(ArithmeticException):

在进行除法运算(divide)时,若运算结果为无限循环小数(如 1÷31 \div 3)且未显式指定舍入模式(RoundingMode),程序将直接抛出 ArithmeticException 异常。

下面的示例代码演示了上述四种常见陷阱

java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class PitfallDemo {
  public static void main(String[] args) {
    // 陷阱 1:使用 double 构造函数导致精度失效
    BigDecimal badBound = new BigDecimal(0.1); 
    System.out.println("double 构造器引入的不精确值: " + badBound); // 输出: 0.1000000000000000055511151231257827021181583404541015625

    // 陷阱 2:equals() 与 compareTo() 的差异
    BigDecimal num1 = new BigDecimal("1.0");
    BigDecimal num2 = new BigDecimal("1.00");
    System.out.println("equals() 比较结果: " + num1.equals(num2));       // false
    System.out.println("compareTo() 比较结果: " + (num1.compareTo(num2) == 0)); // true

    // 陷阱 3:误以为对象可变
    BigDecimal base = new BigDecimal("10.0");
    base.add(new BigDecimal("5.0")); // 未接收返回值
    System.out.println("未赋值接收时的原始值: " + base); // 仍为 10.0

    // 陷阱 4:除不尽且未指定舍入模式引发异常
    try {
      BigDecimal.ONE.divide(new BigDecimal("3"));
    } catch (ArithmeticException e) {
      System.out.println("捕获除不尽异常: " + e.getMessage()); // Non-terminating decimal expansion
    }
  }
}

API:BigDecimal

构造与实例创建组

  • BigDecimal BigDecimal()(String val),将十进制数字的字符串表示形式转换为 BigDecimal 对象。这是最推荐的创建实例方式。

  • BigDecimal BigDecimal.valueOf()(double val),内部通过 Double.toString(double) 转换,安全地将双精度浮点数转化为 BigDecimal 对象。

    java
    import java.math.BigDecimal;
    
    public class ConstructionApiDemo {
      public static void main(String[] args) {
        // 使用字符串构造器创建
        BigDecimal fromString = new BigDecimal("123.456");
    
        // 使用静态工厂方法创建
        BigDecimal fromDouble = BigDecimal.valueOf(123.456);
    
        System.out.println("字符串构造: " + fromString);
        System.out.println("静态工厂构造: " + fromDouble);
      }
    }

算术运算组

  • BigDecimal add()(BigDecimal augend),执行加法运算,返回存储两数之和的新对象。

  • BigDecimal subtract()(BigDecimal subtrahend),执行减法运算,返回存储两数之差的新对象。

  • BigDecimal multiply()(BigDecimal multiplicand),执行乘法运算,返回存储两数之积的新对象。

  • BigDecimal divide()(BigDecimal divisor, int scale, RoundingMode roundingMode),执行除法运算。显式指定商的保留小数位数(scale)与截断舍入模式(roundingMode),有效防止无限循环异常。

    java
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    public class ArithmeticApiDemo {
      public static void main(String[] args) {
        BigDecimal x = new BigDecimal("10.0");
        BigDecimal y = new BigDecimal("3.0");
    
        BigDecimal sum = x.add(y);
        BigDecimal difference = x.subtract(y);
        BigDecimal product = x.multiply(y);
        // 执行除法并保留 4 位小数,采用四舍五入模式 (HALF_UP)
        BigDecimal quotient = x.divide(y, 4, RoundingMode.HALF_UP);
    
        System.out.println("加法结果: " + sum);        // 13.0
        System.out.println("减法结果: " + difference); // 7.0
        System.out.println("乘法结果: " + product);    // 30.00
        System.out.println("除法结果: " + quotient);   // 3.3333
      }
    }

比较与缩放操作组

  • int compareTo()(BigDecimal val),比较两个对象在数学上的大小。若当前对象小于、等于、大于目标对象,则分别返回 -101

  • boolean equals()(Object x)严格等值性校验。只有当目标对象也是 BigDecimal,且两者的数值和 scale 刻度均完全一致时才返回 true

  • BigDecimal setScale()(int newScale, RoundingMode roundingMode)重置该对象的精度刻度。若新刻度导致需要舍弃原有部分小数,则根据指定的舍入模式进行截断。

    java
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    public class ScaleAndCompareApiDemo {
      public static void main(String[] args) {
        BigDecimal v1 = new BigDecimal("2.345");
    
        // 1. 缩放与截断操作:保留 2 位小数,执行常规四舍五入
        BigDecimal roundedValue = v1.setScale(2, RoundingMode.HALF_UP);
        System.out.println("四舍五入保留两位小数: " + roundedValue); // 2.35
    
        // 2. 比较操作
        BigDecimal a = new BigDecimal("5.0");
        BigDecimal b = new BigDecimal("5.00");
    
        int compResult = a.compareTo(b);
        System.out.println("a.compareTo(b) 结果: " + compResult); // 0 (表示数学相等)
        System.out.println("a.equals(b) 结果: " + a.equals(b));     // false (精度刻度不同)
      }
    }

实战:电商订单结算

本小节提供一个适合新手学习的高质量、可直接运行的电商订单结算完整实战案例。该案例模拟了商品总价计算优惠券折扣扣减根据税率计算税费并最终执行综合舍入的完整业务流程。

java
import java.math.BigDecimal;
import java.math.RoundingMode;

/**
* 电商订单高精度结算实战演示案例
*/
public class OrderSettlementSystem {

 public static void main(String[] args) {
     // 1. 定义商品的单价与购买数量
     BigDecimal itemPrice = new BigDecimal("199.99");
     BigDecimal quantity = new BigDecimal("3"); // 购买了 3 件

     // 2. 计算商品原始总价
     BigDecimal rawTotal = itemPrice.multiply(quantity);
     System.out.println("1. 商品原始总价: ¥" + rawTotal); // 599.97

     // 3. 模拟扣减优惠券面额(满减券减免 50 元)
     BigDecimal couponDiscount = new BigDecimal("50.00");
     BigDecimal discountedTotal = rawTotal.subtract(couponDiscount);
     System.out.println("2. 扣减优惠券后金额: ¥" + discountedTotal); // 549.97

     // 4. 应用店铺折扣(例如打 9.5 折)
     BigDecimal shopRate = new BigDecimal("0.95");
     BigDecimal postDiscountTotal = discountedTotal.multiply(shopRate);
     System.out.println("3. 打折后未截断总价: ¥" + postDiscountTotal); // 522.4715

     // 5. 计算增值税额(税率为 6%)
     BigDecimal taxRate = new BigDecimal("0.06");
     BigDecimal taxAmount = postDiscountTotal.multiply(taxRate);
     System.out.println("4. 未截断税额: ¥" + taxAmount); // 31.34829

     // 6. 最终实付金额 = 打折后金额 + 税额
     BigDecimal finalOrderAmount = postDiscountTotal.add(taxAmount);
     System.out.println("5. 结算前最终未截断总价: ¥" + finalOrderAmount); // 553.81979

     // 7. 财务最终截断:保留 2 位小数,按财务惯例执行四舍五入 (HALF_UP)
     BigDecimal finalPayable = finalOrderAmount.setScale(2, RoundingMode.HALF_UP);
     System.out.println("========================================");
     System.out.println("【最终账单支付凭证】");
     System.out.println("最终客户应付实付金额: ¥" + finalPayable); // 553.82
     System.out.println("========================================");
 }
}

java.math.BigInteger

核心原理

java.math.BigInteger 是 Java 提供的用于处理任意精度整数的不可变类。当整数数值超出了基本数据类型 long 的表示范围(263-2^{63}26312^{63}-1)时,必须使用 BigInteger 确保计算不会溢出。

底层核心设计

其底层核心设计采用符号-绝对值(Sign-Magnitude)表示法:

  • 符号位 (signum):一个 int 类型的变量,-1 表示负数,0 表示零,1 表示正数。
  • 绝对值数组 (mag):一个 int[] 类型的数组,采用大端序(Big-Endian)存储。数组的每个元素代表一个 32 位的无符号整数,整个大整数被拆分为多个 32 位的基数拼装而成。
java
public class BigInteger extends Number implements Comparable<BigInteger>, Serializable {
  // 1. 符号位:1=正数,-1=负数,0=数值为0
  final int signum;

  // 2. 核心存储:用 int 数组表示大整数(mag = magnitude,幅度/绝对值)
  // 大整数按 32 位分段,存在 int[] 中,**大端存储**(高位在前,低位在后)
  final int[] mag;

  // 3. 缓存哈希码(避免重复计算)
  private transient int hashCode;

  // 4. 标识:是否为素数(用于素数判断缓存)
  private transient boolean prime = false;
  private transient boolean primeCertainty = false;
}

以下是验证 BigInteger 超越基本类型承载能力的核心代码示例:

java
import java.math.BigInteger;

public class BigIntegerCoreDemo {
  public static void main(String[] args) {
    // 1. 获取 long 类型的最大值
    long maxLong = Long.MAX_VALUE;
    System.out.println("Long 最大值: " + maxLong);

    // 2. 将其转换为字符串并加 1,构造一个超越 long 范围的大数
    String beyondLongStr = "9223372036854775808"; // Long.MAX_VALUE + 1
    BigInteger bigInt = new BigInteger(beyondLongStr);

    System.out.println("BigInteger 存储的内容: " + bigInt);
    System.out.println("BigInteger 位长度: " + bigInt.bitLength());
  }
}

注意事项:

  1. 不可变性(Immutability)BigInteger 对象与 String 类似,一旦创建,其内部值不可改变。所有算术运算(如 addsubtract)都不会修改原对象,而是返回一个全新的 BigInteger 对象。在循环体中执行连续运算时,需特别注意必须对返回值进行重新赋值。

  2. 性能开销:基本数据类型的算术运算是由 CPU 寄存器和硬件指令直接完成的,耗时极短。而 BigInteger 的所有运算都是通过软件层面的数组模拟和算法迭代实现的,伴随着大量的对象创建与内存分配,其性能和内存开销远大于基本数据类型。

  3. 比较行为:由于其为引用类型,禁止使用 == 运算符进行数值比较。判断数值是否相等应优先使用 compareTo() 方法。

API:BigInteger

构造与静态工厂

  • BigInteger BigInteger()(String val),将大整数的十进制字符串表示形式转换为 BigInteger 实例。

  • BigInteger BigInteger()(String val, int radix),将指定进制的字符串表示形式转换为 BigInteger 实例。

  • BigInteger BigInteger.valueOf()(long val),返回其值等于指定 long 的 BigInteger。该方法在内部对 [16,16][-16, 16] 范围内的常用小整数进行了缓存,能够避免重复创建对象。

    java
    import java.math.BigInteger;
    
    public class BigIntegerInitDemo {
      public static void main(String[] args) {
        // 十进制字符串初始化
        BigInteger fromDecStr = new BigInteger("12345678901234567890");
    
        // 十六进制字符串初始化
        BigInteger fromHexStr = new BigInteger("7fff", 16);
    
        // 静态工厂方法初始化(触发缓存机制)
        BigInteger fromLong1 = BigInteger.valueOf(10);
        BigInteger fromLong2 = BigInteger.valueOf(10);
    
        System.out.println("十进制实例化: " + fromDecStr);
        System.out.println("十六进制实例化: " + fromHexStr);
        System.out.println("静态工厂缓存复用验证: " + (fromLong1 == fromLong2)); // 输出 true
      }
    }

基础算术运算

  • BigInteger add()(BigInteger val),返回其值为 (this+val)(this + val) 的 BigInteger。

  • BigInteger subtract()(BigInteger val),返回其值为 (thisval)(this - val) 的 BigInteger。

  • BigInteger multiply()(BigInteger val),返回其值为 (this×val)(this \times val) 的 BigInteger。

  • BigInteger divide()(BigInteger val),返回其值为 (this÷val)(this \div val) 的 BigInteger,执行整数除法,舍弃小数部分

  • BigInteger[] divideAndRemainder()(BigInteger val),返回一个包含两个 BigInteger 的数组,第一个元素为 (this÷val)(this \div val),第二个元素为余数 (this(modval))(this \pmod{val})

    java
    import java.math.BigInteger;
    
    public class BigIntegerArithmeticDemo {
      public static void main(String[] args) {
        BigInteger num1 = new BigInteger("50000000000000000000");
        BigInteger num2 = new BigInteger("20000000000000000000");
    
        // 加法运算(注意:必须接收返回值)
        BigInteger sum = num1.add(num2);
    
        // 减法运算
        BigInteger difference = num1.subtract(num2);
    
        // 乘法运算
        BigInteger product = num1.multiply(num2);
    
        // 除法运算
        BigInteger quotient = num1.divide(num2);
    
        // 同时获取商和余数
        BigInteger[] resultPair = num1.divideAndRemainder(num2);
    
        System.out.println("加法结果: " + sum);
        System.out.println("减法结果: " + difference);
        System.out.println("乘法结果: " + product);
        System.out.println("除法结果: " + quotient);
        System.out.println("商: " + resultPair[0] + ", 余数: " + resultPair[1]);
      }
    }

实战:大数阶乘计算器

在组合数学中,阶乘(Factorial)的增长速度极快。当 n>20n > 20 时,其结果就会超出 long 的最大承载极限。以下是一个基于 BigInteger 实现的通用高性能阶乘计算器,展示了如何在循环体中正确处理大数的不可变性

java
import java.math.BigInteger;

public class FactorialCalculator {

  /**
     * 计算指定非负整数的阶乘 n!
     * @param n 需要计算阶乘的边界值
     * @return n! 的 BigInteger 结果
     */
  public static BigInteger calculateFactorial(int n) {
    if (n < 0) {
      throw new IllegalArgumentException("必须传入非负整数");
    }

    // 初始化结果为 1
    BigInteger result = BigInteger.ONE;

    // 迭代进行大数乘法
    for (int i = 2; i <= n; i++) {
      // 将当前的循环因子转换为 BigInteger,并与结果相乘后重新赋值
      result = result.multiply(BigInteger.valueOf(i));
    }

    return result;
  }

  public static void main(String[] args) {
    int target = 100; // 计算 100 的阶乘

    long startTime = System.nanoTime();
    BigInteger factorialResult = calculateFactorial(target);
    long endTime = System.nanoTime();

    System.out.println(target + "! 的计算结果长度(位数): " + factorialResult.toString().length());
    System.out.println(target + "! 的完整数值结果如下:\n" + factorialResult);
    System.out.println("计算耗时: " + (endTime - startTime) / 1_000_000.0 + " 毫秒");
  }
}

java.util.Random

核心原理

java.util.Random 是 Java 平台提供的经典伪随机数生成器。其底层算法基于线性同余法(Linear Congruential Generator, LCG)

该算法的核心递推公式为:

image-20260525172325850

其中:

  • XX 为伪随机数序列。
  • XnX_n 是当前状态(种子)。
  • aaccmm 是算法的常数参数。

java.util.Random 的具体实现中,状态采用一个 48 位的 AtomicLong 存储。每次调用随机数生成方法时,系统会通过线性同余计算更新该状态,并截取部分高位比特作为随机数输出。由于其输出完全取决于初始种子(Seed),只要初始种子相同,其生成的随机数序列就会完全一致。因此,它被称为“伪随机”。

java
import java.util.Random;

public class RandomSeedPrincipleDemo {
 public static void main(String[] args) {
     long fixedSeed = 123456789L;

     // 使用相同的种子初始化两个 Random 实例
     Random random1 = new Random(fixedSeed);
     Random random2 = new Random(fixedSeed);

     System.out.println("验证伪随机数的确定性(种子相同,序列必然相同):");
     for (int i = 0; i < 5; i++) {
         int val1 = random1.nextInt(100);
         int val2 = random2.nextInt(100);
         System.out.printf("轮次 %d: 实例1 = %d, 实例2 = %d (完全一致: %b)%n",
                 i, val1, val2, (val1 == val2));
     }
 }
}

线程安全与性能分析

java.util.Random线程安全的。为了在多线程环境下保证内部状态(种子)更新的原子性,其底层使用了原子变量 AtomicLong 配合 CAS(Compare-And-Swap)自旋锁机制

当多个线程同时调用同一个 Random 实例时,它们会竞争更新同一个内部状态。如果竞争失败,线程就会进入自旋重试状态。在高并发场景下,这种 CAS 竞争会导致严重的性能衰退和 CPU 资源损耗

java
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class RandomThreadSafetyDemo {
  public static void main(String[] args) throws InterruptedException {
    final Random sharedRandom = new Random();
    int threadCount = 4;
    final int iterations = 100000;
    CountDownLatch latch = new CountDownLatch(threadCount);

    long startTime = System.nanoTime();

    for (int i = 0; i < threadCount; i++) {
      new Thread(() -> {
        for (int j = 0; j < iterations; j++) {
          // 多线程高并发访问同一个 Random 实例,内部触发 CAS 自旋
          sharedRandom.nextInt();
        }
        latch.countDown();
      }).start();
    }

    latch.await();
    long endTime = System.nanoTime();
    System.out.printf("多线程并发访问单例 Random 耗时: %.2f 毫秒%n",
                      (endTime - startTime) / 1_000_000.0);
  }
}

API:Random

构造方法与种子管理

  • Random Random()(),创建一个新的随机数生成器。其内部使用当前系统时间的纳秒数与一个内部原子计数器进行异或运算作为初始种子,确保同一时刻创建的多个实例种子不同。

  • Random Random()(long seed),使用指定的全局种子创建一个随机数生成器。常用于需要结果可复现的场景。

  • synchronized void setSeed()(long seed)重新设置随机数生成器的种子。这会清空当前状态,使其后续生成的伪随机数序列从新种子开始计算。

    java
    import java.util.Random;
    
    public class RandomConstructorDemo {
      public static void main(String[] args) {
        // 1. 无参构造器:采用系统级动态状态生成种子
        Random randDefault = new Random();
        System.out.println("默认构造器生成的随机数: " + randDefault.nextInt(100));
    
        // 2. 有参构造器:显式传入固定种子
        Random randFixed = new Random(999L);
        System.out.println("固定种子生成的首个随机数: " + randFixed.nextInt(100));
    
        // 3. 重置种子
        randFixed.setSeed(999L);
        System.out.println("重置种子后再次生成的首个随机数(与上一次相同): " + randFixed.nextInt(100));
      }
    }

基础伪随机数生成

  • boolean nextBoolean()(),返回下一个伪随机的 boolean 值,生成 truefalse 的概率理论上各占 50%。

  • void nextBytes()(byte[] bytes),生成随机字节并将其存入用户提供的 byte 数组中。生成的随机数占满整个数组。

  • double nextDouble()(),返回下一个伪随机的 double 值,范围在 [0.0, 1.0) 之间(包含 0.0,不包含 1.0)。

  • float nextFloat()(),返回下一个伪随机的 float 值,范围在 [0.0f, 1.0f) 之间。

  • synchronized double nextGaussian()(),返回下一个伪随机的、符合标准正态分布(平均值为 0.0,标准差为 1.0)的 double 值。

  • int nextInt()(),返回下一个伪随机的 int 值,均匀分布在整个 int 的取值范围(231-2^{31}23112^{31}-1)内。

  • int nextInt()(int bound),返回一个在 [0, bound) 范围内的伪随机 int 值。参数 bound 必须大于 0。

  • long nextLong()(),返回下一个伪随机的 long 值,均匀分布在整个 long 的取值范围内。

    java
    import java.util.Arrays;
    import java.util.Random;
    
    public class RandomPrimitiveDemo {
      public static void main(String[] args) {
        Random rand = new Random();
    
        // 生成各种基本类型的伪随机数
        boolean boolVal = rand.nextBoolean();
        double doubleVal = rand.nextDouble(); // [0.0, 1.0)
        float floatVal = rand.nextFloat();   // [0.0f, 1.0f)
        int intVal = rand.nextInt();         // 整个int范围
        int boundedInt = rand.nextInt(50);   // [0, 50)
        long longVal = rand.nextLong();      // 整个long范围
        double gaussianVal = rand.nextGaussian(); // 正态分布
    
        System.out.printf("boolean: %b%n", boolVal);
        System.out.printf("double: %f%n", doubleVal);
        System.out.printf("float: %f%n", floatVal);
        System.out.printf("int (无界): %d%n", intVal);
        System.out.printf("int (0~49): %d%n", boundedInt);
        System.out.printf("long: %d%n", longVal);
        System.out.printf("高斯分布: %f%n", gaussianVal);
    
        // 填充字节数组
        byte[] byteArray = new byte[5];
        rand.nextBytes(byteArray);
        System.out.println("随机字节数组: " + Arrays.toString(byteArray));
      }
    }

流式伪随机数生成(JDK8+)

  • IntStream ints()(),返回一个无限长度的伪随机 int 值流。

  • IntStream ints()(long streamSize, int randomNumberOrigin, int randomNumberBound),返回一个指定大小、指定范围 [randomNumberOrigin, randomNumberBound)IntStream

  • LongStream longs()() / (long streamSize, long randomNumberOrigin, long randomNumberBound),返回一个无限长度/指定大小和范围的 LongStream

  • DoubleStream doubles()() / (long streamSize, double randomNumberOrigin, double randomNumberBound),返回一个无限长度/指定大小和范围的 DoubleStream

    java
    import java.util.Random;
    import java.util.stream.IntStream;
    
    public class RandomStreamDemo {
      public static void main(String[] args) {
        Random rand = new Random();
    
        System.out.println("利用 Stream 生成 5 个 1 到 10 之间的随机整数:");
        // 限制流大小为 5,指定范围为 [1, 11)
        IntStream boundedStream = rand.ints(5, 1, 11);
        boundedStream.forEach(num -> System.out.print(num + " "));
        System.out.println();
    
        System.out.println("获取无限流并截取前 3 个随机双精度浮点数:");
        rand.doubles()
          .limit(3)
          .forEach(System.out::println);
      }
    }

注意事项

  1. 严禁用于安全敏感场景java.util.Random 的算法是完全可预测的。已知当前随机数序列的一段输出后,可以通过数学方法逆向推算出内部状态种子。因此,生成密码、加密密钥、敏感业务 Token 或安全验证码时,必须使用 java.security.SecureRandom

  2. 高并发下的性能替代方案:在多线程高并发环境下,直接使用单例 java.util.Random 会引发高强度的 CAS 自旋锁竞争。推荐在多线程环境下改用 java.util.concurrent.ThreadLocalRandom,或者在非并发场景下让每个线程独享一个 Random 实例。

  3. 取模区间缺陷(避免使用 Math.abs(rand.nextInt()) % bound:过去开发者常用 Math.abs(rand.nextInt()) % bound 来限定区间。这存在两个严重隐患:一是当产生 Integer.MIN_VALUE 时,Math.abs() 依然会返回负数,导致取模后得到负索引;二是取模方式会导致低位概率分布不均(模偏斜问题)。应统一改用标准的 rand.nextInt(int bound) 方法。

实战:掷骰子游戏

本案例模拟一个经典的游戏场景:系统生成一个随机六位验证码(用于登录验证,仅作为业务演示),并模拟 3 名玩家通过抛掷两枚骰子来决出获胜者。代码可直接独立编译运行。

java
import java.util.Random;

public class GameRandomSimulator {

  private static final Random RANDOM = new Random();

  /**
     * 模拟生成一个 6 位数字验证码(仅限业务流程模拟,非安全加密场景)
     */
  public static String generateVerificationCode() {
    StringBuilder code = new StringBuilder();
    for (int i = 0; i < 6; i++) {
      // 生成 [0, 10) 之间的整数,即 0-9 的字符
      int digit = RANDOM.nextInt(10);
      code.append(digit);
    }
    return code.toString();
  }

  /**
     * 模拟抛掷两枚骰子游戏
     */
  public static void runDiceGame() {
    String[] players = {"玩家 A", "玩家 B", "玩家 C"};
    int highestScore = 0;
    String winner = "";

    System.out.println("--- 骰子对决开始 ---");
    for (String player : players) {
      // 骰子 A:[1, 6]
      int die1 = RANDOM.nextInt(6) + 1;
      // 骰子 B:[1, 6]
      int die2 = RANDOM.nextInt(6) + 1;
      int total = die1 + die2;

      System.out.printf("%s 掷出了: %d 点 + %d 点 = 总计 %d 点%n", player, die1, die2, total);

      if (total > highestScore) {
        highestScore = total;
        winner = player;
      }
    }
    System.out.printf("🎉 本局获胜者是: %s,最高得分: %d 点!%n", winner, highestScore);
  }

  public static void main(String[] args) {
    System.out.println("====== 场景 1:生成业务流水验证码 ======");
    String vCode = generateVerificationCode();
    System.out.println("生成的模拟验证码为: " + vCode);
    System.out.println();

    System.out.println("====== 场景 2:模拟游戏随机事件 ======");
    runDiceGame();
  }
}