java juint测试哪个delta选择以避免浮点错误

时间:2018-05-26 14:08:08

标签: java junit floating-point assert delta

我正在使用jUnit来测试我的Java应用程序(我对Java完全不熟悉)。

public class MyClass {
    public static void main(String args[]) {

        double A = 50000.0;
        double B = 1.1;
         System.out.println("Result" + A * B);
    }
}

“正常”答案(从数学角度来看)是:55000

然而java返回:

Result 55000.00000000001

因此,我的测试失败,因为断言不受尊重

我的jUnit测试如下:

        Assert.assertEquals("55000", <javaResult>, 0.0);

我相信问题是由于最后一个名为delta的参数。因为我试图随机改变三角洲。以下不会导致失败(即测试按预期通过)

        Assert.assertEquals("55000", <javaResult>, 0.01);

所以我的问题是:假设我必须执行专门的乘法运算,我应该选择哪个delta? (我觉得随便选择0.01只是因为它有效......我感觉不舒服。)

1 个答案:

答案 0 :(得分:2)

在正常范围内([sup <-10> ,2 1024 )中的大小),Java double具有53位有效数,因此相邻可表示值之间的步长最多为2 52 中的一部分。每当使用舍入到最接近的数字转换为double格式时,结果(如果它在正常范围内)最多距离原始数字的半步。

因此,如果 a 是转换为doublea的小数的值,则a = a < / em>•(1 + e ),其中| e | ≤2 -53

考虑比较两个数字 xy 的数学乘积,将两个数字转换为double xy并将它们相乘产生double结果。我们可能会将确切的产品 xy 写为十进制数字,但是,在将其传递给assertEquals时,它将转换为double,因此我们实际传递 xy •(1 + e 0 )对于某些 e 0 ,如上所述。

同样, x 转换为某些x等于 x •(1 + e 1 )和 y 转换为某些y等于 y •(1 + e 2 )。然后我们将这些乘以形式( x •(1 + e 1 ))•( y •(1 + ë <子> 2 ))•(1+ ë <子> 3 )。

最后,我们将 xy •(1 + e 0 )与( x •(1+)进行比较ë <子> 1 ))•(ý•(1+ ë <子> 2 )) •(1+ ë <子> 3 )。它们有多么不同?

后者是 xy •(1 + e 1 )•(1 + e 2 )•(1 + e 3 ),因此差异为xy•((1 + e 1 )•(1+ ë <子> 2 )•(1+ ë <子> 3 ) - (1 + e 0 ))= xy •( e 1 + ë <子> 2 + ë <子> 3 + ë <子> 1 ë <子> 2 + ë <子> 2 ë <子> 3 + ë <子> 1 ë <子> 3 + ë <子> 1 电子 <子> 2 ë <子> 3 - ë <子> 0 )。当 e 0 为-2 -53 且其他错误为+2 时,我们可以很容易地看到误差项具有最大幅度-53 。然后它是4·2 -53 + 3·2 -106 + 2 -159 x y 是正还是负,此误差范围的最大幅度保持不变,因此我们可以将这个差异的特征描述为| xy < / em> |•(4•2 -53 + 3•2 -106 + 2 -159 )。

我们无法使用double完全计算这一点,原因有三个:

  • xy 可能无法准确表示。
  • 4•2 -53 + 3•2 -106 + 2 -159 无法表示。
  • 当使用double乘以这两个值时,可能会出现另一个舍入错误。

为了解决第一个问题,假设我们将所需产品的绝对值 xy 作为十进制数N。然后我们可以替换| xy |在Math.nextAfter(N, Double.POSITIVE_INFINITY)的表达式中。这会增加少量(尽可能最轻微)以补偿N转换为double时可能向下舍入的事实。 (我们也可以通过将N转换为double并将其舍入为∞而不是舍入到最接近的位置来准备double。)

为了解决第二个问题,我们可以将4•2 -53 + 3•2 -106 + 2 -159 转换为a 0x1.0000000000001p-51向∞舍入。结果是4•2 -53 + 3•2 -106 ,或2 -51 + 2 -103 。从JDK 5开始,我们可以将其写为double

第三个问题相对于结果可能引入最多2 -53 的舍入误差。但是,在将误差项转换为Assert.assertEquals("<exactResult>", <javaResult>, Math.nextAfter(N, Double.POSITIVE_INFINITY)*0x1.0000000000001p-51);时,我们向上舍入的数量超过了2(sup> -103 超过3•2 -106 +的数量2 -159 小于误差项的2 -53 ,因此,即使计算结果向下舍入2 -53 (相对而言),它仍然高于期望的数学结果。

因此,<javaResult>仅在至少满足下列条件之一时才报告错误:

  • double不是<exactResult>计算两个数字的乘积的结果,这两个数字是从十进制数字转换而来的,其精确乘积为<exactResult>
  • double不在double的正常范围内。
  • 计算的公差不在<exactResult>的正常范围内(导致其向下舍入超过预期值)。 (注意,第二个条件意味着第三个条件,因此可以省略第二个条件。)

如果nextAfter处于次正常范围内,则可以使用较小的绝对值作为容差,而不是相对值。我省略了对此的讨论。

请注意,此绑定使得如果产品符合预期,断言将不会报告错误。但是,如果产品错误,则无法保证报告错误。容差是单个操作错误界限的四倍以上(因为涉及四个操作)。因此,断言将接受除计算产品的正确结果之外的若干值。换句话说,对于与正确结果非常接近但不同的结果,可能存在漏报。

这是错误的 上限。通过更多分析可能会进一步收紧它,并且在某些情况下可能会收紧更多,例如,如果已知 xy 可以完全表示,或者的特定值x y 是已知的。

我希望{{1}}可以替换为错误因子的增加,但是我没有进行分析以断言,例如,一次ULP增加就足够了。