【发布时间】:2012-03-25 16:17:23
【问题描述】:
我正在编写一个以离散步骤进行的模拟程序。模拟由许多节点组成,每个节点都有一个与之关联的浮点值,每一步都会重新计算。结果可以是正数、负数或零。
如果结果为零或更小,就会发生某些事情。到目前为止,这似乎很简单——我可以为每个节点做这样的事情:
if (value <= 0.0f) something_happens();
然而,在我最近对程序进行了一些更改后,我重新安排了某些计算的执行顺序,因此出现了一个问题。在一个完美的世界中,重新排列后的值仍然会相同,但由于浮点表示的不精确性,它们的结果会略有不同。由于每个步骤的计算取决于前一步的结果,因此随着模拟的进行,结果中的这些细微变化会累积成更大的变化。
这是一个简单的示例程序,演示了我所描述的现象:
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);
这个程序的输出是
0.0000050000057854
0.0000050000062402
即使加法是可交换的,所以两个结果应该是相同的。注意:我完全理解为什么会发生这种情况 - 这不是问题。问题是这些变化可能意味着有时一个在第 N 步上出现负数的值,触发 something_happens(),现在可能早或晚一两步出现负数,这可能导致非常不同的整体模拟结果,因为something_happens() 有很大的影响。
我想知道的是,是否有一种好方法来决定什么时候应该触发 something_happens(),它不会受到重新排序操作导致的计算结果的微小变化的影响,以便我的程序的较新版本将与旧版本保持一致。
到目前为止,我能想到的唯一解决方案是使用一些像这样的值 epsilon:
if (value < epsilon) something_happens();
但是由于结果中的微小变化会随着时间的推移而累积,我需要使 epsilon 相当大(相对而言)以确保变化不会导致 something_happens() 在不同的步骤上被触发。有没有更好的办法?
我已经阅读了this excellent article 关于浮点比较的内容,但我看不出所描述的任何比较方法在这种情况下对我有什么帮助。
注意:不能选择使用整数值。
编辑提高了使用双精度数而不是浮点数的可能性。这不会解决我的问题,因为变化仍然存在,只是幅度较小。
【问题讨论】:
-
如果微小的变化会导致输出发生很大的变化,那不就是告诉您您的结果准确性低吗? (另外:为什么浮动不加倍?)
-
小心:
printf("%.16f\n", f1);这是一个意想不到的副作用:它会将您的浮点数转换为双加非有效数字。一个浮点数,我认为,最大精度为 7 位。 -
对浮点值求和的标准方法是:对它们进行排序并从最小到最大求和,这会降低精度。也使用 double 而不是 float。
-
@J.N.您可以通过将 printfs 替换为
printf("%x\n",(*(int*)&f1))来验证结果是否不同。在第一种情况下,输出将是36a7c5b9,而在第二种情况下,36a7c5ba- 最后一个十六进制数字不同。 -
你绝对不能打破对先前值的依赖吗?或者类似于Kahan summation algorithm 中的方法来补偿精度损失?
标签: c++ c floating-point