10.8

Java 核心基础实验文档:随机数生成、阶乘计算与浮点数比较
一、文档概述
本文档聚焦 Java 语言中三个核心基础知识点 —— 随机数生成、阶乘计算、浮点数比较,通过 “理论解析 + 实验验证” 的形式,帮助开发者理解知识点原理、掌握代码实现方式,并规避常见错误。每个知识点均包含 “知识原理”“实验设计”“代码实现”“结果分析” 四个模块,确保理论与实践深度结合。
二、随机数生成
2.1 知识原理
Java 中生成随机数的核心方式有两种:java.util.Random 类和 java.lang.Math 类的 random() 方法,二者底层均依赖 “伪随机数算法”(基于种子生成可预测的随机序列,若种子固定,随机序列唯一),但适用场景不同。
特性 java.util.Random 类 Math.random() 方法
生成范围 支持整数(int、long)、浮点数(float、double) 仅支持 [0.0, 1.0) 区间的 double 类型
种子控制 可通过构造方法指定种子(如 new Random(100)) 底层依赖 Random,种子由系统时间自动生成,无法手动指定
线程安全性 线程安全(方法加同步锁) 线程安全(底层调用 Random 的同步方法)
适用场景 需要多类型、固定种子或批量随机数的场景 简单的浮点数随机需求(如概率计算、随机抽奖基础)
2.2 实验设计
实验目标

  1. 验证 Random 类生成不同类型(int、long、double)随机数的能力;
  2. 验证 “种子固定时,Random 生成的序列唯一” 的特性;
  3. 验证 Math.random() 生成 [0.0, 1.0) 浮点数的特性,并扩展其生成指定区间的整数。
    实验步骤
  4. 使用 Random 生成 5 个 [0, 100) 的 int 随机数、3 个 long 随机数、3 个 [0.0, 1.0) 的 double 随机数;
  5. 创建两个种子为 100 的 Random 对象,对比二者生成的随机序列是否一致;
  6. 使用 Math.random() 生成 5 个 [0.0, 1.0) 的 double 数,并通过公式 (int)(Math.random() * (max - min + 1) + min) 生成 5 个 [10, 20] 的整数。
    2.3 代码实现
    java
    运行
    import java.util.Random;

public class RandomTest {
public static void main(String[] args) {
// 1. Random 类生成多类型随机数
Random random = new Random();
System.out.println("1. Random 生成的随机数:");
// 生成 [0, 100) 的 int 随机数
System.out.print("int 类型(0-99):");
for (int i = 0; i < 5; i++) {
System.out.print(random.nextInt(100) + " ");
}
// 生成 long 类型随机数
System.out.print("\nlong 类型:");
for (int i = 0; i < 3; i++) {
System.out.print(random.nextLong() + " ");
}
// 生成 [0.0, 1.0) 的 double 类型随机数
System.out.print("\ndouble 类型(0.0-1.0):");
for (int i = 0; i < 3; i++) {
System.out.print(random.nextDouble() + " ");
}

    // 2. 验证固定种子的随机性(种子一致,序列一致)
    System.out.println("\n\n2. 固定种子(100)的 Random 序列:");
    Random random1 = new Random(100);
    Random random2 = new Random(100);
    System.out.print("random1 生成的 int:");
    for (int i = 0; i < 3; i++) {
        System.out.print(random1.nextInt(100) + " ");
    }
    System.out.print("\nrandom2 生成的 int:");
    for (int i = 0; i < 3; i++) {
        System.out.print(random2.nextInt(100) + " ");
    }

    // 3. Math.random() 生成随机数
    System.out.println("\n\n3. Math.random() 生成的随机数:");
    // 生成 [0.0, 1.0) 的 double
    System.out.print("double 类型(0.0-1.0):");
    for (int i = 0; i < 5; i++) {
        System.out.print(Math.random() + " ");
    }
    // 生成 [10, 20] 的整数
    System.out.print("\nint 类型(10-20):");
    int min = 10, max = 20;
    for (int i = 0; i < 5; i++) {
        int num = (int) (Math.random() * (max - min + 1) + min);
        System.out.print(num + " ");
    }
}

}
2.4 结果分析

  1. 多类型随机数结果:
    Random.nextInt(100) 稳定生成 0-99 的整数,nextLong() 生成范围极大的 long 数值(如 8865541659129011000),nextDouble() 生成 0.0 到 1.0 之间的小数(如 0.345678),符合预期。
  2. 固定种子结果:
    random1 和 random2 生成的序列完全一致(如均为 15 50 66),证明 “伪随机数的种子决定序列”—— 若需不可预测的随机数,应避免手动指定种子(默认种子为系统当前时间戳,每次运行不同)。
  3. Math.random () 结果:
    直接调用生成 0.0-1.0 的小数,通过公式转换后可精准生成 10-20 的整数,说明该方法适合简单的随机场景,但灵活性低于 Random 类。
    三、阶乘计算
    3.1 知识原理
    阶乘是数学中的基础运算,定义为:
    • 正整数 n 的阶乘(记为 n!)= n × (n-1) × (n-2) × ... × 1
    • 特殊规定:0! = 1(空乘积的数学定义)
    Java 中计算阶乘的核心方式有两种:递归(基于函数自身调用)和循环(基于 for/while 迭代),二者各有优劣:
    特性 递归实现 循环实现
    代码复杂度 简洁,符合数学定义逻辑 略繁琐,需手动控制迭代变量
    内存占用 高(依赖调用栈,递归深度过大易栈溢出) 低(仅需局部变量存储中间结果)
    适用场景 小规模 n(如 n ≤ 1000) 大规模 n(无栈溢出风险)
    异常风险 易触发 StackOverflowError(栈溢出) 无栈溢出风险,但需注意数值溢出
    注意:Java 基础数据类型(如 int、long)存储范围有限,当 n > 20 时,long 类型会发生数值溢出(20! = 2432902008176640000,接近 long 最大值 9223372036854775807),若需计算更大阶乘,需使用 java.math.BigInteger(任意精度整数)。
    3.2 实验设计
    实验目标
  4. 用递归和循环两种方式实现阶乘计算,对比二者结果一致性;
  5. 验证 long 类型的数值溢出问题;
  6. 使用 BigInteger 实现大整数阶乘(如 n = 100),避免溢出。
    实验步骤
  7. 实现递归阶乘方法(factorialRecursive)和循环阶乘方法(factorialLoop),计算 n = 5、n = 20、n = 21 的阶乘,对比结果;
  8. 实现 BigInteger 版本的阶乘方法(factorialBigInteger),计算 n = 100 的阶乘,观察结果完整性。
    3.3 代码实现
    java
    运行
    import java.math.BigInteger;

public class FactorialTest {
// 1. 递归实现阶乘(long 类型,有溢出风险)
public static long factorialRecursive(int n) {
if (n < 0) {
throw new IllegalArgumentException("n 不能为负数!");
}
// 基线条件:0! = 1,1! = 1
return n == 0 || n == 1 ? 1 : n * factorialRecursive(n - 1);
}

// 2. 循环实现阶乘(long 类型,有溢出风险)
public static long factorialLoop(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("n 不能为负数!");
    }
    long result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// 3. BigInteger 实现阶乘(无溢出风险,支持大整数)
public static BigInteger factorialBigInteger(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("n 不能为负数!");
    }
    BigInteger result = BigInteger.ONE; // 等价于 1
    for (int i = 2; i <= n; i++) {
        result = result.multiply(BigInteger.valueOf(i)); // 大整数乘法
    }
    return result;
}

public static void main(String[] args) {
    // 测试 1:小数值 n(无溢出)
    int n1 = 5;
    System.out.println("1. n = " + n1 + " 的阶乘:");
    System.out.println("递归实现:" + factorialRecursive(n1)); // 预期 120
    System.out.println("循环实现:" + factorialLoop(n1));     // 预期 120

    // 测试 2:临界值 n = 20(long 无溢出)
    int n2 = 20;
    System.out.println("\n2. n = " + n2 + " 的阶乘:");
    System.out.println("递归实现:" + factorialRecursive(n2)); // 预期 2432902008176640000
    System.out.println("循环实现:" + factorialLoop(n2));     // 预期 2432902008176640000

    // 测试 3:n = 21(long 溢出,结果错误)
    int n3 = 21;
    System.out.println("\n3. n = " + n3 + " 的阶乘(long 溢出):");
    System.out.println("递归实现:" + factorialRecursive(n3)); // 预期错误(实际为 -4249290049419214848)
    System.out.println("循环实现:" + factorialLoop(n3));     // 预期错误(同上)

    // 测试 4:BigInteger 计算大阶乘(n = 100,无溢出)
    int n4 = 100;
    System.out.println("\n4. n = " + n4 + " 的阶乘(BigInteger):");
    System.out.println(factorialBigInteger(n4)); // 输出完整的 100! 结果
}

}
3.4 结果分析

  1. 小数值与临界值结果:
    当 n = 5 和 n = 20 时,递归和循环实现的结果完全一致(120 和 2432902008176640000),证明两种实现逻辑正确。
  2. long 溢出结果:
    当 n = 21 时,两种实现均返回负数(如 -4249290049419214848),原因是 21! = 51090942171709440000 超过 long 最大值,触发 “数值溢出”(Java 不报错,仅返回错误结果),需警惕基础类型的存储上限。
  3. BigInteger 结果:
    计算 n = 100 时,BigInteger 可输出完整的 100! 结果(共 158 位),证明其 “任意精度” 特性,适合大规模阶乘计算。
    四、浮点数比较
    4.1 知识原理
    Java 中的浮点数类型(float、double)基于 IEEE 754 标准存储,核心问题是 “部分十进制小数无法用二进制精确表示”(如 0.1 的二进制是无限循环小数),导致直接使用 == 或 != 比较时会出现逻辑错误。
    核心问题示例
    java
    运行
    double a = 0.1 + 0.2;
    double b = 0.3;
    System.out.println(a == b); // 输出 false,而非预期的 true
    原因:0.1 和 0.2 的二进制存储均有误差,相加后的结果(约 0.30000000000000004)与 0.3(约 0.29999999999999998)不相等。
    正确比较方式
  4. 绝对误差比较:判断两个浮点数的差值绝对值是否小于一个极小的阈值(如 1e-9,即 0.000000001),适合差值较小的场景。
    公式:Math.abs(a - b) < ε(ε 为阈值,通常取 1e-9 或 1e-6,根据精度需求调整)。
  5. 相对误差比较:判断差值绝对值与两个数中较大值的比值是否小于阈值,适合数值范围差异大的场景(如 1.0e6 和 1.0e6 + 0.1)。
    公式:Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) < ε。
  6. 使用 BigDecimal 类:将浮点数转换为 BigDecimal(通过字符串构造,避免二进制误差),再调用 compareTo() 方法比较,适合高精度场景(如金融计算)。
    4.2 实验设计
    实验目标
  7. 验证直接使用 == 比较浮点数的错误性;
  8. 用 “绝对误差” 和 “相对误差” 两种方式实现正确比较;
  9. 用 BigDecimal 实现高精度浮点数比较。
    实验步骤
  10. 计算 0.1 + 0.2 与 0.3 的差值,用 == 比较,观察错误结果;
  11. 定义阈值 ε = 1e-9,用绝对误差和相对误差方式比较上述两个值,验证正确性;
  12. 用 BigDecimal 构造 0.1、0.2、0.3,计算 0.1 + 0.2 后与 0.3 比较,验证正确性。
    4.3 代码实现
    java
    运行
    import java.math.BigDecimal;

public class FloatCompareTest {
// 定义比较阈值(根据精度需求调整,此处取 1e-9)
private static final double EPSILON = 1e-9;

// 1. 绝对误差比较
public static boolean compareAbsolute(double a, double b) {
    return Math.abs(a - b) < EPSILON;
}

// 2. 相对误差比较
public static boolean compareRelative(double a, double b) {
    // 处理其中一个数为 0 的情况(避免除以 0)
    if (a == 0 || b == 0) {
        return Math.abs(a - b) < EPSILON;
    }
    return Math.abs(a - b) / Math
posted @ 2025-10-08 17:19  春酲01  阅读(8)  评论(0)    收藏  举报