【问题标题】:Java: why does multiplying large positive number cause negative results? [duplicate]Java:为什么乘以大的正数会导致负结果? [复制]
【发布时间】:2019-03-24 11:09:12
【问题描述】:

我看到一些用 Java 乘以整数的奇怪行为。我正在做一些编码练习,并遇到了以下嘶嘶声类型的练习。要求:给定一个整数,编写一个函数,找出小于给定整数的每个 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;
}

这适用于小数字。然而,在测试中我发现一旦你以某种方式超过 33,返回值就会偏离。例如,如果我调用限制为 36 的函数,它会返回 -1.466221696E9。这就是我感到困惑的地方。我将 正数 整数相乘,结果却是负数。

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

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;
}

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

【问题讨论】:

  • 超过 Double.MAX_VALUE ,所以最左边的位是'1',这就是为什么值为负数

标签: java math types


【解决方案1】:

让我们以Integer 为例来研究一下。

Integer.MAX_VALUE 可以表示为01111111111111111111111111111111,这是一个32 位长字符串(包括符号位)。现在,如果您碰巧将1 添加到上述字符串中,则结果为10000000000000000000000000000000,与Integer.MIN_VALUE 相同。这称为Integer 的溢出。

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

根据Integer#toBinaryString

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

这就是为什么你看不到符号位,但Integer.MAX_VALUE 的实际值是01111111111111111111111111111111。现在看看这段代码:

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE + 1));
// 10000000000000000000000000000000
System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));
// 10000000000000000000000000000000

两个数字的输出相同。 Java 不能防止Integer 溢出。应该由开发人员处理这个问题。那么这个问题的可能解决方案是什么?您可以使用其他数据类型,例如longBigInteger。以下是您可能感兴趣的最大值:

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

一旦Integer 达到MAX_VALUE,它将开始溢出,您最终将得到负值。

【讨论】:

  • 哦。嗯。那么如果超过这个限制会发生什么,它只是循环到最小的数字吗?这可以解释我如何得到一个否定的结果。
  • @C.Peck 你可以在这里获得更多信息:stackoverflow.com/a/34704078/6099347
  • 这并不能解释为什么乘以大数会导致负数。
【解决方案2】:

如果您想完全避免整数溢出问题,请尝试使用 BigInteger:
https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html
这允许任意长度的整数。

(其实从BigInteger的源码看,好像有一个最大的大小,即int[]里面存储了BigInteger的:

Integer.MAX_VALUE / Integer.SIZE + 1

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

【讨论】:

  • 另一个不错的答案,令人惊讶的是,您现在可以发布 cmets。除此之外,快速说明:关注质量,而不是数量;-)
【解决方案3】:

这称为整数溢出。从本质上讲,对于存储解决方案的数据类型,您的数字变得太大。发生这种情况时,由于二进制补码,数字会环绕为负数。 double 是一种更大的数据类型,但如果你足够大,它也会变成负数。

two's complementinteger overflow 上查看这些文章。他们会彻底解释的。

编辑:维基文章的简化解释。我仍然建议你阅读这篇文章。 假设我们有一个 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 |
+---------+--------+

并且模式重复......

因此,对于更大的系统,同样的规则适用(32 位或 64 位)。这些数字将在2^(n-1) 处回绕,其中n 表示该数字有多少位(例如,对于如上所示的 3 位系统,它在 2^(3-1) = 2^2 = 4 处回绕)。请注意,使用二进制补码意味着正空间的范围减半。对于 3 位无符号整数,通常您可以从 0 数到 7,现在您只能在 -43 之间数数(仍然是 8 个数字)。

现在尝试将两个正数相乘: +2 * +3 = ?

通常这将是 +6 或 110。但在二进制补码中,这实际上是 -2。

【讨论】:

  • 你能解释一下“由于二进制补码,数字环绕为负数”吗?
  • 我编辑了我的解释以包含一个带有小型计数系统的示例,希望该解释足够彻底。
【解决方案4】:

超出 Double.MAX_VALUE 的范围,这就是结果为负数的原因

示例:

对于四位二进制数据是。

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

所以对于 4 位,我们将能够写入最多 15 个十进制值。

现在如果我想写 16 则溢出值范围。

所以如果我将十进制 16 转换为二进制数据,它将是 10000,所以符号位现在是 1。最终结果是否定的

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-13
    • 1970-01-01
    • 2021-07-08
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 2021-10-20
    • 2017-08-17
    相关资源
    最近更新 更多