使用浮动时如何获得一致的程序行为?

sjs*_*sjs 11 c c++ floating-point

我正在编写一个以离散步骤进行的模拟程序.模拟由许多节点组成,每个节点都有一个与之关联的浮点值,每个节点都会重新计算.结果可以是正数,负数或零.

在结果为零或更少的情况下发生的事情.到目前为止,这似乎很简单 - 我可以为每个节点做这样的事情:

if (value <= 0.0f) something_happens();
Run Code Online (Sandbox Code Playgroud)

然而,在我最近对程序进行的一些更改之后出现了一个问题,在该程序中我重新安排了某些计算完成的顺序.在一个完美的世界中,在重新安排之后,数值仍然会相同,但由于浮点表示的不精确,它们的出现略有不同.由于每个步骤的计算取决于前一步骤的结果,因此随着模拟的进行,结果中的这些微小变化会累积成更大的变化.

这是一个简单的示例程序,演示了我所描述的现象:

float f1 = 0.000001f, f2 = 0.000002f;
f1 += 0.000004f; // This part happens first here
f1 += (f2 * 0.000003f);
printf("%.16f\n", f1);

f1 = 0.000001f, f2 = 0.000002f;
f1 += (f2 * 0.000003f);
f1 += 0.000004f; // This time this happens second
printf("%.16f\n", f1);
Run Code Online (Sandbox Code Playgroud)

这个程序的输出是

0.0000050000057854
0.0000050000062402
Run Code Online (Sandbox Code Playgroud)

即使加法是可交换的,所以两个结果应该是相同的.注意:我完全理解为什么会发生这种情况 - 这不是问题.问题在于,这些变化可能意味着有时一个曾经在步骤N中出现负值的值,触发something_happens(),现在可能在一两步之前或之后出现负值,这可能导致整体模拟结果非常不同,因为something_happens()有很大的影响.

我想知道的是,是否有一个很好的方法可以决定何时应该触发something_happens(),这不会受到重新排序操作导致的计算结果微小变化的影响,从而导致更新版本的行为我的程序将与旧版本保持一致.

我到目前为止唯一能够想到的解决方案是使用像这样的值epsilon:

if (value < epsilon) something_happens();
Run Code Online (Sandbox Code Playgroud)

但是因为结果中的微小变化随着时间的推移而积累,我需要使epsilon相当大(相对而言)以确保变化不会导致something_happens()在不同的步骤中被触发.有没有更好的办法?

我已经阅读了这篇关于浮点比较的优秀文章,但我没有看到所描述的任何比较方法在这种情况下如何帮助我.

注意:不能使用整数值.


编辑使用双精度而不是浮点数的可能性已被提出.这不会解决我的问题,因为变化仍然存在,它们只是一个较小的量级.

Olo*_*ell 0

我建议您单步执行计算(最好是在汇编模式下),同时在计算器上执行相同的算术。您应该能够确定哪些计算顺序产生的结果质量低于您的预期以及哪些有效。您将从中学习,并可能在将来编写更好排序的计算。

最后 - 考虑到您使用的数字示例 - 您可能需要接受这样的事实:您将无法进行相等比较。

对于 epsilon 方法,您通常需要为每个可能的指数一个 epsilon。对于单精度浮点格式,您需要 256 个单精度浮点值,因为指数为 8 位宽。一些指数可能是异常的结果,但为了简单起见,拥有 256 个成员向量比进行大量测试更好。

一种方法是确定指数为 0 的情况下的底 epsilon,即要比较的值在 1.0 <= x < 2.0 范围内。最好将 epsilon 选择为以 2 为基础的适应 IEA 值,该值可以用单精度浮点格式精确表示 - 这样您就可以确切地知道您正在测试的内容,并且不必考虑 epsilon 中的舍入问题,因为出色地。对于指数 -1,您将使用基数 epsilon 除以 2,对于 -2,您将使用除以 4 等等。当您接近指数范围的最低和最高部分时,您会逐渐失去精度(一点一点),因此您需要意识到极端值可能会导致 epsilon 方法失败。