Java:为什么大正数相乘会导致负面结果?

Java:为什么大正数相乘会导致负面结果?,java,math,types,Java,Math,Types,我看到一些奇怪的行为,用Java将整数相乘。我在做一些编码练习,突然发现了下面的fizz-buzz类型的练习。要求:给定一个整数,编写一个函数,查找小于给定整数的3的每一个倍数的乘积,除了5的任意倍数。给定17,我们想返回12*9*6*3(=1944)。我写了以下内容: public int findProduct(int limit) { int product = 1; for(int n = 3; n < limit; n = n + 3) { if

我看到一些奇怪的行为,用Java将整数相乘。我在做一些编码练习,突然发现了下面的fizz-buzz类型的练习。要求:给定一个整数,编写一个函数,查找小于给定整数的3的每一个倍数的乘积,除了5的任意倍数。给定17,我们想返回12*9*6*3(=1944)。我写了以下内容:

public int findProduct(int limit) { 
    int product = 1;
    for(int n = 3; n < limit; n = n + 3) {
        if (n % 5 != 0) {
            product = product * n;
        }
    }
    return product;
}
public int find产品(int限制){
int乘积=1;
对于(int n=3;n
这只适用于小数字。然而,在测试中,我发现,一旦你达到33以上,不知何故,返回值是远远偏离。例如,如果调用限制为36的函数,它将返回-1.466221696E9。这就是我感到困惑的地方。我将正的整数相乘,结果是负的

然而,我发现如果你声明一个double,它似乎总是返回正确的结果

public double findProduct(int limit) { 
    double product = 1;
    for(int n = 3; n < limit; n = n + 3) {
        if (n % 5 != 0) {
            product = product * n;
        }
    }
    return product;
}
公共双findProduct(整数限制){
双积=1;
对于(int n=3;n

我的问题是:为什么整数会发生这种情况?双精度类型与双精度类型有什么不同,使其能够正确执行?

让我们以
Integer
为例来检查这一点

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
// 1111111111111111111111111111111
Integer.MAX_VALUE
可以表示为
011111111111111111
,它是一个
32
位长字符串(包括符号位)。现在,如果您将
1
添加到上述字符串中,它将产生
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000。这称为
整数的溢出

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
// 1111111111111111111111111111111
根据:

如果参数为负,则无符号整数值为参数加232;否则,它等于论点。此值转换为二进制ASCII数字字符串(以2为基数),无额外的前导0

这就是为什么你看不到符号位,但整数的实际值是
011111111111
。现在看一下这段代码:

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE + 1));
// 10000000000000000000000000000000
System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));
// 10000000000000000000000000000000
两个数字的输出相同。Java不能防止
Integer
溢出。开发人员应该注意这一点。那么,这个问题的可能解决方案是什么呢?您可以使用其他数据类型,如
long
或。以下是您可能感兴趣的最大值:

System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Long.MAX_VALUE); // 9223372036854775807
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
System.out.println(Float.MAX_VALUE); // 3.4028235E38

一旦
整数
达到
最大值
它将开始溢出,并以负值结束。

这称为整数溢出。本质上,您的数字对于存储解决方案的数据类型来说太大了。当这种情况发生时,由于两个补码,这些数字会变成负数。double是一种更大的数据类型,但如果您足够大,它也将变为负值

请参阅和上的这些文章。他们会详细解释的

编辑:来自wiki文章的简化解释。我还是建议你读一下这个。 假设我们有一个3位架构(000到111)。在二的补码中,我们说前导位决定数字是否为正/负。如果是负数,则以下数字与负数正相加。 下面是一个简单的计数示例:

+---------+--------+
| Decimal | Binary |
+---------+--------+
|       0 |    000 |
|       1 |    001 |
|       2 |    010 |
|       3 |    011 |
|      -4 |    100 | <----- note this isn't negative 0, or +4.
|      -3 |    101 | <----- note this isn't -1, or +5
|      -2 |    110 | <----- note this isn't +6
|      -1 |    111 |<----- note this isn't -3, or +7
|       0 |    000 |
+---------+--------+
+---------+--------+
|十进制|二进制|
+---------+--------+
|       0 |    000 |
|       1 |    001 |
|       2 |    010 |
|       3 |    011 |

|-4 | 100 |超出Double.MAX_值的范围,这就是结果为负值的原因

例如:

对于四位二进制数据是

00001此处最左边的位保留为符号位,[0表示+,1表示-],其余4位用于存储值。so 00001二进制=十进制+1

因此,对于4位,我们将能够写入最大15位十进制值

现在如果我想写16,那么溢出值范围


所以如果我把十进制16转换成二进制数据,它将是10000,所以符号位现在是1。最后的结果将是负数

如果要完全避免整数溢出问题,请尝试使用biginger:

这允许任意长度的整数

(实际上,从BigInteger的源代码来看,似乎存在一个最大大小,即int[],它存储以下的BigInteger:

Integer.MAX_VALUE / Integer.SIZE + 1

比任何人合理想要的都大得多!)

哦。呵呵。那么,如果你超过了这个极限,会发生什么呢?它会循环到最小的数吗?这可以解释我是如何得到一个负的结果。@C.Peck您可以在这里获得更多信息:这并不能解释为什么大数相乘会得到负的.except Double.MAX_值,所以最左边的位是“1”,这就是为什么值是负的。另一个好答案,令人惊讶的是,您现在可以发表评论了。除此之外,还有一个简短的提示:关注质量,而不是数量;-)你能解释一下“由于两个补码,数字变成负数”吗?我编辑了我的解释,加入了一个小计数系统的例子,希望这个解释足够彻底。