使用浮点数时如何获得一致的程序行为? [英] How can I get consistent program behavior when using floats?

查看:57
本文介绍了使用浮点数时如何获得一致的程序行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个模拟程序,该程序以不连续的步骤进行.该模拟由许多节点组成,每个节点都有一个与之关联的浮点值,该浮点值将在每个步骤中重新计算.结果可以为正,负或零.

I am writing a simulation program that proceeds in discrete steps. The simulation consists of many nodes, each of which has a floating-point value associated with it that is re-calculated on every step. The result can be positive, negative or zero.

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

In the case where the result is zero or less something happens. So far this seems straightforward - I can just do something like this for each node:

if (value <= 0.0f) something_happens();

但是,在对程序进行了一些最近的更改之后,出现了一个问题,在该程序中,我重新安排了某些计算的顺序.在理想的情况下,重新安排后这些值仍然会相同,但是由于浮点表示形式的不精确性,它们的输出结果略有不同.由于每个步骤的计算取决于上一步的结果,因此结果的这些细微变化会随着模拟的进行而累积成较大的变化.

A problem has arisen, however, after some recent changes I made to the program in which I re-arranged the order in which certain calculations are done. In a perfect world the values would still come out the same after this re-arrangement, but because of the imprecision of floating point representation they come out very slightly different. Since the calculations for each step depend on the results of the previous step, these slight variations in the results can accumulate into larger variations as the simulation proceeds.

这是一个简单的示例程序,用于演示我正在描述的现象:

Here's a simple example program that demonstrates the phenomena I'm describing:

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()具有很大的作用.

even though addition is commutative so both results should be the same. Note: I understand perfectly well why this is happening - that's not the issue. The problem is that these variations can mean that sometimes a value that used to come out negative on step N, triggering something_happens(), now may come out negative a step or two earlier or later, which can lead to very different overall simulation results because something_happens() has a large effect.

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

What I want to know is whether there is a good way to decide when something_happens() should be triggered that is not going to be affected by the tiny variations in calculation results that result from re-ordering operations so that the behavior of newer versions of my program will be consistent with the older versions.

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

The only solution I've so far been able to think of is to use some value epsilon like this:

if (value < epsilon) something_happens();

但是由于结果的微小变化会随时间累积,因此我需要使epsilon很大(相对而言),以确保变化不会导致在不同的步骤上触发something_happens().有更好的方法吗?

but because the tiny variations in the results accumulate over time I need to make epsilon quite large (relatively speaking) to ensure that the variations don't result in something_happens() being triggered on a different step. Is there a better way?

我已阅读关于浮点比较的出色文章,但我看不到所描述的任何比较方法如何在这种情况下对我有帮助.

I've read this excellent article on floating point comparison, but I don't see how any of the comparison methods described could help me in this situation.

注意:不能使用整数值.

Note: Using integer values instead is not an option.

编辑提出了使用双精度代替浮点的可能性.这不会解决我的问题,因为变化仍然存在,只是变化幅度较小.

Edit the possibility of using doubles instead of floats has been raised. This wouldn't solve my problem since the variations would still be there, they'd just be of a smaller magnitude.

推荐答案

我建议您通过计算进行单步操作(最好是在组装模式下进行),同时在计算器上进行相同的算术运算.您应该能够确定哪些计算顺序所产生的结果质量比您预期的要差,并且哪种计算有效.您将从中学习,并可能在将来编写更有序的计算.

I recommend that you single step - preferably in assembly mode - through the calculations while doing the same arithmetic on a calculator. You should be able to determine which calculation orderings yield results of lesser quality than you expect and which that work. You will learn from this and probably write better-ordered calculations in the future.

最后-给出您使用的数字示例-您可能需要接受一个事实,即您将无法进行相等比较.

In the end - given the examples of numbers you use - you will probably need to accept the fact that you won't be able to do equality comparisons.

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

As to the epsilon approach you usually need one epsilon for every possible exponent. For the single-precision floating point format you would need 256 single precision floating point values as the exponent is 8 bits wide. Some exponents will be the result of exceptions but for simplicity it is better to have a 256 member vector than to do a lot of testing as well.

一种实现此目的的方法可能是在指数为0的情况下确定您的基本epsilon,即要与之进行比较的值在1.0< = x<范围内. 2.0.最好将epsilon选择为以2为基数的值,即可以精确地以单精度浮点格式表示的值-这样,您就可以准确地知道要测试的内容,而不必考虑epsilon中的舍入问题,因为出色地.对于指数-1,您将使用基本epsilon除以2,对于-2除以4,依此类推.当您接近指数范围的最低和最高部分时,您会逐渐失去精度-一点一点-因此,您需要意识到,极值会导致epsilon方法失败.

One way to do this could be to determine your base epsilon in the case where the exponent is 0 i e the value to be compared against is in the range 1.0 <= x < 2.0. Preferably the epsilon should be chosen to be base 2 adapted i e a value that can be exactly represented in a single precision floating point format - that way you know exactly what you are testing against and won't have to think about rounding problems in the epsilon as well. For exponent -1 you would use your base epsilon divided by two, for -2 divided by 4 and so on. As you approach the lowest and the highest parts of the exponent range you gradually run out of precision - bit by bit - so you need to be aware that extreme values can cause the epsilon method to fail.

这篇关于使用浮点数时如何获得一致的程序行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆