我做了这些大的随机操作循环来进行基准测试(只是因为我很好奇),然后碰到了我不了解的东西。无论我为大循环输入什么,它总是会产生相同的结果。这是减少到我正在谈论的部分的代码。
public int stuff;
public int result;
myJavaProgram(String[] cmdArguments)
{
stuff=1;
superLoopCalcualtion();
System.out.println(stuff+" converted to result:"+result);
stuff=12345;
superLoopCalcualtion();
System.out.println(stuff+" converted to result:"+result);
stuff=9823450;
superLoopCalcualtion();
System.out.println(stuff+" converted to result:"+result);
}
public void superLoopCalcualtion()
{
int a=stuff;
int b=a+99;
int z=0;
for (z = 0; z < 200000; z++)
{
a=a+22;
b=a*44;
a=b+1234;
}
result=a;
}
输出是这个
1 converted to result:1398361394
12345 converted to result:1398361394
9823450 converted to result:1398361394
没有办法可能是正确的...对吗?
最佳答案
您在这里看到的是(伪装的)溢出,导致有效的乘数为0,从而有效地结束了循环中的值更改。
溢出行动
为了看到这一点,让我们考虑一个使用byte
的简化代码示例,它可以使循环更短:
byte a = stuff;
for (byte z = 0; z < 8; z++) {
a = (byte) (a * 2);
}
我省略了在每次迭代中以十进制和二进制形式很好地打印数字的代码。以下是
stuff
为1、11和127(Byte.MAX_VALUE
)的结果:1
---
0: 1 00000001
1: 2 00000010
2: 4 00000100
3: 8 00001000
4: 16 00010000
5: 32 00100000
6: 64 01000000
7: -128 10000000
8: 0 00000000
11
---
0: 11 00001011
1: 22 00010110
2: 44 00101100
3: 88 01011000
4: -80 10110000
5: 96 01100000
6: -64 11000000
7: -128 10000000
8: 0 00000000
127
---
0: 127 01111111
1: -2 11111110
2: -4 11111100
3: -8 11111000
4: -16 11110000
5: -32 11100000
6: -64 11000000
7: -128 10000000
8: 0 00000000
要理解这一点,请考虑将二进制数乘以2会在右侧添加一个
0
。如果我们继续这样做,我们将“推”数字到数据结构可以容纳的范围之外。对于byte
,范围是8位。因此,在将8乘以2后,我们保证不管预先包含的数字是多少,现在都是0
。连续没有任何作用,因此我们陷入停滞(或术语陈旧)。将有效位压入“可见”范围之内称为溢出,因为二进制表示形式不能包含它们并且它们...溢出。以十进制表示,这会导致符号1发生变化。如果您查看
1
的示例,则该溢出仅在最后一次迭代时发生,因为该数量足够小,这相当于说它右边有很多可用空间。另一方面,127
立即溢出,因为它是byte
的最大值,也就是说,需要所有位。1在Java中,所有数字均已签名。
应用于您的代码
从这里开始,逐步增加复杂性直到我们到达您的代码为止,但是潜在的现象是相同的。
首先,我们可以使用
short
,int
和long
来增加二进制表示容量,但这只会延迟不可避免的时间。不需要8次迭代,我们将分别需要12, 32 and 64。接下来,我们可以更改乘法因子。偶数只是2的乘积,因此我们将得到相同的结果。请注意,在
2^n
的特殊情况下,我们总是会更快地达到结果,因为我们实际上只是在减少迭代次数。但是,使用奇数,我们将永远不会达到(十进制)0
;溢出将始终跳过它,我们将再次达到起始编号。这是用于64(stuff = 1
)次迭代的乘积因子3
的Byte.MAX_VALUE / 2 + 1
(字节):1
---
0: 1 00000001
1: 3 00000011
2: 9 00001001
3: 27 00011011
4: 81 01010001
5: -13 11110011
6: -39 11011001
7: -117 10001011
8: -95 10100001
9: -29 11100011
10: -87 10101001
11: -5 11111011
12: -15 11110001
13: -45 11010011
14: 121 01111001
15: 107 01101011
16: 65 01000001
17: -61 11000011
18: 73 01001001
19: -37 11011011
20: -111 10010001
21: -77 10110011
22: 25 00011001
23: 75 01001011
24: -31 11100001
25: -93 10100011
26: -23 11101001
27: -69 10111011
28: 49 00110001
29: -109 10010011
30: -71 10111001
31: 43 00101011
32: -127 10000001
33: -125 10000011
34: -119 10001001
35: -101 10011011
36: -47 11010001
37: 115 01110011
38: 89 01011001
39: 11 00001011
40: 33 00100001
41: 99 01100011
42: 41 00101001
43: 123 01111011
44: 113 01110001
45: 83 01010011
46: -7 11111001
47: -21 11101011
48: -63 11000001
49: 67 01000011
50: -55 11001001
51: 91 01011011
52: 17 00010001
53: 51 00110011
54: -103 10011001
55: -53 11001011
56: 97 01100001
57: 35 00100011
58: 105 01101001
59: 59 00111011
60: -79 10110001
61: 19 00010011
62: 57 00111001
63: -85 10101011
64: 1 00000001
我不想过多地讨论数学,因为我觉得这超出了问题的范围。可以肯定地说,在
MAX_VALUE / 2 + 1
迭代中,您将再次到达起始编号(对于某些编号,在此之前也是如此)。关键是您的
44
是偶数,因此您得到的停滞结果。现在开始您的加法运算。与您一起玩游戏一样,在乘法之前和之后,它所做的只是改变常数。效果保持不变。考虑
for (byte z = 0; z < 10; z++) {
a = (byte) (a + 1);
a = (byte) (a * 2);
}
结果是
1
---
0: 1 00000001
1: 4 00000100
2: 10 00001010
3: 22 00010110
4: 46 00101110
5: 94 01011110
6: -66 10111110
7: 126 01111110
8: -2 11111110
9: -2 11111110
10: -2 11111110
所以我们停滞在
-2
上。以十进制形式,您可以通过循环公式(-2 + 1) * 2 = -2
轻松看到这一点。您在循环中“随机”选择数字会(确定性地)导致〜15次迭代后确定数字1398361394
(使用long
将使此结果延迟一定的迭代次数)。只需逐个迭代进行数学运算,您将获得类似于上述的循环公式。结束时间
小心溢出!确保您选择的数据结构始终足以包含您正在使用的数字范围。最坏的情况是,您具有(非原始)类型的BigInteger以获得任意精度(但要慢得多)。不管上面讨论的任何参数如何,一旦您溢出数学结果将是错误的(除非您故意进行溢出数学)。
关于java - 为什么大型的计算循环无论输入如何都会产生相同的输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47562542/