java – 什么可能导致浮点数突然被关闭1位没有算术变化

在进行一些没有改变任何算术的重构变化时,我以某种方式改变了我的程序(基于代理的仿真系统)的输出.输出中的各种数字现在以微小的数量关闭.检查显示,这些数字在其最低有效位中被关闭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).后两个是使用标准库完成的.在整个程序中使用随机数,并且使用包含在框架中的Random class(Repast)生成.

大多数数字在1e-3到1e5的范围内.几乎没有很大或很小的数字. Infinity和NaN在许多地方使用.

作为基于代理的模拟系统,许多公式被重复应用于模拟出现.评估的顺序是非常重要的(因为许多变量取决于其他人首先评估 – 例如,要计算BMI,我们需要首先计算饮食和心脏状态).以前的变量值在许多计算中也是非常重要的(所以这个问题可以在程序的早期介绍到其余的部分).

以下是浮点表达式的评估可以不同的几种方法:

(1)浮点处理器具有“当前舍入模式”,这可能导致最低有效位的结果不同.您可以拨打一个电话,您可以获取或设置当前值:向零向零,朝向∞或朝向∞.

(2)听起来strictfp与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
翻译自:https://stackoverflow.com/questions/25047290/what-could-cause-floating-point-numbers-to-suddenly-be-off-by-1-bit-without-arit

转载注明原文:java – 什么可能导致浮点数突然被关闭1位没有算术变化