在不修改任何算术的大重构更改中,我设法更改了程序的输出(一个基于代理的仿真系统)。输出中的各种数字现在以微乎其微的数量减少。检查表明,这些数字是1位,在他们的最小显着性位。
例如,24.198110084326416将变成24.19811008432642。每个数字的浮点表示是:
24.198110084326416 = 0 10000000011 1000001100101011011101010111101011010011000010010100
24.19811008432642 = 0 10000000011 1000001100101011011101010111101011010011000010010101其中我们注意到最不重要的比特是不同的。
我的问题是,当我没有修改任何类型的算术时,我如何才能引入这一变化?更改涉及通过删除继承简化对象(它的超类包含不适用于该类的方法)。
我注意到,输出(在模拟的每个滴答处显示某些变量的值)有时会关闭,然后对于另一个滴答,这些数字与预期相同,但对于下面的滴答(例如,在一个代理上,它的值在滴答57 - 83上显示了这个问题,但对于滴答84和85,它的值却与预期的一样,只会在第86号上再次关闭)。
我知道我们不应该直接比较浮点数。当仅将输出文件与预期输出进行比较的集成测试失败时,就会注意到这些错误。我可以(也许也应该)修复解析文件的测试,并将分析过的双数与一些epsilon进行比较,但我仍然好奇为什么会引入这个问题。
编辑:
引入问题的最小变化差异:
diff --git a/src/main/java/modelClasses/GridSquare.java b/src/main/java/modelClasses/GridSquare.java
index 4c10760..80276bd 100644
--- a/src/main/java/modelClasses/GridSquare.java
+++ b/src/main/java/modelClasses/GridSquare.java
@@ -63,7 +63,7 @@ public class GridSquare extends VariableLevel
public void addHousehold(Household hh)
{
assert household == null;
- subAgents.add(hh);
+ neighborhood.getHouseholdList().add(hh);
household = hh;
}
@@ -73,7 +73,7 @@ public class GridSquare extends VariableLevel
public void removeHousehold()
{
assert household != null;
- subAgents.remove(household);
+ neighborhood.getHouseholdList().remove(household);
household = null;
}
diff --git a/src/main/java/modelClasses/Neighborhood.java b/src/main/java/modelClasses/Neighborhood.java
index 834a321..8470035 100644
--- a/src/main/java/modelClasses/Neighborhood.java
+++ b/src/main/java/modelClasses/Neighborhood.java
@@ -166,9 +166,14 @@ public class Neighborhood extends VariableLevel
World world;
/**
+ * List of all grid squares within the neighborhood.
+ */
+ ArrayList<VariableLevel> gridSquareList = new ArrayList<>();
+
+ /**
* A list of empty grid squares within the neighborhood
*/
- ArrayList<GridSquare> emptyGridSquareList;
+ ArrayList<GridSquare> emptyGridSquareList = new ArrayList<>();
/**
* The neighborhood's grid square bounds
@@ -836,7 +841,7 @@ public class Neighborhood extends VariableLevel
*/
public GridSquare getGridSquare(int i)
{
- return (GridSquare) (subAgents.get(i));
+ return (GridSquare) gridSquareList.get(i);
}
/**
@@ -865,7 +870,7 @@ public class Neighborhood extends VariableLevel
@Override
public ArrayList<VariableLevel> getGridSquareList()
{
- return subAgents;
+ return gridSquareList;
}
/**
@@ -874,12 +879,7 @@ public class Neighborhood extends VariableLevel
@Override
public ArrayList<VariableLevel> getHouseholdList()
{
- ArrayList<VariableLevel> list = new ArrayList<VariableLevel>();
- for (int i = 0; i < subAgents.size(); i++)
- {
- list.addAll(subAgents.get(i).getHouseholdList());
- }
- return list;
+ return subAgents;
}不幸的是,我无法创建一个小的、可编译的示例,因为我无法将此行为复制到程序之外,也无法将这个非常大且纠缠的程序缩减到大小。
至于做什么浮点操作,没有什么特别令人兴奋的。大量的加法、乘法、自然对数和幂(几乎总是以e为基数)。后两者都是用标准库完成的。随机数是在整个程序中使用的,并与使用的框架(Repast)中包含的 class一起生成。
大多数数字在1e-3到1e5之间。几乎没有很大或很小的数字。无穷大和NaN在许多地方都有使用。
作为一个基于agent的仿真系统,许多公式被反复应用于模拟突现。评价的顺序是非常重要的(因为许多变量取决于其他变量首先被评估--例如,要计算BMI,我们需要首先计算饮食和心脏状况)。以前的变量值在许多计算中也是非常重要的(因此,这个问题可以在程序的早期某个地方引入,并贯穿其中的其余部分)。
发布于 2016-04-12 10:32:21
以下是浮点表达式的计算可能不同的几种方法:
(1)浮点处理器有一个“当前舍入模式”,这可能导致结果在最小有效位上存在差异。您可以进行一个调用,可以得到或设置当前值:向零、向-∞或向+∞舍入。
(2)听起来严格的be与C中的FLT_EVAL_METHOD有关,后者指定了中间计算中使用的精度。有时,编译器的新版本将使用与旧版本不同的方法(我被那个方法咬了)。{0,1,2}分别对应于{单、双、扩展}精度,除非被较高精度的操作数覆盖。
(3)不同的编译器可以采用不同的默认浮点计算方法,不同的机器可以使用不同的浮点计算方法。
(4)单精度IEEE浮点算法定义清晰、重复性好、机器无关.双精度也是如此。我已经(非常小心地)编写了跨平台浮点测试,它使用SHA-1散列来检查计算是否准确!然而,在FLT_EVAL_METHOD=2中,中间计算采用扩展精度,采用64位、80位或128位浮点算法实现,因此在中间计算中使用扩展精度很难获得跨平台和交叉编译的重复性。
(5)浮点运算是不相联的。
(A + B) + C ≠ A + (B + C)因此,编译器不允许重新排序浮点数的计算。
(6)行动的次序很重要。一种以尽可能高的精度计算一大组数字之和的算法,就是把它们加在一个数量级上。另一方面,如果两个数字的大小相差很大
B < (A * epsilon)然后把它们加在一起是不可行的:
A + B = A发布于 2014-08-05 13:42:53
由于严格限制已被取消,我将提供一个想法。
有些版本的Repast有/有错误,错误地生成了某些随机数*。
即使将随机种子设置为相同的值,因为您的ArrayList是在代码中的不同位置创建和使用的,您也有可能以不同的顺序对其中的代理进行操作。如果有任何具有随机优先级的调度方法,则尤其如此。如果使用getAgentList()或类似的方式填充subAgents列表,情况也是如此。实际上,您可以生成一个随机数(/order),它位于为其设置种子的RNG之外。
如果在执行顺序上有微小的差异,这可以解释在一步之间的通信,而在其他步骤中只看到这个微小的差异。
我已经发生过这种情况,并且在调试时与您的报告有类似的头痛。如果你能提供更多的细节,我很高兴。
*知道你使用的是哪个版本会有很大帮助(我知道我不应该在回答中要求澄清,但没有得到代表的评论)。从您链接的API中,我认为您使用的是旧的Repast 3--我使用的是Simphony,但答案可能仍然适用。
发布于 2014-08-01 06:04:36
如果没有精确的源代码来再现这个问题,显然就不可能找到问题所在。但是您的差异显示,您更改了处理列表的方式。您还提到了很多简单的数学,比如在应用程序中进行加法。因此,我的猜测是,通过对列表的更改,您可以更改处理内容的顺序,这可能足以更改舍入错误。
是的,任何事情都不应该依赖于最不重要的浮点变量,所以测试应该需要epsilons。
https://stackoverflow.com/questions/25047290
复制相似问题