At first glance, floating point math seems straightforward. But if you've ever been surprised by the result of 0.1 + 0.2
not being exactly 0.3
in Java, you're not alone.
This behavior isn’t a bug, it’s a result of how floating point numbers are represented in binary using the IEEE 754 standard. Because numbers like 0.1
and 0.2
don’t have exact binary equivalents, rounding errors occur, leading to results like 0.30000000000000004
instead of the expected 0.3
.
These small inaccuracies can accumulate and cause significant issues in financial or scientific applications, where precision is critical.
Real Example: Float vs BigDecimal
Let's look at a real-world scenario to illustrate this:
@Test
void exampleFloatingPoint() {
float somaFloat = (float) DoubleStream.of(19.41f, -0.00045f).sum();
Float resultadoFloat = toMoneyDto(somaFloat);
BigDecimal resultadoBD = BigDecimal.valueOf(19.41)
.add(BigDecimal.valueOf(-0.00045f).setScale(4, RoundingMode.HALF_UP));
System.out.println("Sum with float (DecimalFormat): " + resultadoFloat);
System.out.println("Sum with BigDecimal: " + resultadoBD);
}
In this test:
We're summing floating point numbers, including a small negative adjustment.
The
toMoneyDto
method likely rounds the float to four decimal places using DecimalFormat.For comparison, we use
BigDecimal
, which provides much better control over precision and rounding.
Output might be something like:
Sum with Float (toMoneyDto): 19.4095
Sum with BigDecimal: 19.4096
Even though both approaches seem to work, the float version may lose or hide small precision errors, depending on the rounding logic. Over time or at scale, these tiny errors can have big impacts.
Access the code here: GitHub Repo
Why This Matters
Floating point types are not suitable for exact values like money or measurements that require precision. For those, BigDecimal
is the safer, more accurate choice, even though it's more verbose and slightly slower.
Want to Go Deeper?
🗨️ Have you encountered floating point surprises in your code? Or watched a session that helped you understand it better? Share it in the comments!
Excellent article, Thiago. Adding to the point, maybe, a recommended next step is to encapsulate it within a dedicated domain object, such as a Money class. This practice avoids Primitive Obsession and centralizes business rules, like currency rounding, in a single place resulting in a more type-safe and self-documenting API.