You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit测试:如何为assertEquals()正确设置动态delta值?

自定义浮点数类JUnit测试的delta值误区与替代方案

问题描述

为完成作业实现了自定义浮点数类OwnFloat,多数输入组合下运行正常,因此编写了基于随机值的JUnit测试用例。但调用assertEquals(String message, Double expected, Double actual, Double delta)方法时,动态设置delta值遇到问题。

测试代码

@Test
public void testRandomMath() {

    DoubleStream doubleStream = ThreadLocalRandom.current().doubles(100);

    //limiting the doubles to be withing a workable range for my ownFloat class
    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++) {
            assertEquals("Failed " + doubles[i] + " + " + doubles[j],doubles[i] + doubles[j], ownFloats[i].add(ownFloats[j]).toDouble(), Math.min(doubles[i], doubles[j]));
            assertEquals("Failed " + doubles[i] + " - " + doubles[j], doubles[i] - doubles[j], ownFloats[i].sub(ownFloats[j]).toDouble(), Math.min(doubles[i], doubles[j]));
        }
    }
}

报错信息

java.lang.AssertionError: Failed -0.01393084463838419 + -0.01393084463838419 
Expected :-0.02786168927676838
Actual   :-0.027861595153808594

回答

delta的理解误区

  • delta是允许的绝对误差范围,而非输入值的极值:assertEquals的delta参数要求是一个非负数值,代表预期值与实际值之间可接受的最大绝对差值。你使用Math.min(doubles[i], doubles[j])作为delta存在两个严重问题:一是当输入为负数时,delta会变成负数,JUnit会直接判定不通过;二是输入值的大小和浮点数运算的误差没有直接关联,这个设置完全不符合误差校验的逻辑。
  • 混淆了输入值与浮点数精度的关系:自定义浮点数类的运算误差由其自身的精度设计决定(比如位数、编码方式),和输入值的大小没有直接对应关系,不能用输入值的极值来定义误差范围。

替代delta的可行方案

根据OwnFloat的设计目标,可选择以下几种delta设置方式:

  • 基于ULP(末位有效单位):这是浮点数精度的标准衡量方式,Math.ulp(expected)会返回预期值对应的最小可分辨单位。通常允许1~2个ULP的误差即可覆盖浮点数运算的正常精度损失,示例代码:
    double sumExpected = doubles[i] + doubles[j];
    double sumDelta = 2 * Math.ulp(sumExpected);
    assertEquals("Failed " + doubles[i] + " + " + doubles[j], sumExpected, ownFloats[i].add(ownFloats[j]).toDouble(), sumDelta);
    
    double subExpected = doubles[i] - doubles[j];
    double subDelta = 2 * Math.ulp(subExpected);
    assertEquals("Failed " + doubles[i] + " - " + doubles[j], subExpected, ownFloats[i].sub(ownFloats[j]).toDouble(), subDelta);
    
  • 固定精度阈值:如果OwnFloat是固定小数位数的实现(比如保留6位小数),可以直接设置固定的delta值,例如1e-6,确保实际结果与预期值的差不超过该阈值。
  • 相对误差阈值:若OwnFloat的误差是相对比例型的,可基于预期值的百分比设置delta,比如允许0.0001%的相对误差:
    double expected = doubles[i] + doubles[j];
    double delta = Math.abs(expected) * 1e-6;
    // 处理预期值为0的特殊情况,回退到极小固定值
    if (Double.isNaN(delta) || delta == 0) {
        delta = 1e-10;
    }
    assertEquals("Failed " + doubles[i] + " + " + doubles[j], expected, ownFloats[i].add(ownFloats[j]).toDouble(), delta);
    
  • 参考标准浮点数精度:如果OwnFloat是模拟Java单精度float的实现,可直接使用float对应的精度阈值,比如1e-7(单精度浮点数的有效十进制位数约为7位)。

内容的提问来源于stack exchange,提问作者Lantanar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 10:10:51