Kat*_*Kat 15 java floating-point floating-point-precision
在进行稍微大的重构更改而不修改任何类型的算术时,我设法以某种方式更改了我的程序的输出(基于代理的模拟系统).输出中的各种数字现在都是微不足道的数量.检查表明这些数字在最低有效位中偏离了1位.
例如,24.198110084326416将变为24.19811008432642.每个数字的浮点表示为:
24.198110084326416 = 0 10000000011 1000001100101011011101010111101011010011000010010100
24.19811008432642 = 0 10000000011 1000001100101011011101010111101011010011000010010101
Run Code Online (Sandbox Code Playgroud)
其中我们注意到最低有效位是不同的.
我的问题是,当我没有修改任何类型的算术时,我是如何引入这种变化的?该更改涉及通过删除继承来简化对象(其超类因使用不适用于此类的方法而膨胀).
我注意到输出(在模拟的每个刻度处显示某些变量的值)有时会关闭,然后对于另一个滴答,数字是预期的,只有在下一个滴答时再次关闭(例如,在一个代理上) ,它的值在刻度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;
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,我无法创建一个小的,可编译的例子,因为我无法在程序之外复制这种行为,也没有将这个非常庞大且纠缠不清的程序缩小到规模.
至于正在进行什么样的浮点运算,没有什么特别令人兴奋的.大量的加法,乘法,自然对数和幂(几乎总是以e为基数).后两者是用标准库完成的.在整个程序中使用随机数,并使用所使用的框架(Repast)中包含的Random类生成.
大多数数字在1e-3到1e5的范围内.几乎没有非常大或非常小的数字.Infinity和NaN在很多地方使用.
作为基于代理的仿真系统,许多公式被重复应用于模拟出现.评估的顺序非常重要(因为许多变量取决于首先评估的其他变量 - 例如,为了计算BMI,我们需要首先计算饮食和心脏状态).先前的变量值在许多计算中也非常重要(因此这个问题可以在程序的早期某个地方引入,并在其余部分中进行).
Jen*_*der -2
如果没有准确的源代码来重现问题,显然不可能查明问题所在。但您的差异表明您更改了列表的处理方式。您还提到许多简单的数学运算,例如加法发生在您的应用程序中。因此,我的猜测是,通过更改列表,您可以更改处理内容的顺序,这可能足以改变舍入误差。
是的,任何东西都不应该依赖于浮点变量的最低有效位,因此测试应该需要 epsilon。