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 实验设计
实验目标
- 验证 Random 类生成不同类型(int、long、double)随机数的能力;
- 验证 “种子固定时,Random 生成的序列唯一” 的特性;
- 验证 Math.random() 生成 [0.0, 1.0) 浮点数的特性,并扩展其生成指定区间的整数。
实验步骤 - 使用 Random 生成 5 个 [0, 100) 的 int 随机数、3 个 long 随机数、3 个 [0.0, 1.0) 的 double 随机数;
- 创建两个种子为 100 的 Random 对象,对比二者生成的随机序列是否一致;
- 使用 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 结果分析
- 多类型随机数结果:
Random.nextInt(100) 稳定生成 0-99 的整数,nextLong() 生成范围极大的 long 数值(如 8865541659129011000),nextDouble() 生成 0.0 到 1.0 之间的小数(如 0.345678),符合预期。 - 固定种子结果:
random1 和 random2 生成的序列完全一致(如均为 15 50 66),证明 “伪随机数的种子决定序列”—— 若需不可预测的随机数,应避免手动指定种子(默认种子为系统当前时间戳,每次运行不同)。 - 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 实验设计
实验目标 - 用递归和循环两种方式实现阶乘计算,对比二者结果一致性;
- 验证 long 类型的数值溢出问题;
- 使用 BigInteger 实现大整数阶乘(如 n = 100),避免溢出。
实验步骤 - 实现递归阶乘方法(factorialRecursive)和循环阶乘方法(factorialLoop),计算 n = 5、n = 20、n = 21 的阶乘,对比结果;
- 实现 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 结果分析
- 小数值与临界值结果:
当 n = 5 和 n = 20 时,递归和循环实现的结果完全一致(120 和 2432902008176640000),证明两种实现逻辑正确。 - long 溢出结果:
当 n = 21 时,两种实现均返回负数(如 -4249290049419214848),原因是 21! = 51090942171709440000 超过 long 最大值,触发 “数值溢出”(Java 不报错,仅返回错误结果),需警惕基础类型的存储上限。 - 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)不相等。
正确比较方式 - 绝对误差比较:判断两个浮点数的差值绝对值是否小于一个极小的阈值(如 1e-9,即 0.000000001),适合差值较小的场景。
公式:Math.abs(a - b) < ε(ε 为阈值,通常取 1e-9 或 1e-6,根据精度需求调整)。 - 相对误差比较:判断差值绝对值与两个数中较大值的比值是否小于阈值,适合数值范围差异大的场景(如 1.0e6 和 1.0e6 + 0.1)。
公式:Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) < ε。 - 使用 BigDecimal 类:将浮点数转换为 BigDecimal(通过字符串构造,避免二进制误差),再调用 compareTo() 方法比较,适合高精度场景(如金融计算)。
4.2 实验设计
实验目标 - 验证直接使用 == 比较浮点数的错误性;
- 用 “绝对误差” 和 “相对误差” 两种方式实现正确比较;
- 用 BigDecimal 实现高精度浮点数比较。
实验步骤 - 计算 0.1 + 0.2 与 0.3 的差值,用 == 比较,观察错误结果;
- 定义阈值 ε = 1e-9,用绝对误差和相对误差方式比较上述两个值,验证正确性;
- 用 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
浙公网安备 33010602011771号