我在Java中编写了一些捕食者 - 猎物模拟。即使规则非常复杂并且最终处于混乱系统中,所使用的技术也很简单:
所以我认为在使用相同的参数初始化系统时,它应该输出相同的结果,但它没有,我想知道为什么。
对此的一些想法:
我的应用程序使用Random
s,但对于该测试,我使用给定值初始化它们,因此根据我的理解,它们应该为每次运行创建相同顺序的相同输出。
我正在迭代Set
,我知道没有定义迭代Set
的顺序。但是我没有看到为什么以相同的值填充相同顺序的Set
在多次运行中表现不同的原因。是吗?
我正在使用很多float
s。 1 + 1 = 1.9999999999725的数据类型总是让我怀疑,但即使他们的行为对我来说很奇怪,也应该总是一样奇怪。不是吗?
垃圾收集不是确定性的,但只要我不依赖于析构函数,我就应该是安全的。
如上所述,根据实际使用时间,没有并发性和数据类型。
我无法在一个简单的例子中重现这种行为。但是通过我的代码,我看不到任何可能无法预测的事情。那么我上面的任何假设都是错误的吗?我有什么想法可以丢失吗?
这是验证我的假设的测试:
public static void main(String[] args) {
Random r = new Random(1);
Set<Float> s = new HashSet<Float>();
for (int i = 0; i < 1000000; i++) {
s.add(r.nextFloat());
}
float ret = 1;
int cnt = 0;
for (Float f : s) {
float multiply = 0.3f;
if (cnt++ % 2 == 0) {
multiply = 0.7f;
}
float f2 = (f * multiply);
ret += f2;
}
System.out.println(ret);
}
对我来说总是在242455.25。
答案 0 :(得分:20)
您可以用Java编写确定性程序。你只需要消除非确定性的可能来源。
很难知道在没有看到实际代码的情况下可能导致非确定性的原因,以及该决定论的具体证据。
有许多库方法可能是非确定性行为的来源......取决于您如何使用它们。
例如,Object.hashcode()
返回的值(第一次在实例上调用)是非确定性的。并且渗透到任何使用散列的库。它肯定会影响迭代它们时返回HashSet
或HashMap
元素的顺序...如果元素类没有覆盖hashcode()
。
随机数生成器可能是也可能不是确定性的。如果它们是伪随机的并且用固定种子初始化,则每个生成的数字序列将是确定性的。
浮点算术应该是确定性的。对于算术表达式的任何(固定)输入集,结果应始终相同。 (我不确定浮点运算的确定性是否由JLS保证,但如果它在实践中发生则会非常奇怪。就像......你在破坏的硬件上运行。)
关注 ... strictfp
和非决定论。
根据JLS 15.4:
“在非FP严格的表达式中,为实现使用扩展指数范围来表示中间结果的一些余地被授予;粗略地说,净效应是计算可能产生”正确答案“在独占使用浮点值设置或双值设置可能导致上溢或下溢的情况下。”
这并不能说明实现在非FP严格表达式中有多少“余地”。但是,我认为这种余地不会扩展到允许非确定性行为。我曾经认为特定平台上的JIT编译器总是会为同一个表达式生成等效的本机代码,并且该代码将是确定性的。 (我看不出任何非确定性的原因......除非硬件本身具有非确定性浮点。)非确定性的另一个可能来源可能是JIT编译和解释代码的行为可能不同。但坦率地说,我认为允许这种情况发生是“坚果”......我想我们已经听说过了。
因此,虽然非FP严格的表达评估在理论上可能是非确定性的,但我认为我们应该对此进行折扣......除非有明确证据证明它在实践中发生。
(请注意,我说的是真正的非决定论,而不是平台差异。)
答案 1 :(得分:11)
我正在通过集合进行迭代,并且我知道迭代Set的顺序没有被定义。但是我没有看到为什么以相同的值填充相同顺序的Set在多次运行中应该表现不同的原因。是吗?
它可以。该实现可以自由使用,例如,对象在内存中的位置作为底层哈希表的键。这取决于垃圾收集何时运行。