Java 什么会导致浮点数在没有算术更改的情况下突然关闭1位
在做了一个不修改任何算法的较大重构更改时,我设法以某种方式更改了程序的输出(基于代理的模拟系统)。现在,输出中的各种数字都减少了很小的数量。检查表明,这些数字在其最低有效位中偏离了1位 例如,24.198110084326416将变成24.19811008432642。每个数字的浮点表示形式为:Java 什么会导致浮点数在没有算术更改的情况下突然关闭1位,java,floating-point,floating-point-precision,Java,Floating Point,Floating Point Precision,在做了一个不修改任何算法的较大重构更改时,我设法以某种方式更改了程序的输出(基于代理的模拟系统)。现在,输出中的各种数字都减少了很小的数量。检查表明,这些数字在其最低有效位中偏离了1位 例如,24.198110084326416将变成24.19811008432642。每个数字的浮点表示形式为: 24.198110084326416 = 0 10000000011 1000001100101011011101010111101011010011000010010100 24.1981100843
24.198110084326416 = 0 10000000011 1000001100101011011101010111101011010011000010010100
24.19811008432642 = 0 10000000011 1000001100101011011101010111101011010011000010010101
我们注意到最低有效位是不同的
我的问题是,在我没有修改任何类型的算术的情况下,我如何能够引入这种变化?更改涉及通过删除继承来简化对象(其超类中包含了不适用于该类的方法)
我注意到,输出(在模拟的每个刻度上显示某些变量的值)有时会关闭,然后对于另一个刻度,数字与预期一样,只是在接下来的刻度上再次关闭(例如,在一个代理上,其值在滴答声57-83上显示此问题,但与滴答声84和85的预期一样,只在滴答声86上再次关闭)
我知道我们不应该直接比较浮点数。当仅仅将输出文件与预期输出进行比较的集成测试失败时,就会注意到这些错误。我可以(也许也应该)修复解析文件的测试,并将解析的double与一些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;
}
diff--git a/src/main/java/modelClasses/GridSquare.java b/src/main/java/modelClasses/GridSquare.java
索引4c10760..80276bd 100644
---a/src/main/java/modelClasses/GridSquare.java
+++b/src/main/java/modelClasses/GridSquare.java
@@-63,7+63,7@@公共类GridSquare扩展了VariableLevel
公共住户(住户hh)
{
断言住户==null;
-子代理添加(hh);
+getHouseholdList().add(hh);
住户=hh;
}
@@-73,7+73,7@@公共类GridSquare扩展了VariableLevel
公屋住户()
{
断言家庭!=null;
-子代理。移除(家用);
+getHouseholdList().remove(householdlist);
住户=零;
}
diff——git a/src/main/java/modelClasses/Neighborhood.java b/src/main/java/modelClasses/Neighborhood.java
索引834a321..8470035 100644
---a/src/main/java/modelClasses/Neighborhood.java
+++b/src/main/java/modelClasses/Neighborhood.java
@@-166,9+166,14@@公共类邻域扩展VariableLevel
世界;
/**
+*社区内所有网格正方形的列表。
+ */
+ArrayList gridSquareList=新的ArrayList();
+
+ /**
*邻域内的空网格正方形列表
*/
-ArrayList emptyGridSquareList;
+ArrayList emptyGridSquareList=新建ArrayList();
/**
*邻域的方格边界
@@-836,7+841,7@@公共类邻域扩展VariableLevel
*/
公共GridSquare getGridSquare(int i)
{
-返回(GridSquare)(子代理获取(i));
+return(GridSquare)gridsquarlist.get(i);
}
/**
@@-865,7+870,7@@公共类邻域扩展可变级别
@凌驾
公共ArrayList getGridSquareList()
{
-返回子代理;
+返回列表;
}
/**
@@-874,12+879,7@@公共类邻域扩展VariableLevel
@凌驾
公共阵列列表getHouseholdList()
{
-ArrayList=新建ArrayList();
-对于(int i=0;i
不幸的是,我无法创建一个小的、可编译的示例,因为我无法在程序之外复制这种行为,也无法将这个非常大的、纠缠不清的程序缩减到一定的大小
至于要做什么样的浮点运算,没有什么特别令人兴奋的。大量的加法、乘法、自然对数和幂(几乎总是以e为底).后两个是用标准库完成的。随机数在整个程序中使用,并随所使用的框架(Repast)一起生成
大多数数字都在1e-3到1e5之间。几乎没有非常大或非常小的数字。很多地方都使用无穷大和NaN
作为一个基于代理的模拟系统,许多公式被重复应用于模拟出现。评估顺序非常重要(因为许多变量取决于首先评估的其他变量——例如,要计算BMI,我们需要首先计算饮食和心脏状态).变量的先前值在许多计算中也非常重要(因此,这个问题可以在程序早期的某个地方引入,并贯穿整个程序的其余部分).如果没有精确的源代码来重现问题,显然不可能找出问题所在。但您的差异表明,您改变了列表的处理方式。您还提到,许多简单的数学运算,例如加法,都发生在您的应用程序中。因此,我猜想,通过更改列表,您可以改变列表的顺序ch stuff得到处理,这可能足以改变舍入错误
是的,任何东西都不应该依赖于浮点变量的最低有效位,因此测试应该需要epsilons。由于strictfp已被消除,我将提供一个想法 某些版本的Repast存在错误,某些随机数生成错误* 即使将随机种子设置为相同的值,ArrayList也是在代码中的不同点创建和使用的
(A + B) + C ≠ A + (B + C)
B < (A * epsilon)
A + B = A