Java Double vs. BigDecimal – Java 双倍与大十进制

最后修改: 2023年 9月 22日

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

1. Overview

1.概述

The choice between Double vs. BigDecimal in Java can significantly impact performance as well as the precision and accuracy of floating-point numbers. In this tutorial, we’ll compare and contrast the characteristics, advantages, and disadvantages of these two classes, their use cases, and how to address precision and rounding issues with them.

在 Java 中选择 Double 还是 BigDecimal 会显著影响性能以及浮点数的精度和准确性 在本教程中,我们将比较和对比这两个类的特性、优缺点、使用案例以及如何使用它们解决精度和舍入问题。

2. Double

2.双</em

The Double class is a wrapper for the double primitive data type, which is well-suited for general-purpose floating-point arithmetic and works well in many scenarios. However, it has some limitations. The most prominent concern is its limited precision. Due to the nature of binary representation, double numbers might suffer from rounding errors when dealing with decimal fractions.

Double 类是 double 原始数据类型的包装器,它非常适合通用浮点运算,在许多情况下都能很好地工作。但是,它也有一些局限性。最突出的问题是其精度有限。由于二进制表示的性质,双数在处理十进制分数时可能会出现舍入错误

For example, the double literal 0.1 is not exactly equal to the decimal fraction 0.1, but rather to a slightly larger value:

例如,double 字面 0.1 并不完全等于十进制分数 0.1,而是一个稍大的值:

@Test
public void givenDoubleLiteral_whenAssigningToDoubleVariable_thenValueIsNotExactlyEqual() {
    double doubleValue = 0.1;
    double epsilon = 0.0000000000000001;
    assertEquals(0.1, doubleValue, epsilon);
}

3. BigDecimal

3. 大十进制

The BigDecimal class represents an immutable, arbitrary-precision, signed decimal number. It can handle numbers of any size without loss of precision. Imagine having a powerful magnifying glass that can zoom in on any part of the number line, allowing us to work with large or incredibly tiny numbers.

BigDecimal类表示不可变、任意精度、带符号的十进制数。它可以处理任何大小的数字,而不会降低精度。想象一下,如果有一个功能强大的放大镜,它可以放大数字线的任何部分,让我们可以处理大到难以置信的微小数字。

It consists of two parts: an unscaled value (an integer with arbitrary precision), and the scale (which indicates the number of digits after the decimal point). For example, the BigDecimal 3.14 has an unscaled value of 314 and a scale of 2.

它由两部分组成:未缩放值(具有任意精度的整数)和刻度(表示小数点后的位数)。例如,BigDecimal 3.14 的未缩放值为 314,缩放比例为 2。

The BigDecimal class offers better precision than Double, as it can perform calculations with arbitrary-precision decimals, avoiding the rounding errors arising from Double’s binary representation. That’s because BigDecimal uses integer arithmetic internally, which is more accurate than floating-point arithmetic.

BigDecimal类提供了比Double更高的精度,因为它可以使用任意精度的小数执行计算,避免了因 Double 的二进制表示而产生的四舍五入错误。这是因为 BigDecimal 在内部使用整数运算,而整数运算比浮点运算更精确。

Let’s see some examples of how to use the BigDecimal class in Java:

让我们看看如何在 Java 中使用 BigDecimal 类的一些示例:

private BigDecimal bigDecimal1 = new BigDecimal("124567890.0987654321");
private BigDecimal bigDecimal2 = new BigDecimal("987654321.123456789");

@Test
public void givenTwoBigDecimals_whenAdd_thenCorrect() {
    BigDecimal expected = new BigDecimal("1112222211.2222222211");
    BigDecimal actual = bigDecimal1.add(bigDecimal2);
    assertEquals(expected, actual);
}

@Test
public void givenTwoBigDecimals_whenMultiply_thenCorrect() {
    BigDecimal expected = new BigDecimal("123030014929277547.5030955772112635269");
    BigDecimal actual = bigDecimal1.multiply(bigDecimal2);
    assertEquals(expected, actual);
}

@Test
public void givenTwoBigDecimals_whenSubtract_thenCorrect() {
    BigDecimal expected = new BigDecimal("-863086431.0246913569");
    BigDecimal actual = bigDecimal1.subtract(bigDecimal2);
    assertEquals(expected, actual);
}

@Test
public void givenTwoBigDecimals_whenDivide_thenCorrect() {
    BigDecimal expected = new BigDecimal("0.13");
    BigDecimal actual = bigDecimal1.divide(bigDecimal2, 2, RoundingMode.HALF_UP);
    assertEquals(expected, actual);
}

4. Comparisons and Use Cases

4.比较和用例

4.1. Comparison Between Double and BigDecimal

4.1.DoubleBigDecimal 之间的比较

Converting from Double to BigDecimal is relatively straightforward. The BigDecimal class provides constructors that accept a double value as a parameter. However, the conversion doesn’t eliminate the precision limitations of a Double. Conversely, converting from BigDecimal to Double can result in data loss and rounding errors when fitting into Double’s constrained scope.

Double 转换为 BigDecimal 相对简单。BigDecimal 类提供了可接受 Double 值作为参数的构造函数。但是,这种转换并不能消除 Double 的精度限制。相反,BigDecimal 转换为 Double 时,如果适合 Double 的限制范围,可能会导致数据丢失和舍入错误

Let’s see both scenarios, that is, converting properly and losing precision:

让我们看看两种情况,即正确转换和失去精度:

@Test
void whenConvertingDoubleToBigDecimal_thenConversionIsCorrect() {
    double doubleValue = 123.456;
    BigDecimal bigDecimalValue = BigDecimal.valueOf(doubleValue);
    BigDecimal expected = new BigDecimal("123.456").setScale(3, RoundingMode.HALF_UP);
    assertEquals(expected, bigDecimalValue.setScale(3, RoundingMode.HALF_UP));
}
@Test
void givenDecimalPlacesGreaterThan15_whenConvertingBigDecimalToDouble_thenPrecisionIsLost() {
    BigDecimal bigDecimalValue = new BigDecimal("789.1234567890123456");
    double doubleValue = bigDecimalValue.doubleValue();
    BigDecimal convertedBackToBigDecimal = BigDecimal.valueOf(doubleValue);
    assertNotEquals(bigDecimalValue, convertedBackToBigDecimal);
}

In terms of speed and range, the utilization of hardware-level floating-point arithmetic in Java’s Double makes it faster than BigDecimal. The Double class covers a broad spectrum, accommodating both large and small numbers. However, its confinement within a 64-bit structure introduces precision limitations, especially for extremely large or small numbers. In contrast, BigDecimal presents a more extensive range of values and better precision across a wide array of values.

就速度和范围而言,Java 的 Double 中硬件级浮点运算的使用使其比 BigDecimal 更快。Double类涵盖的范围很广,可容纳大数和小数。但是,它被限制在 64 位结构中,这就带来了精度限制,尤其是对于超大或超小的数字。相比之下,BigDecimal 提供了更广泛的数值范围,并且在各种数值中具有更好的精度。

There are also differences in memory usage. Java’s Double is more compact, which results in more efficient memory usage. On the other hand, BigDecimal’s strength in arbitrary-precision arithmetic entails higher memory consumption. This can have implications for our application performance and scalability, especially in memory-intensive contexts.

内存使用方面也存在差异。Java的Double更加紧凑,因此内存使用效率更高。另一方面,BigDecimal 在任意精度运算方面的优势会导致更高的内存消耗。这可能会影响我们的应用性能和可扩展性,尤其是在内存密集型环境中。

4.2. Use Cases

4.2.使用案例

Double effortlessly interfaces with other numeric types, making it a convenient choice for basic arithmetic. It’s the go-to option when performance is a priority. Double’s speed and memory efficiency make it a solid choice for applications such as graphics and game development, which often involve real-time rendering and complex visual effects. Here, performance is crucial to maintain smooth user experiences.

Double 可以毫不费力地与其他数字类型接口,使其成为基本运算的便捷选择。当性能优先时,它是首选Double 的速度和内存效率使其成为图形和游戏开发等应用的可靠选择,这些应用通常涉及实时渲染和复杂的视觉效果。在这种情况下,性能对于保持流畅的用户体验至关重要。

On the other hand, BigDecimal shines when dealing with monetary calculations, where precision errors can result in substantial financial losses. It’s also a savior in scientific simulations requiring absolute precision. While BigDecimal may be slower and more memory-intensive, the assurance it provides in terms of accuracy can be invaluable in critical scenarios.

另一方面,BigDecimal在处理货币计算时大放异彩,因为精确度误差可能导致重大经济损失。在需要绝对精确的科学模拟中,它也是救星。虽然 BigDecimal 可能更慢、更耗费内存,但它在精度方面提供的保证在关键场景中是无价的。

As a result, BigDecimal is better suited for tasks in financial applications, scientific simulations, engineering and physical simulations, data analysis and reporting, and other domains where precision is critical.

因此,大十进制更适合金融应用、科学模拟、工程和物理模拟、数据分析和报告以及其他对精度要求极高的领域的任务

4.3. Precision and Rounding Considerations

4.3.精度和四舍五入考虑因素

With BigDecimal, we get to decide how many decimal places our calculations will have. This is useful when we need exact decimal calculations as it can store each decimal digit as-is.

使用 BigDecimal 时,我们可以决定计算的小数位数。这在我们需要精确的小数计算时非常有用,因为它可以原样存储每个小数位。

We can also choose how rounding happens in our calculations. Different rounding modes have different effects on our results, such as:

我们还可以选择计算中的四舍五入方式。不同的四舍五入模式会对计算结果产生不同的影响,例如

  • UP: increases the number to the next higher value (when we want to ensure that a value is never less than a certain amount)
  • DOWN: decreases the number to the preceding lower value (when we want to ensure that a value is never greater than a certain amount)
  • HALF_UP: rounds up if the discarded fraction is greater than 0.5
  • HALF_DOWN: rounds down if the discarded fraction is less than 0.5

This level of control over rounding and precision is another reason why BigDecimal is better for financial calculations when we need things to be accurate and uniform.

对四舍五入和精确度的这种控制水平是 BigDecimal 更适合财务计算的另一个原因,因为我们需要准确和统一。

Double introduces chances of tiny errors creeping in because of how computers represent numbers. Representing repeating decimals, like 1/3, can get tricky as they’ll result in an infinite binary expansion.

二进制由于计算机表示数字的方式,可能会出现微小的错误。表示重复的小数(如 1/3)可能会很棘手,因为它们会导致二进制无限扩展。

Simple numbers like 0.1 can get similarly messy when we try to represent them in binary (base 2). We’d get a repeating fraction like 0.00011001100110011… Computers have a limited number of bits to represent these fractions, so they have to round them off at some point. As a result, the stored value isn’t exactly 0.1, and this can lead to tiny errors when we perform calculations.

当我们尝试用二进制(2 进制)表示 0.1 这样的简单数字时,也会出现类似的混乱情况。我们会得到像 0.00011001100110011 这样的重复分数……计算机用于表示这些分数的位数有限,因此它们必须在某些时候四舍五入。因此,存储的值并不完全是 0.1,这可能导致我们在进行计算时出现微小的错误。

4.4. Comparison Table

4.4 对照表

Let’s summarize what we’ve learned about Double vs. BigDecimal in a table:

让我们用表格总结一下我们所学到的有关 DoubleBigDecimal 的知识:

Aspect Double BigDecimal
Precision Limited Arbitrary
Range Broad (both large and small) Extensive
Memory Usage Compact Higher
Performance Faster Slower
Use Cases General purpose Financial, Scientific

5. Conclusion

5.结论

In this article, we’ve discussed the nuances between the Java Double and BigDecimal types and the trade-offs between precision and performance when using them.

在本文中,我们讨论了 Java DoubleBigDecimal 类型之间的细微差别,以及使用它们时在精度和性能之间的权衡。

As usual, the code samples are available over on GitHub.

与往常一样,代码示例可在 GitHub 上获取