
在软件开发中,尤其是在涉及科学计算、金融或自定义数值类型(如自定义浮点数)时,对浮点数进行精确测试是一个常见的挑战。由于浮点数在计算机内部的表示方式,许多看似简单的算术运算都可能引入微小的精度误差。例如,0.1 + 0.2在某些情况下可能不严格等于0.3。
JUnit框架为解决这一问题提供了assertEquals(String message, Double expected, Double actual, Double delta)方法。其中,delta参数至关重要,它定义了expected和actual之间允许的最大差值。只有当|expected - actual| <= delta时,断言才会通过。正确设置delta是编写健壮浮点数测试的关键。
在对自定义浮点数类型进行随机值测试时,开发者可能尝试根据输入值动态设置delta。一个常见的误区是使用Math.min(doubles[i], doubles[j])作为delta值,如下面的代码片段所示:
assertEquals("Failed " + doubles[i] + " + " + doubles[j],
doubles[i] + doubles[j],
ownFloats[i].add(ownFloats[j]).toDouble(),
Math.min(doubles[i], doubles[j]));这种设置方式存在两个主要问题:
考虑以下错误示例:
java.lang.AssertionError: Failed -0.01393084463838419 + -0.01393084463838419 Expected :-0.02786168927676838 Actual :-0.027861595153808594
这里,expected和actual之间存在一个微小差异。如果delta被错误地设置为负值或一个不合适的正值,就会导致断言失败。
为了克服上述问题,一个更稳健的动态delta设置策略是基于操作数的绝对值和相对误差来确定。推荐的方法是使用Math.max(Math.abs(doubles[i]), Math.abs(doubles[j])) / 100.0。
// 计算预期结果
double expectedAdd = doubles[i] + doubles[j];
// 获取实际结果
double actualAdd = ownFloats[i].add(ownFloats[j]).toDouble();
// 计算动态delta
double deltaAdd = Math.max(Math.abs(doubles[i]), Math.abs(doubles[j])) / 100.0;
assertEquals("Failed " + doubles[i] + " + " + doubles[j],
expectedAdd, actualAdd, deltaAdd);
// 减法同理
double expectedSub = doubles[i] - doubles[j];
double actualSub = ownFloats[i].sub(ownFloats[j]).toDouble();
double deltaSub = Math.max(Math.abs(doubles[i]), Math.abs(doubles[j])) / 100.0; // 或者根据预期结果的magnitude来调整
assertEquals("Failed " + doubles[i] + " - " + doubles[j],
expectedSub, actualSub, deltaSub);这种delta设置方法的优势在于:
结合上述改进,原始的测试方法可以更新为:
import org.junit.jupiter.api.Test; // 假设使用JUnit 5
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.DoubleStream;
// 假设有一个OwnFloat类,实现了add和sub方法
class OwnFloat {
private double value;
public OwnFloat(double value) {
this.value = value;
}
public OwnFloat add(OwnFloat other) {
// 实际的自定义浮点数加法逻辑,可能引入精度误差
return new OwnFloat(this.value + other.value);
}
public OwnFloat sub(OwnFloat other) {
// 实际的自定义浮点数减法逻辑,可能引入精度误差
return new OwnFloat(this.value - other.value);
}
public double toDouble() {
return this.value;
}
}
public class OwnFloatTest {
@Test
public void testRandomMath() {
DoubleStream doubleStream = ThreadLocalRandom.current().doubles(100);
// 限制double的范围,使其在OwnFloat类可处理的范围内
double[] doubles = doubleStream.map(d -> {
if (ThreadLocalRandom.current().nextBoolean()) {
return d * -1d;
} else {
return d;
}
}).map(d -> d * Math.pow(2, ThreadLocalRandom.current().nextInt(-8, 9))).toArray();
OwnFloat[] ownFloats = new OwnFloat[doubles.length];
for (int i = 0; i < doubles.length; i++) {
ownFloats[i] = new OwnFloat(doubles[i]);
}
for (int i = 0; i < doubles.length; i++) {
for (int j = 0; j < doubles.length; j++) {
// 加法测试
double expectedAdd = doubles[i] + doubles[j];
double actualAdd = ownFloats[i].add(ownFloats[j]).toDouble();
// 使用动态delta,基于操作数中绝对值较大的一个
// 100.0可以根据实际精度要求调整,例如1000.0或更高
double deltaAdd = Math.max(Math.abs(doubles[i]), Math.abs(doubles[j])) / 100.0;
// 如果deltaAdd过小(例如接近0),可以设置一个最小delta值,防止除以零或delta过小
if (deltaAdd < Double.MIN_NORMAL) { // Double.MIN_NORMAL是最小正double值
deltaAdd = Double.MIN_NORMAL;
}
assertEquals("Failed " + doubles[i] + " + " + doubles[j],
expectedAdd, actualAdd, deltaAdd);
// 减法测试
double expectedSub = doubles[i] - doubles[j];
double actualSub = ownFloats[i].sub(ownFloats[j]).toDouble();
double deltaSub = Math.max(Math.abs(doubles[i]), Math.abs(doubles[j])) / 100.0;
if (deltaSub < Double.MIN_NORMAL) {
deltaSub = Double.MIN_NORMAL;
}
assertEquals("Failed " + doubles[i] + " - " + doubles[j],
expectedSub, actualSub, deltaSub);
}
}
}
}正确处理JUnit中的浮点数断言assertEquals的delta参数是确保浮点数代码质量的关键。避免使用可能产生负值的delta,并优先考虑基于操作数或预期结果的相对误差来动态计算delta。通过采纳Math.max(Math.abs(a), Math.abs(b)) / N这种策略,并结合对delta选取原则的理解,可以构建出更健壮、更适应实际浮点数运算特性的单元测试。在追求更高精度时,ULP比较也是一个值得探索的高级选项。
以上就是深入理解JUnit浮点数断言:动态Delta值的正确设置与实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号