Overflow and Underflow in Java – Java中的溢出和下溢

最后修改: 2019年 11月 30日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll look at the overflow and underflow of numerical data types in Java.

在本教程中,我们将看看Java中数字数据类型的溢出和下溢。

We won’t dive deeper into the more theoretical aspects — we’ll just focus on when it happens in Java.

我们不会深入研究更多的理论方面–我们只关注它在Java中发生的时间。

First, we’ll look at integer data types, then at floating-point data types. For both, we’ll also see how we can detect when over- or underflow occurs.

首先,我们将看一下整数数据类型,然后是浮点数据类型。对于这两种类型,我们还将看到如何检测溢出或欠溢出的情况。

2. Overflow and Underflow

2.溢出和下溢

Simply put, overflow and underflow happen when we assign a value that is out of range of the declared data type of the variable.

简单地说,当我们分配的数值超出变量声明的数据类型范围时,就会发生溢出和下溢。

If the (absolute) value is too big, we call it overflow, if the value is too small, we call it underflow. 

如果(绝对)数值过大,我们称之为溢出,如果数值过小,我们称之为下溢。

Let’s look at an example where we attempt to assign the value 101000 (a 1 with 1000 zeros) to a variable of type int or double. The value is too big for an int or double variable in Java, and there will be an overflow.

让我们看一个例子,我们试图将101000(一个带有1000零的1)的值赋给一个intdouble类型的变量。对于Java中的intdouble变量来说,这个值太大了,会出现溢出。

As a second example, let’s say we attempt to assign the value 10-1000 (which is very close to 0) to a variable of type double. This value is too small for a double variable in Java, and there will be an underflow.

作为第二个例子,假设我们试图将10-1000(非常接近于0)的值赋给double类型的变量。这个值对于Java中的double变量来说太小了,所以会有一个下溢。

Let’s see what happens in Java in these cases in more detail.

让我们更详细地看看在这些情况下在Java中会发生什么。

3. Integer Data Types

3.整数数据类型

The integer data types in Java are byte (8 bits), short (16 bits), int (32 bits), and long (64 bits).

Java中的整数数据类型是byte(8位)、short(16位)、int(32位)和long(64位)。

Here, we’ll focus on the int data type. The same behavior applies to the other data types, except that the minimum and maximum values differ.

在这里,我们将重点讨论int数据类型。同样的行为适用于其他数据类型,只是最小值和最大值有所不同。

An integer of type int in Java can be negative or positive, which means with its 32 bits, we can assign values between -231 (-2147483648) and 231-1 (2147483647).

Java中int类型的整数可以是负数或正数,这意味着用它的32位,我们可以在-231-2147483648)和231-12147483647)之间分配值。

The wrapper class Integer defines two constants that hold these values: Integer.MIN_VALUE and Integer.MAX_VALUE.

封装类Integer定义了两个持有这些值的常量。Integer.MIN_VALUEInteger.MAX_VALUE

3.1. Example

3.1.例子

What will happen if we define a variable m of type int and attempt to assign a value that’s too big (e.g., 21474836478 = MAX_VALUE + 1)?

如果我们定义一个m类型的变量int,并试图分配一个太大的值(例如,21474836478 = MAX_VALUE + 1),会发生什么?

A possible outcome of this assignment is that the value of m will be undefined or that there will be an error.

这个赋值的可能结果是,m的值将是未定义的,或者会有一个错误。

Both are valid outcomes; however, in Java, the value of m will be -2147483648 (the minimum value). On the other hand, if we attempt to assign a value of -2147483649 (= MIN_VALUE – 1), m will be 2147483647 (the maximum value). This behavior is called integer-wraparound.

这两种结果都是有效的;但是,在Java中,m的值将是-2147483648(最小值)。另一方面,如果我们试图分配一个-2147483649的值(= MIN_VALUE – 1),m将是2147483647(最大值)。这种行为被称为整数绕射。

Let’s consider the following code snippet to illustrate this behavior better:

让我们考虑下面的代码片段,以更好地说明这种行为。

int value = Integer.MAX_VALUE-1;
for(int i = 0; i < 4; i++, value++) {
    System.out.println(value);
}

We’ll get the following output, which demonstrates the overflow:

我们会得到下面的输出,这表明了溢出的情况。

2147483646
2147483647
-2147483648
-2147483647

4. Handling Underflow and Overflow of Integer Data Types

4.处理整数数据类型的下溢和溢出

Java does not throw an exception when an overflow occurs; that is why it can be hard to find errors resulting from an overflow. Nor can we directly access the overflow flag, which is available in most CPUs.

当溢出发生时,Java不会抛出一个异常;这就是为什么很难发现溢出导致的错误。我们也不能直接访问溢出标志,这在大多数CPU中是可用的。

However, there are various ways to handle a possible overflow. Let’s look at several of these possibilities.

然而,有各种方法来处理可能出现的溢出。让我们来看看其中的几种可能性。

4.1. Use a Different Data Type

4.1.使用不同的数据类型

If we want to allow values larger than 2147483647 (or smaller than -2147483648), we can simply use the long data type or a BigInteger instead.

如果我们想允许大于2147483647(或小于2147483648)的值,我们可以简单地使用long数据类型或BigInteger代替。

Though variables of type long can also overflow, the minimum and maximum values are much larger and are probably sufficient in most situations.

尽管long类型的变量也会溢出,但最小值和最大值要大得多,在大多数情况下可能已经足够。

The value range of BigInteger is not restricted, except by the amount of memory available to the JVM.

BigInteger的值范围不受限制,除了JVM的可用内存量。

Let’s see how to rewrite our above example with BigInteger:

让我们看看如何用BigInteger重写我们上面的例子。

BigInteger largeValue = new BigInteger(Integer.MAX_VALUE + "");
for(int i = 0; i < 4; i++) {
    System.out.println(largeValue);
    largeValue = largeValue.add(BigInteger.ONE);
}

We’ll see the following output:

我们将看到以下输出。

2147483647
2147483648
2147483649
2147483650

As we can see in the output, there’s no overflow here. Our article BigDecimal and BigInteger in Java covers BigInteger in more detail.

正如我们在输出中看到的,这里没有溢出。我们的文章BigDecimalBigInteger in Java更详细地介绍了BigInteger

4.2. Throw an Exception

4.2.抛出一个异常

There are situations where we don’t want to allow larger values, nor do we want an overflow to occur, and we want to throw an exception instead.

在有些情况下,我们不希望允许更大的值,也不希望发生溢出,我们希望抛出一个异常来代替。

As of Java 8, we can use the methods for exact arithmetic operations. Let’s look at an example first:

从Java 8开始,我们可以使用这些方法进行精确的算术运算。我们先来看一个例子。

int value = Integer.MAX_VALUE-1;
for(int i = 0; i < 4; i++) {
    System.out.println(value);
    value = Math.addExact(value, 1);
}

The static method addExact() performs a normal addition, but throws an exception if the operation results in an overflow or underflow:

静态方法addExact()执行正常的加法,但如果操作导致溢出或下溢,则抛出一个异常。

2147483646
2147483647
Exception in thread "main" java.lang.ArithmeticException: integer overflow
	at java.lang.Math.addExact(Math.java:790)
	at baeldung.underoverflow.OverUnderflow.main(OverUnderflow.java:115)

In addition to addExact(), the Math package in Java 8 provides corresponding exact methods for all arithmetic operations. See the Java documentation for a list of all these methods.

除了addExact()之外,Java 8中的Math包为所有算术运算提供了相应的精确方法。有关所有这些方法的列表,请参见Java 文档

Furthermore, there are exact conversion methods, which throw an exception if there is an overflow during the conversion to another data type.

此外,还有一些精确的转换方法,如果在转换到另一种数据类型的过程中出现溢出,则会抛出一个异常。

For the conversion from a long to an int:

用于从longint的转换。

public static int toIntExact(long a)

And for the conversion from BigInteger to an int or long:

而对于从BigIntegerintlong的转换。

BigInteger largeValue = BigInteger.TEN;
long longValue = largeValue.longValueExact();
int intValue = largeValue.intValueExact();

4.3. Before Java 8

4.3.在Java 8之前

The exact arithmetic methods were added to Java 8. If we use an earlier version, we can simply create these methods ourselves. One option to do so is to implement the same method as in Java 8:

确切的算术方法被添加到了Java 8中。如果我们使用较早的版本,我们可以简单地自己创建这些方法。这样做的一个选项是实现与Java 8中相同的方法。

public static int addExact(int x, int y) {
    int r = x + y;
    if (((x ^ r) & (y ^ r)) < 0) {
        throw new ArithmeticException("int overflow");
    }
    return r;
}

5. Non-Integer Data Types

5.非整数数据类型

The non-integer types float and double do not behave in the same way as the integer data types when it comes to arithmetic operations.

当涉及到算术运算时,非整数类型floatdouble的行为方式与整数数据类型不一样。

One difference is that arithmetic operations on floating-point numbers can result in a NaN. We have a dedicated article on NaN in Java, so we won’t look further into that in this article. Furthermore, there are no exact arithmetic methods such as addExact or multiplyExact for non-integer types in the Math package.

一个区别是,对浮点数的算术运算会导致NaN。我们有一篇关于Java中的NaN的专门文章,所以我们不会在本文中进一步探讨这个问题。此外,在Math包中没有针对非整数类型的精确算术方法,如addExactmultiplyExact

Java follows the IEEE Standard for Floating-Point Arithmetic (IEEE 754) for its float and double data types. This standard is the basis for the way that Java handles over- and underflow of floating-point numbers.

Java的floatdouble数据类型遵循IEEE浮点运算标准(IEEE 754)。这个标准是Java处理浮点数溢出和下溢的方式的基础。

In the below sections, we’ll focus on the over- and underflow of the double data type and what we can do to handle the situations in which they occur.

在下面的章节中,我们将重点讨论double数据类型的溢出和下溢,以及我们可以做什么来处理它们发生的情况。

5.1. Overflow

5.1 溢出

As for the integer data types, we might expect that:

至于整数数据类型,我们可能会期待。

assertTrue(Double.MAX_VALUE + 1 == Double.MIN_VALUE);

However, that is not the case for floating-point variables. The following is true:

然而,对于浮点变量来说,情况并非如此。下面的情况是真实的。

assertTrue(Double.MAX_VALUE + 1 == Double.MAX_VALUE);

This is because a double value has only a limited number of significant bits. If we increase the value of a large double value by only one, we do not change any of the significant bits. Therefore, the value stays the same.

这是因为一个double值只有有限的重要位。如果我们将一个大的double值只增加一个,我们不会改变任何一个有效位。因此,该值保持不变。

If we increase the value of our variable such that we increase one of the significant bits of the variable, the variable will have the value INFINITY:

如果我们增加我们的变量的值,使我们增加变量的一个有效位,那么该变量的值将是INFINITY

assertTrue(Double.MAX_VALUE * 2 == Double.POSITIVE_INFINITY);

and NEGATIVE_INFINITY for negative values:

NEGATIVE_INFINITY的负值。

assertTrue(Double.MAX_VALUE * -2 == Double.NEGATIVE_INFINITY);

We can see that, unlike for integers, there’s no wraparound, but two different possible outcomes of the overflow: the value stays the same, or we get one of the special values, POSITIVE_INFINITY or NEGATIVE_INFINITY.

我们可以看到,与整数不同的是,没有包络,但溢出有两种不同的可能结果:值保持不变,或者我们得到一个特殊的值,POSITIVE_INFINITYNEGATIVE_INFINITY

5.2. Underflow

5.2.下溢

There are two constants defined for the minimum values of a double value: MIN_VALUE (4.9e-324) and MIN_NORMAL (2.2250738585072014E-308).

有两个常数定义了double值的最小值。MIN_VALUE(4.9e-324)和MIN_NORMAL(2.2250738585072014E-308)。

IEEE Standard for Floating-Point Arithmetic (IEEE 754) explains the details for the difference between those in more detail.

IEEE浮点运算标准(IEEE 754)更详细地解释了这些区别的细节。

Let’s focus on why we need a minimum value for floating-point numbers at all.

让我们来关注一下为什么我们需要一个浮点数的最小值。

A double value cannot be arbitrarily small as we only have a limited number of bits to represent the value.

一个double值不可能是任意小的,因为我们只有有限的比特数来表示这个值。

The chapter about Types, Values, and Variables in the Java SE language specification describes how floating-point types are represented. The minimum exponent for the binary representation of a double is given as -1074. That means the smallest positive value a double can have is Math.pow(2, -1074), which is equal to 4.9e-324.

Java SE语言规范中关于类型、值和变量的章节描述了浮点类型的表示方法。double的二进制表示法的最小指数是-1074。这意味着双倍数的最小正值是Math.pow(2, -1074),等于4.9e-324

As a consequence, the precision of a double in Java does not support values between 0 and 4.9e-324, or between -4.9e-324 and 0 for negative values.

因此,Java中double的精度不支持0和4.9e-324之间的值,4.9e-3240之间的负值。

So what happens if we attempt to assign a too-small value to a variable of type double? Let’s look at an example:

那么,如果我们试图给一个double类型的变量分配一个太小的值,会发生什么?让我们看一个例子。

for(int i = 1073; i <= 1076; i++) {
    System.out.println("2^" + i + " = " + Math.pow(2, -i));
}

With output:

有输出。

2^1073 = 1.0E-323
2^1074 = 4.9E-324
2^1075 = 0.0
2^1076 = 0.0

We see that if we assign a value that’s too small, we get an underflow, and the resulting value is 0.0 (positive zero).
Similarly, for negative values, an underflow will result in a value of -0.0 (negative zero).

我们看到,如果我们分配的值太小,就会出现下溢,结果是0.0(正零)。
同样地,对于负值,下溢将导致一个-0.0(负零)的值。

6. Detecting Underflow and Overflow of Floating-Point Data Types

6.检测浮点数据类型的下溢和溢出

As overflow will result in either positive or negative infinity, and underflow in a positive or negative zero, we do not need exact arithmetic methods like for the integer data types. Instead, we can check for these special constants to detect over- and underflow.

由于溢出将导致正或负的无穷大,而下溢将导致正或负的零,我们不需要像整数数据类型那样的精确算术方法。相反,我们可以检查这些特殊的常数来检测溢出和下溢。

If we want to throw an exception in this situation, we can implement a helper method. Let’s look at how that can look for the exponentiation:

如果我们想在这种情况下抛出一个异常,我们可以实现一个辅助方法。让我们看看这对指数化来说是怎样的。

public static double powExact(double base, double exponent) {
    if(base == 0.0) {
        return 0.0;
    }
    
    double result = Math.pow(base, exponent);
    
    if(result == Double.POSITIVE_INFINITY ) {
        throw new ArithmeticException("Double overflow resulting in POSITIVE_INFINITY");
    } else if(result == Double.NEGATIVE_INFINITY) {
        throw new ArithmeticException("Double overflow resulting in NEGATIVE_INFINITY");
    } else if(Double.compare(-0.0f, result) == 0) {
        throw new ArithmeticException("Double overflow resulting in negative zero");
    } else if(Double.compare(+0.0f, result) == 0) {
        throw new ArithmeticException("Double overflow resulting in positive zero");
    }

    return result;
}

In this method, we need to use the method Double.compare(). The normal comparison operators (< and >) do not distinguish between positive and negative zero.

在这个方法中,我们需要使用Double.compare()/em>方法。一般的比较运算符(<>)不区分正负零。

7. Positive and Negative Zero

7.正的和负的

Finally, let’s look at an example that shows why we need to be careful when working with positive and negative zero and infinity.

最后,让我们看一个例子,说明为什么我们在处理正负零和无穷大时需要谨慎。

Let’s define a couple of variables to demonstrate:

让我们定义几个变量来演示一下。

double a = +0f;
double b = -0f;

Because positive and negative 0 are considered equal:

因为正和负的0被认为是相等的:

assertTrue(a == b);

Whereas positive and negative infinity are considered different:

其中正无穷和负无穷被认为是不同的:

assertTrue(1/a == Double.POSITIVE_INFINITY);
assertTrue(1/b == Double.NEGATIVE_INFINITY);

However, the following assertion is correct:

然而,以下论断是正确的。

assertTrue(1/a != 1/b);

Which seems to be a contradiction to our first assertion.

这似乎与我们的第一个论断相矛盾。

8. Conclusion

8.结语

In this article, we saw what is over- and underflow, how it can occur in Java, and what is the difference between the integer and floating-point data types.

在这篇文章中,我们看到了什么是溢出和下溢,它在Java中是如何发生的,以及整数和浮点数据类型之间的区别是什么。

We also saw how we could detect over- and underflow during program execution.

我们还看到了如何在程序执行过程中检测溢出和下溢。

As usual, the complete source code is available over on Github.

像往常一样,完整的源代码可在Github上获得。