提问人:aybe 提问时间:8/14/2023 最后编辑:aybe 更新时间:8/15/2023 访问量:71
比较两个 32 位浮点值的增量值可以有多低?
How low can a delta value be to compare two 32-bit floating point values?
问:
我有一套 240 个 FIR 滤波器卷积的单元测试,具有不同的抽头数量。
预期用途:
它用于在 Unity 中编写音频过滤器,最初的朴素卷积是在托管代码中完成的,但对于实时音频处理来说太慢了。
单元测试确实测试了不同矢量化 FIR 卷积算法的精确性,其中一种算法使用 Unity Burst 进行拾取和实现,并将其转换为可感知 SIMD 的原生代码。
为了能够决定哪种卷积算法性能更好,我写了几篇文章:
- 朴素卷积
- 用作比较的参考
- 矢量化卷积
- 内环、外环、内环和外环
- 上述的半频段版本
- 1 次点击,2 次迭代所有点击
- 1 抽头进 2 次迭代一半的抽头(利用滤波器对称性)
实际情况:
所有测试都通过了,当我打磨时,我注意到比较的增量是 .1E-04f
我把它降低到,这有效,尝试过,但通过率< 50%。1E-05f
1E-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-05f
1E-06f
答:
如果确定浮点值完全相同,则可以精确比较浮点数。但是一旦你开始涉及数学,事情就会变得更加复杂,因为精度将取决于你正在执行的具体操作,以及值的范围。
例如说.结果应该是 ,但错误是什么?如果 和 结果是无穷大的,所以误差是无穷大的。通常,在大小差异较大的值之间执行操作时,精度会受到影响。(a * b) / a
b
a = float.MaxValue
b = float.MinValue
有关详细信息,请参阅我应该如何进行浮点比较?。另请参阅每个程序员都应该了解的浮点运算知识
在实践中,对于单元测试,可以使用通过测试的最低增量。这样你至少知道结果不会变得更糟。如果你需要更高的精度,你可能应该使用double来代替。
评论
您应该规范化 ,只需看一下(夸张的)示例: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
我们甚至可以用更自相矛盾的方式来表达它:
- 如果我们同时以兆秒差距为单位进行测量,我们总是符合标准
expected
actual
- 如果我们同时以埃为单位进行测量,我们总是无法达到标准
expected
actual
测量值是相同的,测量单位(我们的自由选择)是不同的。
我认为你应该选择一个不同的选项,比如说
actual
至少有正确的数字反对digits
expected
int digits = 6;
bool match = Math.Abs((expected - actual) / actual) < Math.Pow(10, -digits);
评论
bool match = Math.Abs((expected - actual) / actual) < Math.Pow(10, -digits);
是错误的,因为 FIR 结果中的错误预计不会与预期结果有绑定。它是中间结果的函数。从问题中的示例输出中可以看出,一些数据是负数,因此一些中间结果将被取消。因此,在某些情况下,例如,计算了接近 3 的某个值,并计算了接近 −3 的另一个值,并将这些值相加以产生接近零的最终结果,例如 2^−20...
边界浮点舍入误差
我有一套 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•2−24 + m 2•2−24•(t(t+1)/2−1) = 2−24m 2(1/2t2+3/2•t−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 格式表示。您可以随机选择,也可以选择对您有用的模式。
评论
双倍的。厄普西隆
。expectedValue = 1e30
actualValue = 1.000000001e30
1e+21
+
1e21 / 1e30 ~ 1e-9
0.00011297482
1e-4
18
1e1