Converting BigDecimal to Integer in Java – 在 Java 中将大十进制转换为整数

最后修改: 2024年 1月 14日

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

1. Overview

1.概述

BigDecimal is designed to work with floating point numbers. It provides a convenient way to manage the precision and, most importantly, deals with the rounding errors.

BigDecimal 设计用于处理浮点数。它提供了一种管理精度的便捷方法,最重要的是,它还能处理 四舍五入误差

However, in some cases, we need to work with it as a simple integer number and convert it to an Integer or an int. In this tutorial, we’ll learn how to do this properly and understand some underlying problems with the conversion.

但是,在某些情况下,我们需要将其作为一个简单的整数处理,并将其转换为 Integerint 。在本教程中,我们将学习如何正确进行转换,并了解转换中的一些潜在问题。

2. Narrowing Conversion

2.缩小转换范围

BigDecimal can store a much wider range of numbers than an Integer or an int. This often might lead to losing the precision during conversion.

Integerint 相比,BigDecimal 可以存储范围更广的数字。这通常会导致在转换过程中丢失 精度

2.1. Truncation

2.1.截断

BigDecimal provides us with the intValue(), which can convert it to an int:

BigDecimal 为我们提供了intValue() ,它可以将其转换为 int

@ParameterizedTest
@ArgumentsSource(SmallBigDecimalConversionArgumentsProvider.class)
void givenSmallBigDecimalWhenConvertToIntegerThenWontLosePrecision(BigDecimal given, int expected) {
    int actual = given.intValue();
    assertThat(actual).isEqualTo(expected);
}

BigDecimal can contain floating point values, but an int cannot. That’s why the intValue() method truncates all the numbers after the decimal point:

BigDecimal 可以包含浮点数值,但 int 不能。这就是 intValue() 方法截断小数点后所有数字的原因:

@ParameterizedTest
@ValueSource(doubles = {0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5})
void givenLargeBigDecimalWhenConvertToIntegerThenLosePrecision(double given) {
    BigDecimal decimal = BigDecimal.valueOf(given);
    int integerValue = decimal.intValue();
    double actual = Integer.valueOf(integerValue).doubleValue();
    assertThat(actual)
      .isEqualTo((int) given)
      .isNotEqualTo(given);
}

The behavior is similar to casting a double to an int or long. Thus, the number’s precision can be lost. At the same time, losing the precision might be acceptable for an application. However, we should always account for it.

这种行为与将 double 铸造为 intlong 类似。因此,数字的精度可能会丢失。同时,对于应用程序来说,丢失精度可能是可以接受的。但是,我们始终应该考虑到这一点。

2.2. Overflow

2.2 溢出

Another problem is the overflow while using intValue(). It’s similar to the previous issue but gives us an entirely incorrect result:

另一个问题是使用 intValue() 时出现 溢出它与前一个问题类似,但给出的结果完全不正确:

@ParameterizedTest
@ValueSource(longs = {Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 2,
  Long.MAX_VALUE - 3, Long.MAX_VALUE - 4, Long.MAX_VALUE - 5,
  Long.MAX_VALUE - 6, Long.MAX_VALUE - 7, Long.MAX_VALUE - 8})
void givenLargeBigDecimalWhenConvertToIntegerThenLosePrecision(long expected) {
    BigDecimal decimal = BigDecimal.valueOf(expected);
    int actual = decimal.intValue();
    assertThat(actual)
      .isNotEqualTo(expected)
      .isEqualTo(expected - Long.MAX_VALUE - 1);
}

At the same time, it’s a reasonable behavior, considering the binary number representation. We cannot store more bits of information than an int can take. In some scenarios, we have both of these problems:

同时,考虑到二进制数表示法,这是一种合理的行为。我们存储的信息比特数不能超过 int 所能承受的比特数。在某些应用场景中,我们同时存在这两个问题:

@ParameterizedTest
@ValueSource(doubles = {Long.MAX_VALUE - 0.5, Long.MAX_VALUE - 1.5, Long.MAX_VALUE - 2.5,
  Long.MAX_VALUE - 3.5, Long.MAX_VALUE - 4.5, Long.MAX_VALUE - 5.5,
  Long.MAX_VALUE - 6.5, Long.MAX_VALUE - 7.5, Long.MAX_VALUE - 8.5})
void givenLargeBigDecimalWhenConvertToIntegerThenLosePrecisionFromBothSides(double given) {
    BigDecimal decimal = BigDecimal.valueOf(given);
    int integerValue = decimal.intValue();
    double actual = Integer.valueOf(integerValue).doubleValue();
    assertThat(actual)
      .isNotEqualTo(Math.floor(given))
      .isNotEqualTo(given);
}

While intValue() might work for some cases, we must have a better solution to avoid unexpected bugs.

虽然 intValue() 在某些情况下可能有效,但我们必须有更好的解决方案来避免意外错误。

3. Precision Loss

3.精度损失

There are a couple of ways we can approach the issues we discussed. Although we cannot avoid the precision loss, we can make the process more explicit. 

我们可以通过几种方法来解决我们讨论过的问题。虽然我们无法避免精度损失,但我们可以使这一过程更加明确。

3.1. Checking the Scale

3.1.检查刻度

One of the most straightforward approaches is to check the scale of BigDecimal. We can identify if the given number contains a decimal point and assume it has some values afterward. This technique would work in most cases. However, it just identifies the presence of a decimal point and not if the number contains non-zero values after it:

最直接的方法之一是检查 BigDecimal 的刻度。我们可以识别给定的数字是否包含小数点,并假设它后面有一些值。但是,它只能识别小数点的存在,而不能识别数字后面是否包含非零值:

@ParameterizedTest
@ValueSource(doubles = {
  0.0, 1.00, 2.000, 3.0000, 
  4.00000, 5.000000, 6.00000000, 
  7.000000000, 8.0000000000})
void givenLargeBigDecimalWhenCheckScaleThenItGreaterThanZero(double given) {
    BigDecimal decimal = BigDecimal.valueOf(given);
    assertThat(decimal.scale()).isPositive();
    assertThat(decimal.toBigInteger()).isEqualTo((int) given);
}

In this example, the number 0.0 would have a scale equal to one. We might have some edge cases if we base our conversion behavior on the scale value.

在这个示例中,数字 0.0 的刻度值等于 1。如果我们将转换行为建立在刻度值的基础上,可能会出现一些边缘情况。

3.2. Defining Rounding

3.2.定义四舍五入

If losing precision is okay, we can set the scale to zero and identify the rounding strategy. This has a benefit over a simple intValue() invocation. We’ll explicitly define the rounding behavior:

如果损失精度没有问题,我们可以将刻度设置为零,并确定四舍五入策略。与简单的 intValue() 调用相比,这样做有一个好处。我们将明确定义四舍五入行为:

@ParameterizedTest
@ValueSource(doubles = {0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5})
void givenLargeBigDecimalWhenConvertToIntegerWithRoundingUpThenLosePrecision(double given) {
    BigDecimal decimal = BigDecimal.valueOf(given);
    int integerValue = decimal.setScale(0, RoundingMode.CEILING).intValue();
    double actual = Integer.valueOf(integerValue).doubleValue();
    assertThat(actual)
      .isEqualTo(Math.ceil(given))
      .isNotEqualTo(given);
}

We can use the RoundingMode enum to define the rules. It provides us with several predefined strategies to gain more control over the conversion.

我们可以使用 RoundingMode 枚举来定义规则它为我们提供了几种预定义策略,以获得对转换的更多控制。

4. Overflow Prevention

4.防止溢流

Overflow problems are different. While losing precision might be okay for an application, getting an entirely incorrect number is never acceptable.

溢出问题则不同。对于应用来说,失去精度可能没什么问题,但得到一个完全不正确的数字是绝对不能接受的。

4.1. Range Checking

4.1.范围检查

We can check if it’s possible to fit the BigDecimal value into an int. If we can do it, we use intValue() conversion. Otherwise, we can use a default value, for example, the smallest or the largest int:

我们可以检查是否可以将 BigDecimal 值转换为 int 值。如果可以,我们就使用 intValue() 转换。否则,我们可以使用默认值,例如最小或最大的 int

@ParameterizedTest
@ValueSource(longs = {
  Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 2,
  Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 2,
  0, 1, 2
})
void givenLargeBigDecimalWhenConvertToIntegerThenSetTheMaxOrMinValue(long expected) {
    BigDecimal decimal = BigDecimal.valueOf(expected);
    boolean tooBig = isTooBig(decimal);
    boolean tooSmall = isTooSmall(decimal);
    int actual;
    if (tooBig) {
        actual = Integer.MAX_VALUE;
    } else if (tooSmall) {
        actual = Integer.MIN_VALUE;
    } else {
        actual = decimal.intValue();
    }
    assertThat(tooBig).isEqualTo(actual == Integer.MAX_VALUE);
    assertThat(tooSmall).isEqualTo(actual == Integer.MIN_VALUE);
    assertThat(!tooBig && !tooSmall).isEqualTo(actual == expected);
}

In case it’s not possible to identify a reasonable default, we can throw an exception. BigDecimal API already provides us with a similar method.

如果无法确定合理的默认值,我们可以抛出异常。BigDecimal API 已经为我们提供了类似的方法。

4.2. Exact Value

4.2.精确值

BigDecimal has a safer version of intValue() intValueExact(). This method would throw ArithmeticException on any overflow in the decimal part:

BigDecimal 有一个更安全的 intValue() 版本 – intValueExact() 。该方法会在小数部分出现溢出时抛出 ArithmeticException 异常:

@ParameterizedTest
@ValueSource(longs = {Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 2,
  Long.MAX_VALUE - 3, Long.MAX_VALUE - 4, Long.MAX_VALUE - 5,
  Long.MAX_VALUE - 6, Long.MAX_VALUE - 7, Long.MAX_VALUE - 8})
void givenLargeBigDecimalWhenConvertToExactIntegerThenThrowException(long expected) {
    BigDecimal decimal = BigDecimal.valueOf(expected);
    assertThatExceptionOfType(ArithmeticException.class)
      .isThrownBy(decimal::intValueExact);
}

This way, we can ensure that our application will handle the overflow and won’t allow an incorrect state.

这样,我们就可以确保我们的应用程序能够处理溢出,并且不会出现不正确的状态。

5. Conclusion

5.结论</b

Conversion of numeric values might sound trivial, but even a simple conversion can introduce hard-to-debug issues in an application. Thus, we should be careful with narrowing conversion and always consider precision loss and overflow.

数值的转换听起来似乎微不足道,但即使是简单的转换也会在应用程序中引入难以调试的问题。因此,我们应该谨慎对待缩小范围的转换,并始终考虑精度损失和溢出问题。

BigDecimal provides various convenient methods to simplify the conversion and give us more control over the process.

BigDecimal提供了各种方便的方法来简化转换,并让我们对转换过程有更多的控制。

As usual, all the code from this tutorial is available over on GitHub.

与往常一样,本教程中的所有代码都可以在 GitHub 上获取