比较两个 32 位浮点值的增量值可以有多低?

How low can a delta value be to compare two 32-bit floating point values?

提问人:aybe 提问时间:8/14/2023 最后编辑:aybe 更新时间:8/15/2023 访问量:71

问:

我有一套 240 个 FIR 滤波器卷积的单元测试,具有不同的抽头数量。

预期用途:

它用于在 Unity 中编写音频过滤器,最初的朴素卷积是在托管代码中完成的,但对于实时音频处理来说太慢了。

单元测试确实测试了不同矢量化 FIR 卷积算法的精确性,其中一种算法使用 Unity Burst 进行拾取和实现,并将其转换为可感知 SIMD 的原生代码。

为了能够决定哪种卷积算法性能更好,我写了几篇文章:

  • 朴素卷积
    • 用作比较的参考
  • 矢量化卷积
    • 内环、外环、内环和外环
  • 上述的半频段版本
    • 1 次点击,2 次迭代所有点击
    • 1 抽头进 2 次迭代一半的抽头(利用滤波器对称性)

实际情况:

所有测试都通过了,当我打磨时,我注意到比较的增量是 .1E-04f

我把它降低到,这有效,尝试过,但通过率< 50%。1E-05f1E-06f

以下是输入信号的结果。0, 1, 2, 3 ... 28, 29, 30, 31

1E-05f示例:

   index         expected           actual       difference    match
   0   -2.7051452E-34   -2.7051452E-34    0.000000E+000     True
   1    0.00011297482    0.00011297482    0.000000E+000     True
   2    0.00022594964    0.00022594964    0.000000E+000     True
   3    -0.0010179491    -0.0010179491    0.000000E+000     True
   4    -0.0022618477    -0.0022618477    0.000000E+000     True
   5     0.0019123415     0.0019123415    0.000000E+000     True
   6       0.00608653       0.00608653    0.000000E+000     True
   7    -0.0052014478    -0.0052014478    0.000000E+000     True
   8     -0.016489426     -0.016489424    1.862645E-009     True
   9      0.009599558      0.009599558    0.000000E+000     True
  10      0.035688534      0.035688538    3.725290E-009     True
  11     -0.026160091     -0.026160091    0.000000E+000     True
  12      -0.08800873      -0.08800873    0.000000E+000     True
  13       0.16196862       0.16196862    0.000000E+000     True
  14       0.91199124        0.9119913    5.960464E-008     True
  15          1.97384          1.97384    0.000000E+000     True
  16        3.0356889        3.0356886    2.384186E-007     True
  17        4.0095997        4.0095997    0.000000E+000     True
  18        4.9835114         4.983511    4.768372E-007     True
  19        5.9947987         5.994799    4.768372E-007     True
  20         7.006087        7.0060863    4.768372E-007     True
  21         8.001913         8.001913    0.000000E+000     True
  22         8.997738         8.997738    0.000000E+000     True
  23         9.998984         9.998981    2.861023E-006     True
  24        11.000226        11.000226    0.000000E+000     True
  25       12.0001135       12.0001135    0.000000E+000     True
  26               13               13    0.000000E+000     True
  27        13.999999               14    9.536743E-007     True
  28        15.000001        15.000001    0.000000E+000     True
  29        15.999998               16    1.907349E-006     True
  30               17        17.000002    1.907349E-006     True
  31        18.000002               18    1.907349E-006     True

1E-06f示例:

   index         expected           actual       difference    match
   0   -2.7051452E-34   -2.7051452E-34    0.000000E+000     True
   1    0.00011297482    0.00011297482    0.000000E+000     True
   2    0.00022594964    0.00022594964    0.000000E+000     True
   3    -0.0010179491    -0.0010179491    0.000000E+000     True
   4    -0.0022618477    -0.0022618477    0.000000E+000     True
   5     0.0019123415     0.0019123415    0.000000E+000     True
   6       0.00608653       0.00608653    0.000000E+000     True
   7    -0.0052014478    -0.0052014478    0.000000E+000     True
   8     -0.016489426     -0.016489424    1.862645E-009     True
   9      0.009599558      0.009599558    0.000000E+000     True
  10      0.035688534      0.035688538    3.725290E-009     True
  11     -0.026160091     -0.026160091    0.000000E+000     True
  12      -0.08800873      -0.08800873    0.000000E+000     True
  13       0.16196862       0.16196862    0.000000E+000     True
  14       0.91199124        0.9119913    5.960464E-008     True
  15          1.97384          1.97384    0.000000E+000     True
  16        3.0356889        3.0356886    2.384186E-007     True
  17        4.0095997        4.0095997    0.000000E+000     True
  18        4.9835114         4.983511    4.768372E-007     True
  19        5.9947987         5.994799    4.768372E-007     True
  20         7.006087        7.0060863    4.768372E-007     True
  21         8.001913         8.001913    0.000000E+000     True
  22         8.997738         8.997738    0.000000E+000     True
  23         9.998984         9.998981    2.861023E-006    False
  24        11.000226        11.000226    0.000000E+000     True
  25       12.0001135       12.0001135    0.000000E+000     True
  26               13               13    0.000000E+000     True
  27        13.999999               14    9.536743E-007     True
  28        15.000001        15.000001    0.000000E+000     True
  29        15.999998               16    1.907349E-006    False
  30               17        17.000002    1.907349E-006    False
  31        18.000002               18    1.907349E-006    False

这是我执行比较的方式:

const float delta = 1E-05f;

var expectedValue = expected[i];
var actualValue   = actual[i];
var difference    = Math.Abs(expectedValue - actualValue);
var failed        = difference > delta;

根据文档,精度约为 6 到 9 位。

问题:

我能比较的最低增量是确实还是可能的?1E-05f1E-06f

C# 浮点 精度

评论

0赞 Good Night Nerd Pride 8/14/2023
这是双倍的。厄普西隆
2赞 Good Night Nerd Pride 8/14/2023
实际上,这可能不是真的。我认为增量取决于您正在比较的两个值的大小。较大的值可能必须使用更大的增量。
0赞 jdweng 8/14/2023
2.861023E-006大于1.000000E-006,不小于。
0赞 Dmitry Bychenko 8/14/2023
似乎您应该对增量进行归一化,如果并且归一化时差异与(注意)一样多。在你的情况下是关于什么时候是expectedValue = 1e30actualValue = 1.000000001e301e+21+1e21 / 1e30 ~ 1e-90.000112974821e-4181e1
2赞 Eric Postpischil 8/14/2023
Re “根据文档,精度约为 6 到 9 位数字”:该文档是荒谬和错误的

答:

2赞 JonasH 8/14/2023 #1

如果确定浮点值完全相同,则可以精确比较浮点数。但是一旦你开始涉及数学,事情就会变得更加复杂,因为精度将取决于你正在执行的具体操作,以及值的范围。

例如说.结果应该是 ,但错误是什么?如果 和 结果是无穷大的,所以误差是无穷大的。通常,在大小差异较大的值之间执行操作时,精度会受到影响。(a * b) / aba = float.MaxValueb = float.MinValue

有关详细信息,请参阅我应该如何进行浮点比较?。另请参阅每个程序员都应该了解的浮点运算知识

在实践中,对于单元测试,可以使用通过测试的最低增量。这样你至少知道结果不会变得更糟。如果你需要更高的精度,你可能应该使用double来代替。

评论

0赞 aybe 8/14/2023
在我的阅读清单上有那个链接,看起来是时候认真看了!
1赞 Dmitry Bychenko 8/14/2023 #2

您应该规范化 ,只需看一下(夸张的)示例:difference

  expected : 1e-33 
    actual : 1e-30    
difference : 9.99e-31
     match : True
   comment : actual is 1000 times larger than expected but still fits your criterium

  expected : 1e+33 
    actual : 1.000000000000001e+33    
difference : 1e+18
     match : False
   comment : The delta is tiny (in 15th digit!) but delta doesn't fit your criterium

我们甚至可以用更自相矛盾的方式来表达它:

  • 如果我们同时以兆秒差距为单位进行测量,我们总是符合标准expectedactual
  • 如果我们同时以埃为单位进行测量,我们总是无法达到标准expectedactual

测量值是相同的,测量单位(我们的自由选择)是不同的。

我认为你应该选择一个不同的选项,比如说

actual至少有正确的数字反对digitsexpected

int digits = 6;

bool match = Math.Abs((expected - actual) / actual) < Math.Pow(10, -digits);

评论

0赞 aybe 8/14/2023
我必须再写一些测试才能完全掌握你在解释什么!
0赞 Eric Postpischil 8/14/2023
bool match = Math.Abs((expected - actual) / actual) < Math.Pow(10, -digits);是错误的,因为 FIR 结果中的错误预计不会与预期结果有绑定。它是中间结果的函数。从问题中的示例输出中可以看出,一些数据是负数,因此一些中间结果将被取消。因此,在某些情况下,例如,计算了接近 3 的某个值,并计算了接近 −3 的另一个值,并将这些值相加以产生接近零的最终结果,例如 2^−20...
0赞 Eric Postpischil 8/14/2023
...但是累积的舍入误差可能主要由接近 3 和 −3 的值的计算决定,而不是接近 2^−20 的值。
2赞 Eric Postpischil 8/14/2023 #3

边界浮点舍入误差

我有一套 240 个 FIR 滤波器卷积的单元测试,具有不同的抽头数量。

浮点运算中发生的舍入误差取决于操作数的值和执行的运算。您尚未提供这些值或操作。(FIR 在数学上是多个变量的线性函数,但浮点运算不是关联的,因此以各种方式对运算进行分组可能会减少误差。

如果没有精确值,则可以计算给定值边界的误差边界,但尚未给定值的边界。

当使用具有四舍五入到最接近的 IEEE-754 算术时,单次运算的最大误差为 1/2ULP(y),其中 y 是理想的数学结果,ULP(y) 是 y 的最低精度单位。最低精度的单位是有效位中最低位的位置值,根据 y 范围内的数字进行缩放。

对于 IEEE-754 binary32 格式,通常用于 ,ULP(y) 在格式的有限范围内最多为 y•2−23 for y(在有限范围之外,潜在误差是无限的。float

考虑一个 FIR 实现,它以迭代方式在数据上累积总量,一次一个项,并抽头每个量级最多为 m。第一个乘积最多为 m 2,因此误差的边界为 1/2ULP(m2)。然后计算另一个项,其边界最多为 1/2ULP(m 2),并添加到第一个乘积中,产生一个量级最多为 2 m 2 的结果(除非如果舍入误差增加了项,则可能会更多一些),因此加法的误差边界为 1/2ULP(2 m 2) = ULP(m 2)。到目前为止,我们知道的总边界是 1/2ULP(m 2) + 1/2ULP(m 2) + ULP(m 2) = 2 ULP(m 2)。给定 t 抽头,有 t 个误差边界为 1/2ULP(m 2) 的乘法和误差边界为 1/2ULP(i•m 2) 的 t−1 加法,误差范围为 1/2ULP(i•m2),用于 1 < i t

由于 ULP(y) 以 y•2−23 为界,因此总误差的界为 t • 1/2 m 2 • 2−23 + sum(1/2•i•m 2•2−23 for 1 < i ≤ t) = tm 2•2−24 + m•2−24•sum(i for 1 < i t) = tm 2•224 + m 22−24•(tt+1)/2−1) = 2−24m 2(1/2t2+3/2t−1).

除了忽略运算四舍五入以增加 ULP 的可能性(统计学上不太可能)之外,这是误差的绝对界限。它可能比您在实践中观察到的要大。有了其他信息,也许可以得出一个下限。

请注意,它随着点击次数的平方而增加。

消除浮点舍入误差

测试 FIR 的目标可能是测试为实现 FIR 而编写的代码,而不是测试浮点运算的硬件实现。如果可以测试软件是否对所需数据执行所需的加法和乘法运算,则测试覆盖率良好,并且无需测试浮点运算是否正确执行。

为此,只需构建执行基本 FIR 算术的测试用例就足够了。

例如,构造包含所有零的输入数据,但内部某处的单个零除外。输出数据应包含滤波器到达脉冲之前的所有零,然后应按适当的顺序逐个包含每个滤波器系数,然后应恢复零。这些输出不会有舍入错误。像这样的简单测试测试FIR实现是否在预期位置使用预期系数,用于信号内部的数据。其他数据可以测试边缘。

当然,FIR 实现可能会意外地在术语之间使用“最大”运算,而不是加法。单个脉冲仅测试FIR中的一个点,这不足以测试矢量化实现。我们可以构建滤波系数为 1、8、64、512 的数据,...如图221所示,信号数据包含一些模式,如0、1、2、3、4、5、6、7、0、1、2、3、4、5、6、7。预期的输出都应该是精确的(没有舍入错误),因为所有结果(包括中间计算)都可以完全以 binary32 格式表示。但是,此数据可以很好地测试是否正在执行 FIR 的预期计算。您还可以随机化信号数据的顺序(结果仍然准确)。

总之,使用系数为 2 3 i 的滤波器进行测试,表示 0 ≤i < 8,并使用来自 { 1, 2, 3, 4, 5, 6, 7 } 的随机数据的信号进行测试,不会出现舍入误差,并且对 FIR 实现中的错误进行很好的测试。

更广泛地说,对于具有 t 抽头的滤波器,我们可以将信号数据和滤波器系数设置为最多 sqrt(224/t) 的任何幅度整数值,并且预期结果将始终以 binary32 格式表示。您可以随机选择,也可以选择对您有用的模式。

评论

0赞 aybe 8/15/2023
这是一场耐心的游戏,我彻底检查了一下,确实,一些矢量化卷积是不对的,研究它!
0赞 aybe 8/21/2023
最简单的建议是最好的,助长冲动,事实上一切都是对的。